This commit is contained in:
Harald Hoyer 2017-04-26 15:26:07 +02:00 committed by Harald Hoyer
parent e2fded1068
commit 0459ddc966
7 changed files with 261 additions and 193 deletions

View file

@ -80,27 +80,14 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>

View file

@ -45,6 +45,8 @@ import android.widget.Toast;
import com.example.android.bluetoothchat.R;
import org.surfsite.iconsole.common.logger.Log;
import java.util.Locale;
/**
* This fragment controls Bluetooth to communicate with other devices.
*/
@ -59,8 +61,9 @@ public class BluetoothChatFragment extends Fragment {
// Layout Views
private ListView mConversationView;
private EditText mOutEditText;
private Button mSendButton;
private Button mStartButton;
private Button mStopButton;
private Button mDisconnectButton;
/**
* Name of the connected device
@ -72,11 +75,6 @@ public class BluetoothChatFragment extends Fragment {
*/
private ArrayAdapter<String> mConversationArrayAdapter;
/**
* String buffer for outgoing messages
*/
private StringBuffer mOutStringBuffer;
/**
* Local Bluetooth adapter
*/
@ -150,8 +148,9 @@ public class BluetoothChatFragment extends Fragment {
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
mConversationView = (ListView) view.findViewById(R.id.in);
mOutEditText = (EditText) view.findViewById(R.id.edit_text_out);
mSendButton = (Button) view.findViewById(R.id.button_send);
mStartButton = (Button) view.findViewById(R.id.button_start);
mStopButton = (Button) view.findViewById(R.id.button_stop);
mDisconnectButton = (Button) view.findViewById(R.id.button_disconnect);
}
/**
@ -165,27 +164,30 @@ public class BluetoothChatFragment extends Fragment {
mConversationView.setAdapter(mConversationArrayAdapter);
// Initialize the compose field with a listener for the return key
mOutEditText.setOnEditorActionListener(mWriteListener);
// Initialize the send button with a listener that for click events
mSendButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Send a message using content of the edit text widget
View view = getView();
if (null != view) {
TextView textView = (TextView) view.findViewById(R.id.edit_text_out);
String message = textView.getText().toString();
sendMessage(message);
}
}
});
// Initialize the BluetoothChatService to perform bluetooth connections
mChatService = new BluetoothChatService(getActivity(), mHandler);
// Initialize the buffer for outgoing messages
mOutStringBuffer = new StringBuffer("");
mStartButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mChatService.startIConsole();
}
});
mStopButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mChatService.stopIConsole();
}
});
mDisconnectButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mChatService.stop();
}
});
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
}
/**
@ -212,33 +214,9 @@ public class BluetoothChatFragment extends Fragment {
return;
}
// Check that there's actually something to send
if (message.length() > 0) {
// Get the message bytes and tell the BluetoothChatService to write
byte[] send = message.getBytes();
mChatService.write(send);
// Reset out string buffer to zero and clear the edit text field
mOutStringBuffer.setLength(0);
mOutEditText.setText(mOutStringBuffer);
}
mChatService.startIConsole();
}
/**
* The action listener for the EditText widget, to listen for the return key
*/
private TextView.OnEditorActionListener mWriteListener
= new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
// If the action is a key-up event on the return key, send the message
if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) {
String message = view.getText().toString();
sendMessage(message);
}
return true;
}
};
/**
* Updates the status on the action bar.
*
@ -286,16 +264,32 @@ public class BluetoothChatFragment extends Fragment {
case BluetoothChatService.STATE_CONNECTED:
setStatus(getString(R.string.title_connected_to, mConnectedDeviceName));
mConversationArrayAdapter.clear();
mStartButton.setEnabled(true);
mStopButton.setEnabled(true);
mDisconnectButton.setEnabled(true);
break;
case BluetoothChatService.STATE_CONNECTING:
setStatus(R.string.title_connecting);
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
break;
case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE:
setStatus(R.string.title_not_connected);
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
break;
}
break;
case Constants.MESSAGE_DATA:
IConsole.Data data = (IConsole.Data) msg.obj;
String dataMessage = String.format(Locale.US, "Time: %s Speed: %f Power: %f RPM: %d LVL: %d Dist: %f Cal: %d HF: %d",
data.mTime, data.mSpeed10 / 10.0, data.mPower10 / 10.0, data.mRPM,
data.mLevel, data.mDistance10 / 10.0, data.mCalories, data.mHF);
mConversationArrayAdapter.add(mConnectedDeviceName + ": " + dataMessage);
break;
case Constants.MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer

View file

@ -203,13 +203,12 @@ public class BluetoothChatService {
updateUserInterfaceTitle();
}
/**
* Write to the ConnectedThread in an unsynchronized manner
*
* @param out The bytes to write
* @see ConnectedThread#write(byte[])
*/
public void write(byte[] out) {
public synchronized boolean startIConsole() {
return mConnectedThread.startIConsole();
}
public synchronized boolean stopIConsole() {
return mConnectedThread.startIConsole();
}
/**
@ -346,8 +345,10 @@ public class BluetoothChatService {
mmIConsole = new IConsole(mmInStream, mmOutStream, new IConsole.DataListener() {
@Override
public void onData(IConsole.Data data) {
Log.i(TAG, "mConnectedThread: " + data.toString());
/* print */
Log.i(TAG, "mConnectedThread: onData");
// Share the sent message back to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_DATA, -1, -1, data)
.sendToTarget();
}
@Override
@ -361,7 +362,7 @@ public class BluetoothChatService {
@Override
public void onRead(byte[] buffer) {
if (buffer.length > 0) {
String hexbuf = IConsole.byteArrayToHex(Arrays.copyOfRange(buffer, 0, buffer.length)) + '\n';
String hexbuf = IConsole.byteArrayToHex(Arrays.copyOfRange(buffer, 0, buffer.length));
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_READ, hexbuf.length(), -1, hexbuf.getBytes())
@ -371,7 +372,7 @@ public class BluetoothChatService {
@Override
public void onWrite(byte[] buffer) {
String hexbuf = IConsole.byteArrayToHex(buffer) + '\n';
String hexbuf = IConsole.byteArrayToHex(buffer);
// Share the sent message back to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, hexbuf.getBytes())
@ -383,15 +384,25 @@ public class BluetoothChatService {
public void run() {
Log.i(TAG, "BEGIN mConnectedThread");
mmIConsole.start();
while (mState == STATE_CONNECTED) {
if (!mmIConsole.processIO())
break;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
; // ignore
}
}
}
public boolean setLevel(int level) {
return mmIConsole.setLevel(level);
}
public boolean startIConsole() { return mmIConsole.start(); }
public boolean stopIConsole() { return mmIConsole.stop(); }
public void cancel() {
mmIConsole.stop();

View file

@ -27,6 +27,7 @@ public interface Constants {
public static final int MESSAGE_WRITE = 3;
public static final int MESSAGE_DEVICE_NAME = 4;
public static final int MESSAGE_TOAST = 5;
public static final int MESSAGE_DATA = 6;
// Key names received from the BluetoothChatService Handler
public static final String DEVICE_NAME = "device_name";

View file

@ -1,7 +1,10 @@
package org.surfsite.iconsole;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;
/**
* Created by harald on 25.04.17.
@ -10,7 +13,7 @@ import java.io.OutputStream;
class IConsole {
private static final byte[] PING = {(byte) 0xf0, (byte) 0xa0, (byte) 0x01, (byte) 0x01, (byte) 0x92 };
private static final byte[] INIT_A0 = {(byte) 0xf0, (byte) 0xa0, 0x02, 0x02, (byte) 0x94};
private static final byte[] PONG = {(byte) 0xf0, (byte) 0xb0, 0x01, 0x01, (byte) 0xa2};
//private static final byte[] PONG = {(byte) 0xf0, (byte) 0xb0, 0x01, 0x01, (byte) 0xa2};
private static final byte[] STATUS = {(byte) 0xf0, (byte) 0xa1, 0x01, 0x01, (byte) 0x93};
private static final byte[] INIT_A3 = {(byte) 0xf0, (byte) 0xa3, 0x01, 0x01, 0x01, (byte) 0x96};
private static final byte[] INIT_A4 = {(byte) 0xf0, (byte) 0xa4, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, (byte) 0xa0};
@ -24,6 +27,7 @@ class IConsole {
PING,
A0,
A1,
A1_POST_PING,
A3,
A4,
START,
@ -32,9 +36,13 @@ class IConsole {
SETLEVEL,
}
private State mCurrentState;
private State mNextState;
private int mSetLevel;
private State mCurrentState = State.BEGIN;
private State mNextState = State.PING;
private boolean mWaitAck = false;
private int mSetLevel = 1;
private long mTimesent = 0;
private int mExpectLen = 0;
private byte[] mExpectPacket;
private final InputStream mInputStream;
private final OutputStream mOutputStream;
private final DataListener mDataListener;
@ -45,12 +53,9 @@ class IConsole {
this.mOutputStream = outputStream;
this.mDataListener = dataListener;
this.mDebugListener = debugListener;
this.mCurrentState = State.BEGIN;
this.mNextState = State.PING;
this.mSetLevel = 1;
}
class Data {
class Data implements java.io.Serializable {
long mTime; // in seconds
int mSpeed10;
int mRPM;
@ -93,21 +98,17 @@ class IConsole {
void onWrite(byte[] bytes);
}
boolean processIO() {
boolean start() {
synchronized (this) {
Data data = new Data(0, 0, 0, 0, 0, 0, 0, 0);
if (null != mDebugListener) {
mDebugListener.onWrite(PING);
mDebugListener.onRead(PONG);
}
mDataListener.onData(data);
this.mNextState = State.A0;
}
return true;
}
boolean stop() {
synchronized (this) {
this.mNextState = State.STOP;
}
return true;
}
@ -116,121 +117,190 @@ class IConsole {
if (mCurrentState != State.READ)
return false;
this.mCurrentState = State.SETLEVEL;
this.mNextState = State.READ;
this.mNextState = State.SETLEVEL;
this.mSetLevel = level;
}
return true;
}
/*
def send_ack(packet, expect=None, plen=0):
if expect == None:
expect = 0xb0 | (ord(packet[1]) & 0xF)
boolean send(byte[] packet, byte expect, int plen) throws IOException {
long now = System.currentTimeMillis();
if plen == 0:
plen = len(packet)
if ((now - mTimesent) < 200) {
return false;
}
got = None
while got == None:
sleep(0.1)
sock.sendall(packet)
i = 0
while got == None and i < 6:
i+=1
sleep(0.1)
got = sock.recv(plen)
if len(got) == plen:
#print "<-" + hexlify(got)
pass
else:
if len(got) > 0:
#print "Got len == %d" % len(got)
pass
got = None
// Flush input stream
try {
mInputStream.skip(mInputStream.available());
} catch (IOException e) {
; // ignore
}
if got and len(got) >= 3 and got[0] == packet[0] and ord(got[1]) == expect:
break
got = None
#print "---> Retransmit"
return got
// Send packet
mOutputStream.write(packet);
def send_level(lvl):
packet = struct.pack('BBBBBB', 0xf0, 0xa6, 0x01, 0x01, lvl+1, (0xf0+0xa6+3+lvl) & 0xFF)
got = send_ack(packet)
return got
if (null != mDataListener)
mDebugListener.onWrite(packet);
*/
mTimesent = System.currentTimeMillis();
mExpectPacket = packet;
mExpectPacket[1] = expect;
mExpectLen = plen;
mWaitAck = true;
return true;
}
/*
send_ack(PING)
prints(win, "ping done")
private boolean send(byte[] packet) throws IOException {
return send(packet, (byte)(0xb0 | (packet[1] & 0xF)), packet.length);
}
send_ack(INIT_A0, expect=0xb7, plen=6)
prints(win, "A0 done")
private boolean send(byte[] packet, int plen) throws IOException {
return send(packet, (byte)(0xb0 | (packet[1] & 0xF)), plen);
}
for i in range(0, 5):
send_ack(PING)
prints(win, "ping done")
private boolean send_level(int level) throws IOException {
byte[] packet = SETLEVEL.clone();
packet[4] = (byte) (packet[4] + level);
packet[5] = (byte) ((packet[5] + level) & 0xFF);
return send(packet);
}
send_ack(STATUS, plen=6)
prints(win, "status done")
private byte[] wait_ack() throws IOException, TimeoutException {
byte[] buffer = new byte[mExpectLen];
int bytes;
send_ack(PING)
prints(win, "ping done")
long now = System.currentTimeMillis();
send_ack(INIT_A3)
prints(win, "A3 done")
if ((now - mTimesent) > 1000000) {
mWaitAck = false;
return null;
}
send_ack(INIT_A4)
prints(win, "A4 done")
if (mInputStream.available() < mExpectLen)
return null;
send_ack(START)
prints(win, "START done")
bytes = mInputStream.read(buffer);
level = 1
if (null != mDebugListener)
mDebugListener.onRead(Arrays.copyOfRange(buffer, 0, bytes));
while True:
sleep(0.25)
while True:
key = win.getch()
if key == ord('q'):
return
elif key == ord('a') or key == curses.KEY_UP or key == curses.KEY_RIGHT:
if level < 31:
level += 1
prints(win, "Level: %d" % level)
send_level(level)
if (bytes != mExpectLen) {
throw new IOException("Wrong number of bytes read. Expected " + mExpectLen + ", got " + bytes);
}
elif key == ord('y') or key == curses.KEY_DOWN or key == curses.KEY_LEFT:
if level > 1:
level -= 1
prints(win, "Level: %d" % level)
send_level(level)
elif key == -1:
break
if (buffer[0] != mExpectPacket[0]) {
throw new IOException("Byte 0 wrong. Expected " + String.format("%02x", mExpectPacket[0]) + ", got " + String.format("%02x", buffer[0]));
}
got = send_ack(READ, plen=21)
if len(got) == 21:
ic = IConsole(got)
power_meter.update(power = ic.power, cadence = ic.rpm)
speed.update(ic.speed)
win.addstr(0,0, "%s - %s - %s - %s - %s - %s - %s - %s" % (ic.time_str,
ic.speed_str,
ic.rpm_str,
ic.distance_str,
ic.calories_str,
ic.hf_str,
ic.power_str,
ic.lvl_str))
if (buffer[1] != mExpectPacket[1]) {
throw new IOException("Byte 1 wrong. Expected " + String.format("%02x", mExpectPacket[1]) + ", got " + String.format("%02x", buffer[1]));
}
*/
return buffer;
}
/*
send_ack(STOP)
send_ack(PING)
private boolean processIOSend() throws IOException {
switch (mCurrentState) {
case BEGIN:
mCurrentState = mNextState;
break;
case PING:
send(PING);
break;
case A0:
send(INIT_A0, (byte)0xb7, 6);
break;
case A1:
send(STATUS, 6);
break;
case A1_POST_PING:
send(PING);
break;
case A3:
send(INIT_A3);
break;
case A4:
send(INIT_A4);
break;
case START:
send(START);
break;
case STOP:
send(STOP);
break;
case READ:
send(READ);
break;
case SETLEVEL:
send_level(mSetLevel);
break;
}
return true;
}
*/
private boolean processIOAck() throws IOException, TimeoutException {
byte[] got = null;
got = wait_ack();
if (null == got)
return true;
if(mCurrentState == State.READ)
mDataListener.onData(new Data(got));
mCurrentState = mNextState;
switch (mNextState) {
case BEGIN:
mNextState = State.PING;
break;
case PING:
mNextState = State.PING;
break;
case A0:
mNextState = State.A1;
break;
case A1:
mNextState = State.A1_POST_PING;
break;
case A1_POST_PING:
mNextState = State.A3;
break;
case A3:
mNextState = State.A4;
break;
case A4:
mNextState = State.START;
break;
case START:
mNextState = State.READ;
break;
case STOP:
mNextState = State.PING;
break;
case READ:
mNextState = State.READ;
break;
case SETLEVEL:
mNextState = State.READ;
break;
}
return true;
}
boolean processIO() {
synchronized (this) {
try {
if (! mWaitAck) {
return processIOSend();
} else {
return processIOAck();
}
} catch (Exception e) {
mDataListener.onError(e);
return false;
}
}
}
static byte[] hexStringToByteArray(String s) {
int len = s.length();

View file

@ -32,18 +32,21 @@
android:layout_height="wrap_content"
android:orientation="horizontal" >
<EditText
android:id="@+id/edit_text_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1" />
<Button
android:id="@+id/button_send"
android:id="@+id/button_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send" />
android:text="@string/start" />
<Button
android:id="@+id/button_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stop" />
<Button
android:id="@+id/button_disconnect"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disconnect" />
</LinearLayout>
</LinearLayout>

View file

@ -17,7 +17,6 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- BluetoothChat -->
<string name="send">Send</string>
<string name="not_connected">You are not connected to a device</string>
<string name="bt_not_enabled_leaving">Bluetooth was not enabled. Leaving Bluetooth Chat.</string>
<string name="title_connecting">connecting...</string>
@ -37,5 +36,8 @@
<string name="secure_connect">Connect a device - Secure</string>
<string name="insecure_connect">Connect a device - Insecure</string>
<string name="discoverable">Make discoverable</string>
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="disconnect">Disconnect</string>
</resources>