This commit is contained in:
Harald Hoyer 2021-05-26 16:32:45 +02:00
parent 2da8828ef1
commit b1c1a0cb1f
23 changed files with 716 additions and 275 deletions

View file

@ -34,14 +34,16 @@
>
<activity
android:name=".MainActivity"
android:name="org.surfsite.iconsole.MainActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".ChannelService"></service>

View file

@ -44,10 +44,104 @@ import android.widget.NumberPicker;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.ref.WeakReference;
/**
* This fragment controls Bluetooth to communicate with other devices.
*/
public class BluetoothChatFragment extends Fragment {
static class MyInnerHandler extends Handler{
WeakReference<BluetoothChatFragment> mFrag;
MyInnerHandler(BluetoothChatFragment aFragment) {
mFrag = new WeakReference<BluetoothChatFragment>(aFragment);
}
@Override
public void handleMessage(Message msg) {
BluetoothChatFragment theFrag = mFrag.get();
if (theFrag == null) {
return;
}
FragmentActivity activity = theFrag.getActivity();
switch (msg.what) {
case Constants.MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case BluetoothChatService.STATE_CONNECTED:
theFrag.setStatus(theFrag.getString(R.string.title_connected_to, theFrag.mConnectedDeviceName));
//mConversationArrayAdapter.clear();
theFrag.mStartButton.setEnabled(true);
theFrag.mStopButton.setEnabled(true);
theFrag.mDisconnectButton.setEnabled(true);
theFrag.mLevel.setEnabled(true);
theFrag.mLevel.setValue(1);
break;
case BluetoothChatService.STATE_CONNECTING:
theFrag.setStatus(R.string.title_connecting);
theFrag.mStartButton.setEnabled(false);
theFrag.mStopButton.setEnabled(false);
theFrag.mDisconnectButton.setEnabled(false);
theFrag.mLevel.setEnabled(false);
break;
case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE:
theFrag.setStatus(R.string.title_not_connected);
theFrag.mStartButton.setEnabled(false);
theFrag.mStopButton.setEnabled(false);
theFrag.mDisconnectButton.setEnabled(false);
theFrag.mLevel.setEnabled(false);
break;
}
break;
case Constants.MESSAGE_DATA:
if (!(msg.obj instanceof IConsole.Data))
return;
IConsole.Data data = (IConsole.Data) msg.obj;
theFrag.mChannelService.setSpeed(data.mSpeed10 / 10.0);
theFrag.mChannelService.setPower(data.mPower10 * 9 / 100);
theFrag.mChannelService.setCadence(data.mRPM);
theFrag.mSpeedText.setText(String.format("% 3.1f", data.mSpeed10 / 10.0));
theFrag.mPowerText.setText(String.format("% 3.1f", data.mPower10 / 10.0));
theFrag.mRPMText.setText(String.format("%d", data.mRPM));
theFrag.mDistanceText.setText(String.format("% 3.1f", data.mDistance10 / 10.0));
theFrag.mCaloriesText.setText(String.format("% 3d", data.mCalories));
theFrag.mHFText.setText(String.format("%d", data.mHF));
theFrag.mTimeText.setText(String.format("%s", data.getTimeStr()));
//mLevel.setValue(data.mLevel);
break;
case Constants.MESSAGE_WRITE:
//byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
//String writeMessage = new String(writeBuf);
//mConversationArrayAdapter.add("Me: " + writeMessage);
break;
case Constants.MESSAGE_READ:
//byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
//String readMessage = new String(readBuf, 0, msg.arg1);
//mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);
break;
case Constants.MESSAGE_DEVICE_NAME:
// save the connected device's name
theFrag.mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
if (null != activity) {
Toast.makeText(activity, "Connected to "
+ theFrag.mConnectedDeviceName, Toast.LENGTH_SHORT).show();
}
break;
case Constants.MESSAGE_TOAST:
if (null != activity) {
Toast.makeText(activity, msg.getData().getString(Constants.TOAST),
Toast.LENGTH_SHORT).show();
}
break;
}
}
}
private final Handler mHandler = new MyInnerHandler(this);
private static final String TAG = "BluetoothChatFragment";
@ -92,86 +186,6 @@ public class BluetoothChatFragment extends Fragment {
/**
* The Handler that gets information back from the BluetoothChatService
*/
private final Handler mHandler = new Handler() {
@SuppressLint("DefaultLocale")
@Override
public void handleMessage(Message msg) {
FragmentActivity activity = getActivity();
switch (msg.what) {
case Constants.MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case BluetoothChatService.STATE_CONNECTED:
setStatus(getString(R.string.title_connected_to, mConnectedDeviceName));
//mConversationArrayAdapter.clear();
mStartButton.setEnabled(true);
mStopButton.setEnabled(true);
mDisconnectButton.setEnabled(true);
mLevel.setEnabled(true);
mLevel.setValue(1);
break;
case BluetoothChatService.STATE_CONNECTING:
setStatus(R.string.title_connecting);
mStartButton.setEnabled(false);
mStopButton.setEnabled(false);
mDisconnectButton.setEnabled(false);
mLevel.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);
mLevel.setEnabled(false);
break;
}
break;
case Constants.MESSAGE_DATA:
if (!(msg.obj instanceof IConsole.Data))
return;
IConsole.Data data = (IConsole.Data) msg.obj;
mChannelService.setSpeed(data.mSpeed10 / 10.0);
mChannelService.setPower(data.mPower10 / 10);
mChannelService.setCadence(data.mRPM);
mSpeedText.setText(String.format("% 3.1f", data.mSpeed10 / 10.0));
mPowerText.setText(String.format("% 3.1f", data.mPower10 / 10.0));
mRPMText.setText(String.format("%d", data.mRPM));
mDistanceText.setText(String.format("% 3.1f", data.mDistance10 / 10.0));
mCaloriesText.setText(String.format("% 3d", data.mCalories));
mHFText.setText(String.format("%d", data.mHF));
mTimeText.setText(String.format("%s", data.getTimeStr()));
//mLevel.setValue(data.mLevel);
break;
case Constants.MESSAGE_WRITE:
//byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
//String writeMessage = new String(writeBuf);
//mConversationArrayAdapter.add("Me: " + writeMessage);
break;
case Constants.MESSAGE_READ:
//byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
//String readMessage = new String(readBuf, 0, msg.arg1);
//mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage);
break;
case Constants.MESSAGE_DEVICE_NAME:
// save the connected device's name
mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
if (null != activity) {
Toast.makeText(activity, "Connected to "
+ mConnectedDeviceName, Toast.LENGTH_SHORT).show();
}
break;
case Constants.MESSAGE_TOAST:
if (null != activity) {
Toast.makeText(activity, msg.getData().getString(Constants.TOAST),
Toast.LENGTH_SHORT).show();
}
break;
}
}
};
private boolean mChannelServiceBound = false;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {

View file

@ -141,6 +141,7 @@ public class BluetoothChatService extends Service {
mNewState = mState;
// Give the new state to the Handler so the UI Activity can update
if (mHandler != null)
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget();
}

View file

@ -127,7 +127,9 @@ public class ChannelService extends Service {
}
private void closeAllChannels() {
if (powerChannelController != null)
powerChannelController.close();
if (speedChannelController != null)
speedChannelController.close();
powerChannelController = null;
speedChannelController = null;

View file

@ -0,0 +1,316 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.surfsite.iconsole;
import android.os.RemoteException;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
import com.dsi.ant.channel.Capabilities;
import com.dsi.ant.channel.IAntChannelEventHandler;
import com.dsi.ant.message.ChannelId;
import com.dsi.ant.message.ChannelType;
import com.dsi.ant.message.EventCode;
import com.dsi.ant.message.fromant.AcknowledgedDataMessage;
import com.dsi.ant.message.fromant.ChannelEventMessage;
import com.dsi.ant.message.fromant.MessageFromAntType;
import com.dsi.ant.message.ipc.AntMessageParcel;
import java.util.Random;
public class FEChannelController {
public static final int FE_SENSOR_ID = 0x9e3d4b67;
// The device type and transmission type to be part of the channel ID message
private static final int CHANNEL_FE_DEVICE_TYPE = 0x11;
private static final int CHANNEL_FE_TRANSMISSION_TYPE = 5;
// The period and frequency values the channel will be configured to
private static final int CHANNEL_POWER_PERIOD = 8182; // 1 Hz
private static final int CHANNEL_POWER_FREQUENCY = 57;
private static final String TAG = FEChannelController.class.getSimpleName();
private static Random randGen = new Random();
int power = 0;
int cadence = 0;
private AntChannel mAntChannel;
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
private boolean mIsOpen;
public FEChannelController(AntChannel antChannel) {
mAntChannel = antChannel;
openChannel();
}
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
Log.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
// must have the same channel ID, or wildcard (0) is used.
ChannelId channelId = new ChannelId(FE_SENSOR_ID & 0xFFFF,
CHANNEL_FE_DEVICE_TYPE, CHANNEL_FE_TRANSMISSION_TYPE);
try {
// Setting the channel event handler so that we can receive messages from ANT
mAntChannel.setChannelEventHandler(mChannelEventCallback);
// Performs channel assignment by assigning the type to the channel. Additional
// features (such as, background scanning and frequency agility) can be enabled
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
/*
* Configures the channel ID, messaging period and rf frequency after assigning,
* then opening the channel.
*
* For any additional ANT features such as proximity search or background scanning, refer to
* the ANT Protocol Doc found at:
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
*/
mAntChannel.setChannelId(channelId);
mAntChannel.setPeriod(CHANNEL_POWER_PERIOD);
mAntChannel.setRfFrequency(CHANNEL_POWER_FREQUENCY);
mAntChannel.open();
mIsOpen = true;
Log.d(TAG, "Opened channel with device number: " + FE_SENSOR_ID);
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
// This will release, and therefore unassign if required
channelError("Open failed", e);
}
}
} else {
Log.w(TAG, "No channel available");
}
return mIsOpen;
}
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
Log.e(TAG, logString);
}
void channelError(String error, AntCommandFailedException e) {
StringBuilder logString;
if (e.getResponseMessage() != null) {
String initiatingMessageId = "0x" + Integer.toHexString(
e.getResponseMessage().getInitiatingMessageId());
String rawResponseCode = "0x" + Integer.toHexString(
e.getResponseMessage().getRawResponseCode());
logString = new StringBuilder(error)
.append(". Command ")
.append(initiatingMessageId)
.append(" failed with code ")
.append(rawResponseCode);
} else {
String attemptedMessageId = "0x" + Integer.toHexString(
e.getAttemptedMessageType().getMessageId());
String failureReason = e.getFailureReason().toString();
logString = new StringBuilder(error)
.append(". Command ")
.append(attemptedMessageId)
.append(" failed with reason ")
.append(failureReason);
}
Log.e(TAG, logString.toString());
mAntChannel.release();
}
public void close() {
// TODO kill all our resources
if (null != mAntChannel) {
mIsOpen = false;
// Releasing the channel to make it available for others.
// After releasing, the AntChannel instance cannot be reused.
mAntChannel.release();
mAntChannel = null;
}
Log.e(TAG, "Channel Closed");
}
/**
* Implements the Channel Event Handler Interface so that messages can be
* received and channel death events can be handled.
*/
public class ChannelEventCallback implements IAntChannelEventHandler {
int cnt = 0;
int eventCount = 0;
int cumulativePower = 0;
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
Log.e(TAG, "Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
Log.d(TAG, "Rx: " + antParcel);
Log.d(TAG, "Message Type: " + messageType);
byte[] payload = new byte[8];
byte fe_state = 0; // RESERVED
fe_state = 1; // ASLEEP
fe_state = 2; // READY
fe_state = 3; // IN_USE
fe_state = 4; // FINISHED / PAUSE
// Switching on message type to handle different types of messages
switch (messageType) {
// If data message, construct from parcel and update channel data
case BROADCAST_DATA:
// Rx Data
//updateData(new BroadcastDataMessage(antParcel).getPayload());
break;
case ACKNOWLEDGED_DATA:
// Rx Data
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
payload = new AcknowledgedDataMessage(antParcel).getPayload();
Log.d(TAG, "AcknowledgedDataMessage: " + payload);
if ((payload[0] == 0) && (payload[1] == 1) && (payload[2] == (byte)0xAA)) {
payload[0] = (byte) 0x01;
payload[1] = (byte) 0xAC;
payload[2] = (byte) 0xFF;
payload[3] = (byte) 0xFF;
payload[4] = (byte) 0xFF;
payload[5] = (byte) 0xFF;
payload[6] = (byte) 0x00;
payload[7] = (byte) 0x00;
try {
// Setting the data to be broadcast on the next channel period
mAntChannel.setBroadcastData(payload);
} catch (RemoteException e) {
channelError(e);
}
}
break;
case CHANNEL_EVENT:
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
Log.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
case TX:
cnt += 1;
if (cnt % 66 == 64) {
payload[0] = (byte) 0x50;
payload[1] = (byte) 0xFF;
payload[2] = (byte) 0xFF;
payload[3] = (byte) 0x01;
payload[4] = (byte) 0xFF;
payload[5] = (byte) 0x00;
payload[6] = (byte) 0x01;
payload[7] = (byte) 0x00;
} else if (cnt % 66 == 65) {
payload[0] = (byte) 0x51;
payload[1] = (byte) 0xFF;
payload[2] = (byte) 0xFF;
payload[3] = (byte) 0x01;
payload[4] = (byte) ((FE_SENSOR_ID) & 0xFF);
payload[5] = (byte) ((FE_SENSOR_ID >> 8) & 0xFF);
payload[6] = (byte) ((FE_SENSOR_ID >> 16) & 0xFF);
payload[7] = (byte) ((FE_SENSOR_ID >> 24) & 0xFF);
} else if (cnt % 2 == 0) {
// PAGE 16
payload[0] = (byte) 0x10;
payload[1] = (byte) 25; // Equipment Type: 25 == Trainer / Stationary Bike
payload[2] = (byte) elapsedTime_in_0_25_seconds;
payload[3] = (byte) 0; // Distance traveled
payload[4] = (byte) 0xFF; // Speed LSB
payload[5] = (byte) 0xFF; // Speed MSB
payload[6] = (byte) 0xFF; // heart rate
payload[7] = (byte) (fe_state >> 4); // Capabilities 0:3 ; FE State Bit Field 4:7
} else {
// PAGE 25
eventCount = (eventCount + 1) & 0xFF;
cumulativePower = (cumulativePower + power) & 0xFFFF;
byte flags_bit_field;
flags_bit_field = 1; // too slow to achieve target power
flags_bit_field = 2; // too fast to achieve target power
flags_bit_field = 0; // operating at target power
payload[0] = (byte) 0x19;
payload[1] = (byte) eventCount;
payload[2] = (byte) cadence;
payload[3] = (byte) ((cumulativePower) & 0xFF);
payload[4] = (byte) ((cumulativePower >> 8) & 0xFF);
payload[5] = (byte) ((power) & 0xFF);
payload[6] = (byte) (((power >> 8) & 0xFF) | (trainerStatusBits >> 4));
payload[7] = (byte) ((flags_bit_field & 0xF) | ((fe_state >> 4) & 0xF0)); // Capabilities 0:3 ; FE State Bit Field 4:7
}
}
if (mIsOpen) {
try {
// Setting the data to be broadcast on the next channel period
mAntChannel.setBroadcastData(payload);
} catch (RemoteException e) {
channelError(e);
}
}
break;
case CHANNEL_COLLISION:
cnt += 1;
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
Log.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:
case RX_FAIL_GO_TO_SEARCH:
case TRANSFER_RX_FAILED:
case TRANSFER_TX_COMPLETED:
case TRANSFER_TX_FAILED:
case TRANSFER_TX_START:
case UNKNOWN:
// TODO More complex communication will need to handle these events
break;
}
break;
case ANT_VERSION:
case BURST_TRANSFER_DATA:
case CAPABILITIES:
case CHANNEL_ID:
case CHANNEL_RESPONSE:
case CHANNEL_STATUS:
case SERIAL_NUMBER:
case OTHER:
// TODO More complex communication will need to handle these message types
break;
}
}
}
}

View file

@ -101,7 +101,7 @@
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp"
android:text="Power"
android:text="Power corr"
app:layout_constraintBottom_toTopOf="@+id/Power"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/guidelineleft"

View file

@ -46,7 +46,7 @@
<string name="power_n0_0_w">0.0</string>
<string name="level">Level</string>
<string name="local_service_started">OpeniConsole Bluetooth active</string>
<string name="local_service_label">OpeniConsole</string>
<string name="local_service_label">OpeniConsole - TEST</string>
<string name="secure_connect">Connect to iConsole</string>
</resources>