I have been using HCE and have been facing an IOException on
isoDep.connect();
on a specific Android reader device cr100 simcent.
HCE works perfectly fine when I enable the reader mode in NFC with the below flags.
public static int READER_FLAGS =
NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
But with that I am not able to read NDEF tags. Though the same code works perfectly fine on Nexus 7 (2012) Tablet.
The complete code is attached
CardReaderFragment
public class CardReaderFragment extends Fragment implements LoyaltyCardReader.AccountCallback {
public static final String TAG = "CardReaderFragment";
// Recommend NfcAdapter flags for reading from other Android devices. Indicates that this
// activity is interested in NFC-A devices (including other Android devices), and that the
// system should not check for the presence of NDEF-formatted data (e.g. Android Beam).
public static int READER_FLAGS =
NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK;
public LoyaltyCardReader mLoyaltyCardReader;
private TextView mAccountField;
/** Called when sample is created. Displays generic UI with welcome text. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.main_fragment, container, false);
if (v != null) {
mAccountField = (TextView) v.findViewById(R.id.card_account_field);
mAccountField.setText("Waiting...");
mLoyaltyCardReader = new LoyaltyCardReader(this);
// Disable Android Beam and register our card reader callback
enableReaderMode();
}
return v;
}
#Override
public void onPause() {
super.onPause();
disableReaderMode();
}
#Override
public void onResume() {
super.onResume();
enableReaderMode();
}
private void enableReaderMode() {
Log.i(TAG, "Enabling reader mode");
Activity activity = getActivity();
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity);
if (nfc != null) {
nfc.enableReaderMode(activity, mLoyaltyCardReader, READER_FLAGS, null);
}
}
private void disableReaderMode() {
Log.i(TAG, "Disabling reader mode");
Activity activity = getActivity();
NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity);
if (nfc != null) {
nfc.disableReaderMode(activity);
}
}
#Override
public void onAccountReceived(final String account) {
// This callback is run on a background thread, but updates to UI elements must be performed
// on the UI thread.
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
mAccountField.setText(account);
}
});
}
}
LoyaltyCardReader
public class LoyaltyCardReader implements NfcAdapter.ReaderCallback {
private static final String TAG = "LoyaltyCardReader";
// AID for our loyalty card service.
private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222";
// ISO-DEP command HEADER for selecting an AID.
// Format: [Class | Instruction | Parameter 1 | Parameter 2]
private static final String SELECT_APDU_HEADER = "00A40400";
// "OK" status word sent in response to SELECT AID command (0x9000)
private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00};
// Weak reference to prevent retain loop. mAccountCallback is responsible for exiting
// foreground mode before it becomes invalid (e.g. during onPause() or onStop()).
private WeakReference<AccountCallback> mAccountCallback;
public interface AccountCallback {
public void onAccountReceived(String account);
}
public LoyaltyCardReader(AccountCallback accountCallback) {
mAccountCallback = new WeakReference<AccountCallback>(accountCallback);
}
/**
* Callback when a new tag is discovered by the system.
* <p>
* <p>Communication with the card should take place here.
*
* #param tag Discovered tag
*/
#Override
public void onTagDiscovered(Tag tag) {
Log.i(TAG, "New tag discovered");
// Android's Host-based Card Emulation (HCE) feature implements the ISO-DEP (ISO 14443-4)
// protocol.
//
// In order to communicate with a device using HCE, the discovered tag should be processed
// using the IsoDep class.
IsoDep isoDep = IsoDep.get(tag);
if (isoDep != null) {
try {
// Connect to the remote NFC device
isoDep.connect();
// Build SELECT AID command for our loyalty card service.
// This command tells the remote device which service we wish to communicate with.
Log.i(TAG, "Requesting remote AID: " + SAMPLE_LOYALTY_CARD_AID);
byte[] command = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID);
// Send command to remote device
Log.i(TAG, "Sending: " + ByteArrayToHexString(command));
byte[] result = isoDep.transceive(command);
// If AID is successfully selected, 0x9000 is returned as the status word (last 2
// bytes of the result) by convention. Everything before the status word is
// optional payload, which is used here to hold the account number.
int resultLength = result.length;
byte[] statusWord = {result[resultLength - 2], result[resultLength - 1]};
byte[] payload = Arrays.copyOf(result, resultLength - 2);
if (Arrays.equals(SELECT_OK_SW, statusWord)) {
// The remote NFC device will immediately respond with its stored account number
String accountNumber = new String(payload, "UTF-8");
Log.i(TAG, "Received: " + accountNumber);
// Inform CardReaderFragment of received account number
mAccountCallback.get().onAccountReceived(accountNumber);
}
} catch (IOException e) {
Log.e(TAG, "Error communicating with card: " + e.toString());
}
} else {
Ndef ndef = Ndef.get(tag);
if (ndef == null) {
// NDEF is not supported by this Tag.
Log.d("NFCCardTagNDEF", "even this is null");
// return;
}
NdefMessage ndefMessage = ndef.getCachedNdefMessage();
if (ndefMessage == null) {
Log.d("NFCCardTagNDEF", "ndef message is null");
// return;
}
NdefRecord[] records = ndefMessage.getRecords();
String text = ndefRecordToString(records[0]);
Log.d("NFCCardTagNFC", "old" + text);
mAccountCallback.get().onAccountReceived(text);
}
}
public String ndefRecordToString(NdefRecord record) {
byte[] payload = record.getPayload();
return new String(payload);
}
/**
* Build APDU for SELECT AID command. This command indicates which service a reader is
* interested in communicating with. See ISO 7816-4.
*
* #param aid Application ID (AID) to select
* #return APDU for SELECT AID command
*/
public static byte[] BuildSelectApdu(String aid) {
// Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA]
return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid);
}
/**
* Utility class to convert a byte array to a hexadecimal string.
*
* #param bytes Bytes to convert
* #return String, containing hexadecimal representation.
*/
public static String ByteArrayToHexString(byte[] bytes) {
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
/**
* Utility class to convert a hexadecimal string to a byte string.
* <p>
* <p>Behavior with input strings containing non-hexadecimal characters is undefined.
*
* #param s String containing hexadecimal characters to convert
* #return Byte array generated from input
*/
public static byte[] HexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i + 1), 16));
}
return data;
}
}
Any help would be appreciated
Typically, there is not much you could do about this error. An IOException (or the more specific TagLostException) upon IsoDep.connect() indicates that the reading phone could not initiate communication with the HCE device. This is often due to connectivity issues caused by bad coupling (e.g. badly matching antenna dimensions, poor antenna design, antennas partially covered by the device battery without proper antenna design, etc.) or long power-up times (typically only with passive cards and not with HCE). So usually this error is not caused by logical problems with the communication protocol (which you could possibly fix through software) but by problem with the physical characteristics of the devices (which usually require hardware modifications).
Unfortunately, there's not much you can do about this. Possible work-arounds could be:
Try to better place the two phones together (so that the reader device antenna and the HCE device antenna) align to each other and that metal parts of the device case or the device battery does not cover the other device's antenna. In case of significanly different antenna sizes, try to align the other borders of the antennas to each other so that the smaller one is inside the larger one.
On some(!) devices it may help to increase the transceive timeout before calling connect():
isoDep.setTimeout(10000);
However, it seems that this timeout does not have any effect before connect was called on most devices.
There exist antenna "boosters" for NFC which either detune the HF resonance frequency to get better matching, that mitigate coupling issues do to adapting antenna shape around batteries, or that replace the original antenna altogether. I don't have much experience with this and can't quote any source for such boosters.
EDIT
After re-reading your question, it seems that communication between the cr100 and the HCE device works if you set the flag NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK on the reader device. If that's the case, the IOException might indeed be caused by a logical error. In that case, it could be that the cr100 has a broken NFC discovery implementation that breaks if an ISO-DEP card (e.g. HCE device) does not implement the NDEF tag specification. While I would consider this highly unlikely, you could easily test this by implementing the Type 4 Tag application in your HCE application. See my answer here and the source code here for an example on how to do this.
Related
I want to send some data (int) to android app, I can do it by writing text in Serial, but I need to send int from code. All I get is "?". I'm not sure what type of data SerialBT.write should I use.
Here is my code:
void setup() {
Serial.begin(115200);
SerialBT.begin("ESP32test"); //Bluetooth device name
Serial.println("The device started, now you can pair it with bluetooth!");
}
void loop() {
RawValue = analogRead(15);
Voltage = (RawValue / 4096.0) * 3300; // Gets you mV
Amps = ((Voltage - ACSoffset) / mVperAmp);
OdczytNap= analogRead(2);
napiecie=OdczytNap*(3300/4096.0);
napiecie=map(OdczytNap,0,1023,0,5);
SerialBT.write(napiecie);
SerialBT.write(Amps);
if (SerialBT.available()) {
Serial.write(SerialBT.read());
}
delay(20);
}
I'm trying to send Arduino ADC data to android tablet using USB serial Communication. I'm using Serial.println() at arduino side. My Issue is that I'm not able to Decode the data received at the android end.
For Eg. Suppose I send Serial.println(768) from arduino, I check my android receive buffer and it Shows (55,54,56,13,10).
How can i decode this data back to 768 value?
Looking into an ASCII table you'll find that
55,54,56,13,10
represents
"768\n\r"
Most programming languages provide means for conversion between byte values and characters/strings with their string libraries. So you don't have to implement the decoding yourself.
Refer to https://howtodoinjava.com/array/convert-byte-array-string-vice-versa/
or UTF-8 byte[] to String
or anything else you find online for "byte to string Android"
String rawdata = " " ;
String finaldata = " ";
UsbSerialInterface.UsbReadCallback mCallback = new UsbSerialInterface.UsbReadCallback() { //Defining a Callback which triggers whenever data is read.
#Override
public void onReceivedData(byte[] arg0) {
byte[] buffer = arg0;
for (i =0;i <=(buffer.length-1);i++) {
if(buffer[i]!= 13) {
if(buffer[i]== 10){
finaldata = rawdata;
rawdata = "";
}else {
chdata = (char) buffer[i];
rawdata += chdata;
}
}
}
data = Integer.parseInt(finaldata);
}
I'm developing a privileged system app to scan the network. After executing the API, the results does not contain a valid cell identity information. All values return either as 0, null or max int.
Granted relevant system privileged permissions.
An extract of the code:
public class ScannerActivity extends Activity implements View.OnClickListener {
private final int PHONE_STATE_REQUEST = 1;
private Button scanButton;
private TextView resultsTextView;
private class RadioCallback extends TelephonyScanManager.NetworkScanCallback {
private List<CellInfo> mCellInfoResults;
private int mScanError;
#Override
public void onResults(List<CellInfo> cellInfoResults) {
mCellInfoResults = cellInfoResults;
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
for (CellInfo cellInfo:mCellInfoResults) {
resultsTextView.append(" " + cellInfo.toString() + " ");
}
}
});
}
#Override
public void onError(int error) {
mScanError = error;
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
resultsTextView.append(" Error: " + mScanError);
}
});
}
#Override
public void onComplete() {
ScannerActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
resultsTextView.append(" Scan Completed! ");
}
});
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scanner);
scanButton = (Button) findViewById(R.id.scan_button);
scanButton.setOnClickListener(this);
resultsTextView = (TextView) findViewById(R.id.results_text_view);
}
public void onClick(View view) {
NetworkScanRequest networkScanRequest;
RadioAccessSpecifier radioAccessSpecifiers[];
TelephonyManager telephonyManager = (TelephonyManager) getApplicationContext()
.getSystemService(Context.TELEPHONY_SERVICE);
radioAccessSpecifiers = new RadioAccessSpecifier[1];
radioAccessSpecifiers[0] = new RadioAccessSpecifier(
AccessNetworkConstants.AccessNetworkType.UTRAN,
null,
null);
networkScanRequest = new NetworkScanRequest(
NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
radioAccessSpecifiers,
30,
300,
true,
3,
null);
telephonyManager.requestNetworkScan(networkScanRequest, AsyncTask.SERIAL_EXECUTOR,new RadioCallback());
}
Any idea why this happens?
Tried on Pixel 2.
You can check if radio layer has provided a valid cell identity info in response to your requestNetworkScan or not. Get radio log by cmd "adb logcat -v time -b radio" and check any occurrence of UNSOL_NETWORK_SCAN_RESULT API in this log. Below is the description of this unsolicited response.
/**
* RIL_UNSOL_NETWORK_SCAN_RESULT
*
* Returns incremental result for the network scan which is started by
* RIL_REQUEST_START_NETWORK_SCAN, sent to report results, status, or errors.
*
* "data" is NULL
* "response" is a const RIL_NetworkScanResult *
*/
#define RIL_UNSOL_NETWORK_SCAN_RESULT 1049
Response struct RIL_NetworkScanResult has below fields:
typedef struct {
RIL_ScanStatus status; // The status of the scan
uint32_t network_infos_length; // Total length of RIL_CellInfo
RIL_CellInfo_v12* network_infos; // List of network information
RIL_Errno error;
} RIL_NetworkScanResult;
If this UNSOL_NETWORK_SCAN_RESULT response is either returning NULL struct or no UNSOL_NETWORK_SCAN_RESULT response at all then probably radio HAL is not supporting this API.
The requestNetworkScan has similar functionality to the getAvailableNetworks. These functions are doing high-level network scans to find nearby carriers. The modem is only looking for a set of unique PLMNs (i.e. a carrier identifier) and doesn't dwell on the cells long enough to find more detailed information such as the cell identity.
The RIL should be able to return some basic information about the cell such as the frequency channel (ARFCN for GSM, UARFCN for UMTS, and EARFCN for LTE) and a physical cell identity (BSIC for GSM, PSC for UMTS, PCI for LTE) but it doesn't seem to return any valid information for those values currently.
My app uses the foreground dispatch system to allow a user to tap their NFC tag in order to perform a read-then-write operation on the tag.
It works nicely if the user taps their tag properly (i.e., they tap it in the correct place on the phone and leave it connected for long enough), but if they physically remove the tag too early, then ndef.writeNdefMessage(...) throws an IOException.
That means the write operation fails, which is fair enough. But the real problem is that the same failed operation also deletes the entire ndef formatting/message from the tag!
My code is built around the snippets from the Advanced NFC | Android Developers page (as unfortunately the link to the ForegroundDispatch sample appears to be broken and there is no such sample project to import into Android Studio).
Step 1. Here is the logcat/stacktrace output when the user first taps their NFC tag, but moves it away too soon:
03-28 20:15:18.589 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error
java.io.IOException
at android.nfc.tech.Ndef.writeNdefMessage(Ndef.java:320)
at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:170)
at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224)
at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946)
at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959)
at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968)
at android.app.ActivityThread.access$1700(ActivityThread.java:181)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6145)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
03-28 20:15:18.599 1481-17792/? E/SecNfcJni: nfaConnectionCallback: NFA_SELECT_RESULT_EVT error: status = 3
03-28 20:15:18.599 1481-1502/? E/SecNfcJni: reSelect: tag is not active
Step 2. Next, the same user taps the same tag again but it appears to no longer contain an ndef message (which I have confirmed by altering the code and checking that ndef.getCachedNdefMessage() returns null):
03-28 20:15:27.499 21278-21278/com.example.exampleapp E/NfcTestActivity: Tag error
java.lang.Exception: Tag was not ndef formatted: android.nfc.action.TECH_DISCOVERED
at com.example.exampleapp.NfcTestActivity.onNewIntent(NfcTestActivity.java:124)
at android.app.Instrumentation.callActivityOnNewIntent(Instrumentation.java:1224)
at android.app.ActivityThread.deliverNewIntents(ActivityThread.java:2946)
at android.app.ActivityThread.performNewIntents(ActivityThread.java:2959)
at android.app.ActivityThread.handleNewIntent(ActivityThread.java:2968)
at android.app.ActivityThread.access$1700(ActivityThread.java:181)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1554)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6145)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194)
I am getting this issue with both devices I have tested with so far - a Samsung Galaxy Core Prime (a lower end phone) running Android 5.1.1 and a Samsung Galaxy A5 (a mid range phone) running Android 5.0.2.
The NFC tags used by my app contain important information (i.e., inadvertently deleting the tag data is not an option!), so my questions are...
Why is my code (see below) erasing the tag data like this?
How can I fix the underlying problem, or is there an acceptable workaround?
Would it be worth me trying to use NfcA or IsoDep rather than Ndef?
Having done a lot of searching, I'm very surprised that this problem has not been discussed elsewhere, so if the problem isn't to do with my code, then could it be to do with the NFC tags I am using?...
The tags I'm using are NXP MIFARE Ultralight (Ultralight C) - NTAG203 (Tag type: ISO 14443-3A). Some of these I bought from ebay, and some I bought from Rapid NFC (a reputable company), yet I seem to have this problem with all of them.
Here is my complete code for the activity:
package com.example.exampleapp;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentFilter;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.Ndef;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
public class NfcTestActivity extends AppCompatActivity {
private static String LOG_TAG = NfcTestActivity.class.getSimpleName();
private static int SUCCESS_COUNT = 0;
private static int FAILURE_COUNT = 0;
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent;
private IntentFilter[] intentFiltersArray;
private String[][] techListsArray;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nfc_test);
getSupportActionBar().setDisplayShowHomeEnabled(true);
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter == null) {
makeToast("NFC not available!", Toast.LENGTH_LONG);
finish();
}
else {
//makeToast("NFC available");
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
} catch (IntentFilter.MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[]{
ndef
};
techListsArray = new String[][]{
new String[]{
Ndef.class.getName()
}
};
}
}
#Override
public void onPause() {
super.onPause();
if (nfcAdapter != null) {
nfcAdapter.disableForegroundDispatch(this);
}
}
#Override
public void onResume() {
super.onResume();
if (nfcAdapter != null) {
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}
}
public void onNewIntent(Intent intent) {
Ndef ndef = null;
try {
String action = intent.getAction();
//makeToast("action: " + action);
if (!NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
throw new Exception("Tag was not ndef formatted: " + action); // line #124
}
else {
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
ndef = Ndef.get(tag);
//makeToast("ndef: " + ndef);
if (ndef == null) {
throw new Exception("ndef == null!");
}
else {
// Connect
ndef.connect();
// Get cached message
NdefMessage ndefMessageOld = ndef.getCachedNdefMessage();
if (ndefMessageOld == null) {
throw new Exception("No ndef message on tag!");
}
else {
// Get old records
NdefRecord[] ndefRecordsOld = ndefMessageOld.getRecords();
int numRecords = (ndefRecordsOld == null) ? 0 : ndefRecordsOld.length;
// Create/copy 'new' records
NdefRecord[] ndefRecordsNew = new NdefRecord[numRecords];
for (int i = 0; i < numRecords; i++) {
ndefRecordsNew[i] = ndefRecordsOld[i];
}
// Create new message
NdefMessage ndefMessageNew = new NdefMessage(ndefRecordsNew);
// Write new message
ndef.writeNdefMessage(ndefMessageNew); // line #170
SUCCESS_COUNT++;
// Report success
String msg = "Read & wrote " + numRecords + " records.";
makeToast(msg);
Log.d(LOG_TAG, msg);
}
}
}
}
catch(Exception e) {
FAILURE_COUNT++;
Log.e(LOG_TAG, "Tag error", e);
makeToast("Tag error: " + e, Toast.LENGTH_LONG);
}
finally {
try {
if (ndef != null) {
ndef.close();
}
}
catch(Exception e) {
Log.e(LOG_TAG, "Error closing ndef", e);
makeToast("Error closing ndef: " + e, Toast.LENGTH_LONG);
}
makeToast("Successes: " + SUCCESS_COUNT + ". Failures: " + FAILURE_COUNT);
}
}
private void makeToast(final String msg) {
makeToast(msg, Toast.LENGTH_SHORT);
}
private void makeToast(final String msg, final int duration) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
Toast.makeText(NfcTestActivity.this, msg, duration).show();
}
});
}
}
I really wonder what else you would expect to happen when you remove a storage device in the middle of overwriting its data.
Why is my code (see below) erasing the tag data like this?
You code is not really "erasing" data. It simply starts overwriting the data from the beginning of the tag memory leaving the tag in an undefined state when you interrupt writing.
An NFC tag only supports storing one NDEF message at a time. Consequently, when you start to write an new NDEF message, the old NDEF message needs to be overwritten. Thus,
ndef.writeNdefMessage(ndefMessageNew);
will overwrite the existing NDEF message starting at its first block. For NTAG203, MIFARE Ultralight and MIFARE Ultralight C (that's three different tag types by the way), this first block will be around block 4. writeNdefMessage will then write the new message block for block replacing old data with new data.
If the write procedure is interrupted (e.g. by pulling the tag from the reader field), then only parts of the new message are written (and parts of the old message may remain on the tag). Since neither the old nor the new message are complete, Android (just as any other NDEF reader) cannot read a valid NDEF message from the tag and, therefore, does not detect any NDEF message. The tag is still detected by your app since you also registered for the TECH_DISCOVERED intent (which does not require the tag to contain a vaild NDEF message).
How can I fix the underlying problem, or is there an acceptable workaround?
If your NDEF message is that long that your users are actually able to pull the tag while writing there is not much you can do against the pulling itself (except for instructing the users not to do so). NFC tags also do not have any form of pulling protection out-of-the-box. I.e. there are currently no tags that will reliably store the old NDEF message until the new NDEF message was written completely.
What you could possibly do is to store the old (or the new) NDEF message (possibly mapped to the tag ID) within your app and let the users restart the write procedure once it failed. Still, that would require user cooperation.
Would it be worth me trying to use NfcA or IsoDep rather than Ndef?
That might be another option: Don't use NDEF for the critical data but use an application-specific memory layout instead (or in addition to NDEF). NTAG/MIFARE Ultralight have a command set on top of ISO 14443-3A (NFC-A) and do not support ISO-DEP (ISO 14443-4). Thus, you could use NfcA (or MifareUltralight) to directly read from/write to the tags using low-level commands. You could structure the tag memory in two sections that you use to store the old and the new data:
Block x: Flag indicating which section (1 or 2) contains the valid data
Block x+1: First block of section 1
Block x+2: Second block of section 1
[...]
Block x+m: Last block of section 1
Block x+m+1: First block of section 2
Block x+m+2: Second block of section 2
[...]
Block x+2*m: Last block of section 2
Where x is the first block of your custom memory structure (you could even start that area after some fixed NDEF message) and m is the length of each section in blocks (1 block on NTAG/MF Ultralight has 4 bytes).
You would then use something like this to read and update your tag:
Read from block x to find out which section contains the vaild (newest) data -> section s.
Read the data from section s and use it as current data.
Write the new data to the other section (if s = 1: section 0; if s = 0: section 1).
If the data was written successfully (and completely), update block x with the new section number.
Low-level read and write commands look like this:
READ:
byte[] result = nfcA.transceive(new byte[] {
(byte)0x30, // READ
(byte)(blockNumber & 0x0ff)
});
WRITE:
byte[] result = nfcA.transceive(new byte[] {
(byte)0xA2, // WRITE
(byte)(blockNumber & 0x0ff),
byte0, byte1, byte2, byte3
});
I am doing some works with Texas Instruments SensorTag CC2541. I am trying to show the temperature data in the advertising packets.
First, I implemented the temperature service UUID in the advertising data packet. This is done in the SensorTag.c firmware (using IAR Workbench for 8051).
static uint8 advertData[] =
{
// Flags; this sets the device to use limited discoverable
// mode (advertises for 30 seconds at a time) instead of general
// discoverable mode (advertises indefinitely)
0x02, // length of this data
GAP_ADTYPE_FLAGS,
DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
0x03,
GAP_ADTYPE_16BIT_MORE,
LO_UINT16(IRTEMPERATURE_SERV_UUID),
HI_UINT16(IRTEMPERATURE_SERV_UUID)
};
And here is the Scan Response Array:
// GAP - SCAN RSP data (max size = 31 bytes)
static uint8 scanRspData[] =
{
// complete name
0x0A, // length of this data
GAP_ADTYPE_LOCAL_NAME_COMPLETE,
0x53, // 'S'
0x65, // 'e'
0x6E, // 'n'
0x73, // 's'
0x6F, // 'o'
0x66, // 'f'
0x74, // 't'
0x69, // 'i'
0x61, // 'a'
// connection interval range
0x05, // length of this data
GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE,
LO_UINT16( DEFAULT_DESIRED_MIN_CONN_INTERVAL ),
HI_UINT16( DEFAULT_DESIRED_MIN_CONN_INTERVAL ),
LO_UINT16( DEFAULT_DESIRED_MAX_CONN_INTERVAL ),
HI_UINT16( DEFAULT_DESIRED_MAX_CONN_INTERVAL ),
0x03,
GAP_ADTYPE_16BIT_MORE,
LO_UINT16(IRTEMPERATURE_SERV_UUID),
HI_UINT16(IRTEMPERATURE_SERV_UUID)
};
Then, I use a sample Bluetooth Low Energy GAT application and modify it to show the data when it is scanning. The original app was used for another beacon device and it showed the beacon name + RSSI + thermometer service when scanning.
Here is the original code:
class TemperatureBeacon {
/* Full Bluetooth UUID that defines the Health Thermometer Service */
public static final ParcelUuid THERM_SERVICE = ParcelUuid.fromString("00001809-0000-1000-8000-00805f9b34fb");
/* Short-form UUID that defines the Health Thermometer service */
private static final int UUID_SERVICE_THERMOMETER = 0x1809;
private String mName;
private float mCurrentTemp;
//Device metadata
private int mSignal;
private String mAddress;
/* Builder for Lollipop+ */
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TemperatureBeacon(ScanRecord record, String deviceAddress, int rssi) {
mSignal = rssi;
mAddress = deviceAddress;
mName = record.getDeviceName();
byte[] data = record.getServiceData(THERM_SERVICE);
if (data != null) {
mCurrentTemp = parseTemp(data);
} else {
mCurrentTemp = 0f;
}
}
/* Builder for pre-Lollipop */
public TemperatureBeacon(List<AdRecord> records, String deviceAddress, int rssi) {
mSignal = rssi;
mAddress = deviceAddress;
for(AdRecord packet : records) {
//Find the device name record
if (packet.getType() == AdRecord.TYPE_NAME) {
mName = AdRecord.getName(packet);
}
//Find the service data record that contains our service's UUID
if (packet.getType() == AdRecord.TYPE_SERVICEDATA
&& AdRecord.getServiceDataUuid(packet) == UUID_SERVICE_THERMOMETER) {
byte[] data = AdRecord.getServiceData(packet);
mCurrentTemp = parseTemp(data);
}
}
}
private float parseTemp(byte[] serviceData) {
/*
* Temperature data is two bytes, and precision is 0.5degC.
* LSB contains temperature whole number
* MSB contains a bit flag noting if fractional part exists
*/
float temp = (serviceData[0] & 0xFF);
if ((serviceData[1] & 0x80) != 0) {
temp += 0.5f;
}
return temp;
}
public String getName() {
return mName;
}
public int getSignal() {
return mSignal;
}
public float getCurrentTemp() {
return mCurrentTemp;
}
public String getAddress() {
return mAddress;
}
#Override
public String toString() {
return String.format("%s (%ddBm): %.1fC", mName, mSignal, mCurrentTemp);
}
}
As you can see, the program uses the THERM_SERVICE UUID (00001809-0000-1000-8000-00805f9b34fb) and its short form (0x1809). I tried to modify it with the SensorTag IR Temperature UUID service (F000AA00-0451-4000-B000-000000000000). And the result is that, it cannot show neither the name of the scanned device nor the temperature data.
Can you give some suggestions for this problem? Thank you!
The missing part in your question is the scan response structure in your modified SensorTag code. The advertData only notifies the central (iOS) of it's presence and available services (temperature). The scan response may carry more information, such as the value of the temperature. Could you post the scan response array?