/** * Required for making Beacon parcelable * * @param in parcel */ protected Beacon(Parcel in) { int size = in.readInt(); this.mIdentifiers = new ArrayList<Identifier>(size); for (int i = 0; i < size; i++) { mIdentifiers.add(Identifier.parse(in.readString())); } mDistance = in.readDouble(); mRssi = in.readInt(); mTxPower = in.readInt(); mBluetoothAddress = in.readString(); mBeaconTypeCode = in.readInt(); mServiceUuid = in.readInt(); int dataSize = in.readInt(); this.mDataFields = new ArrayList<Long>(dataSize); for (int i = 0; i < dataSize; i++) { mDataFields.add(in.readLong()); } int extraDataSize = in.readInt(); if (LogManager.isVerboseLoggingEnabled()) { LogManager.d(TAG, "reading " + extraDataSize + " extra data fields from parcel"); } this.mExtraDataFields = new ArrayList<Long>(extraDataSize); for (int i = 0; i < extraDataSize; i++) { mExtraDataFields.add(in.readLong()); } mManufacturer = in.readInt(); mBluetoothName = in.readString(); }
/** * 设置距离计算模型 * * @param distanceModelUpdateUrl null 使用asserts/model-distance-calculations.json; * 否则使用url指定的网上模型文件,如"http://data.altbeacon.org/android-distance.json" */ public void setDefaultDistanceCalcuator(String distanceModelUpdateUrl) { /** * 读取应用程序asserts/model-distance-calculations.json,在应用程序私有数据空间写入同名文件。 * java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java的loadModelMapFromFile()读取此文件,以构造缺省模型。 */ if (distanceModelUpdateUrl == null) { try { String jsonString = stringFromAssertFile(); // 读取asserts/model-distance-calculations.json boolean ok = saveJson(jsonString); // 将距离模型json数据写入应用程序私有空间中的文件:CONFIG_FILE。 if (ok) { LogManager.d(TAG, "setDefaultDistanceCalcuator ok,from asserts/" + CONFIG_FILE); } else { LogManager.d(TAG, "setDefaultDistanceCalcuator error,from asserts/" + CONFIG_FILE); } } catch (IOException e) { LogManager.d(TAG, "Exception:" + e.toString()); } // 设置一个虚假的url,目的是引起BeaconService中调用ModelSpecificDistanceCalculator()-->loadModelMap()-->在应程序私有空间找CONFIG_FILE // 由于以上已经存储了这个文件,因此,绑定BeaconService后,执行上述序列,loadModelMap()能够成功找到该文件。 // 鉴于此,必须在绑定BeaconService前执行此函数。loadModelMap()仅在第一次调用时才检查此文件,一旦文件已经写入,下一次就不检查了。因此测试时,每次要完全卸载程序,才能验证此程序的逻辑。 BeaconManager.setDistanceModelUpdateUrl("nodistanceModelUpdateUrl"); } else { // BeaconService中调用ModelSpecificDistanceCalculator()设置距离计算模型 BeaconManager.setDistanceModelUpdateUrl(distanceModelUpdateUrl); LogManager.d(TAG, "setDefaultDistanceCalcuator, from " + distanceModelUpdateUrl); } }
/** * Required for making object Parcelable. If you override this class, you must override this * method if you add any additional fields. */ public void writeToParcel(Parcel out, int flags) { out.writeInt(mIdentifiers.size()); LogManager.d(TAG, "serializing identifiers of size %s", mIdentifiers.size()); for (Identifier identifier : mIdentifiers) { out.writeString(identifier == null ? null : identifier.toString()); } out.writeDouble(getDistance()); out.writeInt(mRssi); out.writeInt(mTxPower); out.writeString(mBluetoothAddress); out.writeInt(mBeaconTypeCode); out.writeInt(mServiceUuid); out.writeInt(mDataFields.size()); for (Long dataField : mDataFields) { out.writeLong(dataField); } if (LogManager.isVerboseLoggingEnabled()) { LogManager.d(TAG, "writing " + mExtraDataFields.size() + " extra data fields to parcel"); } out.writeInt(mExtraDataFields.size()); for (Long dataField : mExtraDataFields) { out.writeLong(dataField); } out.writeInt(mManufacturer); out.writeString(mBluetoothName); }
@Override public void getBeacons(Collection<Beacon> beacons) { // 防止停止收集beacons前,再次收到此回调,导致重复记录,在开始和结束监控(查找)beacons时设置。 if (isRecorded) return; // 已经记录了 // 日志记录和屏幕显示Beacon信息 // 有可能到达采样周期时,没有找到所有beacons,甚至是0个beacons,因此,应重复记录,一直到采样周期结束。当然是最后更新的有效。 String str = "beacons=" + beacons.size(); LogManager.d(TAG, str); logToDisplay(str); String rssi; for (Beacon beacon : beacons) { // becaon的两个id(major,minor),rssi及其平均值 str = beacon.getId2() + ":" + beacon.getId3() + "=" + beacon.getRssi() + "," + String.format("%.2f", beacon.getRunningAverageRssi()); LogManager.d(TAG, str); logToDisplay(str); // 记录至mBeaconsRssi rssi = beacon.getId2() + "_" + beacon.getId3() + ":" + String.format("%.2f", beacon.getRunningAverageRssi()); mBeaconsRssi.put(beacon, rssi); } // 记录参考点的各个beacon的id和rssi平均值 if ((System.currentTimeMillis() - startSample) >= SamplePeroid) { // 将目前定位参考点测量的各个beacons的rssi平均值计入数据库。 SaveRssiToDb(); str = "记录完毕,定位参考点[" + reference_pointPerf + reference_pointNum + "]" + "各个beacon的rssi平均值"; LogManager.d(TAG, str); logToDisplay(str); // 以下必须在UI现成中执行,否则,程序将异常终止。 runOnUiThread( new Runnable() { public void run() { String str = "记录完毕,定位参考点[" + reference_pointPerf + reference_pointNum + "]"; Toast.makeText(trainingActivity.this, str, Toast.LENGTH_LONG).show(); // 下一个参考点默认名称 reference_pointNum++; reference_point_edit.setText(reference_pointPerf + reference_pointNum); // 停止查找beacons onMonitoringStop(null); } }); } }
/** * 每个扫描周期结束,根据20秒内各beacon的RSSI平均值计算它的距离,该回调获取这些beacon的距离值 Called once per second (实际上是每扫描周期) * to give an estimate of the mDistance to visible beacons */ @Override public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) { LogManager.d(TAG, "didRangeBeaconsInRegion(),beacons=" + beacons.size()); for (Beacon beacon : beacons) { LogManager.d(TAG, beacon.getId2() + ":" + beacon.getId3() + "," + beacon.getDistance()); } Beacon beacon = mNearestBeacon.getNearestBeacon(mGetBeaconType, beacons); mOnNearestBeaconListener.getNearestBeacon(mGetBeaconType, beacon); }
/** Called when no beacons in a Region are visible. */ @Override public void didExitRegion(Region region) { LogManager.d(TAG, "didExitRegion(),region uniqueId= " + region.getUniqueId()); /** * Tells the BeaconService to stop looking for beacons that match the passed Region object * and providing mDistance information for them. */ try { mBeaconManager.stopRangingBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION); } catch (RemoteException e) { LogManager.d(TAG, "RemoteException:" + e.toString()); } }
/** * Sets the duration in milliseconds spent not scanning between each Bluetooth LE scan cycle when * no ranging/monitoring clients are in the background. default 5 minutes * * @param p (ms) */ public void setBackgroundBetweenScanPeriod(long p) { mBeaconManager.setBackgroundBetweenScanPeriod(p); try { mBeaconManager.updateScanPeriods(); // 保证在下一个循环扫描周期生效 } catch (RemoteException e) { LogManager.d(TAG, "RemoteException:" + e.toString()); } }
/** Called when at least one beacon in a Region is visible. */ @Override public void didEnterRegion(Region region) { LogManager.d(TAG, "didEnterRegion(),region uniqueId= " + region.getUniqueId()); /** * 启动测距修正 Tells the BeaconService to start looking for beacons that match the passed * Region object, and providing updates on the estimated mDistance every * seconds(实际上是每个扫描周期) while beacons in the Region are visible. Note that the Region's * unique identifier must be retained to later call the stopRangingBeaconsInRegion method. * this will provide an update once per second with the estimated distance to the beacon * in the didRAngeBeaconsInRegion method. */ try { mBeaconManager.startRangingBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION); } catch (RemoteException e) { LogManager.d(TAG, "RemoteException:" + e.toString()); } }
private void loadDefaultModelMap() { mModelMap = new HashMap<AndroidModel, DistanceCalculator>(); try { buildModelMap(stringFromFilePath(CONFIG_FILE)); } catch (Exception e) { LogManager.e(e, TAG, "Cannot build model distance calculations"); } }
@Override protected void onDestroy() { LogManager.d(TAG, "onDestroy()"); super.onDestroy(); mFindBeacons.closeSearcher(); loghelper.stop(); }
@Override public double calculateDistance(int txPower, double rssi) { if (mDistanceCalculator == null) { LogManager.w(TAG, "distance calculator has not been set"); return -1.0; } return mDistanceCalculator.calculateDistance(txPower, rssi); }
/** * Estimate the distance to the beacon using the DistanceCalculator set on this class. If no * DistanceCalculator has been set, return -1 as the distance. * * @see org.altbeacon.beacon.distance.DistanceCalculator * @param txPower * @param bestRssiAvailable * @return */ protected static Double calculateDistance(int txPower, double bestRssiAvailable) { if (Beacon.getDistanceCalculator() != null) { return Beacon.getDistanceCalculator().calculateDistance(txPower, bestRssiAvailable); } else { LogManager.e(TAG, "Distance calculator not set. Distance will bet set to -1"); return -1.0; } }
private boolean loadModelMapFromFile() { File file = new File(mContext.getFilesDir(), CONFIG_FILE); FileInputStream inputStream = null; BufferedReader reader = null; StringBuilder sb = new StringBuilder(); try { inputStream = new FileInputStream(file); reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { sb.append(line).append("\n"); } } catch (FileNotFoundException fnfe) { // This occurs on the first time the app is run, no error message necessary. return false; } catch (IOException e) { LogManager.e(e, TAG, "Cannot open distance model file %s", file); return false; } finally { if (reader != null) { try { reader.close(); } catch (Exception e2) { } } if (inputStream != null) { try { inputStream.close(); } catch (Exception e2) { } } } try { buildModelMap(sb.toString()); return true; } catch (JSONException e) { LogManager.e( TAG, "Cannot update distance models from online database at %s with JSON", e, mRemoteUpdateUrlString, sb.toString()); return false; } }
/** * Called with a state value of MonitorNotifier.INSIDE when at least one beacon in a Region * is visible. Called with a state value of MonitorNotifier.OUTSIDE when no beacons in a * Region are visible. */ @Override public void didDetermineStateForRegion(int state, Region region) { LogManager.d( TAG, "didDetermineStateForRegion() ,region uniqueId= " + region.getUniqueId() + " state=" + (state == 1 ? "inside" : "outside")); }
/** 停止查找beacons */ public void onMonitoringStop(View view) { logToDisplay("onMonitoringStop(),stopMonitoringBeaconsInRegion"); LogManager.d(TAG, "onMonitoringStop(),stopMonitoringBeaconsInRegion"); mFindBeacons.closeSearcher(); mStart_btn.setEnabled(true); mStop_btn.setEnabled(false); // 设置记录标志 isRecorded = true; }
private boolean saveJson(String jsonString) { FileOutputStream outputStream = null; try { outputStream = mContext.openFileOutput(CONFIG_FILE, Context.MODE_PRIVATE); outputStream.write(jsonString.getBytes()); outputStream.close(); } catch (Exception e) { LogManager.w(e, TAG, "Cannot write updated distance model to local storage"); return false; } finally { try { if (outputStream != null) outputStream.close(); } catch (Exception e) { } } LogManager.i(TAG, "Successfully saved new distance model file"); return true; }
/** * Provides a calculated estimate of the distance to the beacon based on a running average of the * RSSI and the transmitted power calibration value included in the beacon advertisement. This * value is specific to the type of Android device receiving the transmission. * * @see #mDistance * @return distance */ public double getDistance() { if (mDistance == null) { double bestRssiAvailable = mRssi; if (mRunningAverageRssi != null) { bestRssiAvailable = mRunningAverageRssi; } else { LogManager.d(TAG, "Not using running average RSSI because it is null"); } mDistance = calculateDistance(mTxPower, bestRssiAvailable); } return mDistance; }
private DistanceCalculator findCalculatorForModel(AndroidModel model) { LogManager.d( TAG, "Finding best distance calculator for %s, %s, %s, %s", model.getVersion(), model.getBuildNumber(), model.getModel(), model.getManufacturer()); if (mModelMap == null) { LogManager.d(TAG, "Cannot get distance calculator because modelMap was never initialized"); return null; } int highestScore = 0; AndroidModel bestMatchingModel = null; for (AndroidModel candidateModel : mModelMap.keySet()) { if (candidateModel.matchScore(model) > highestScore) { highestScore = candidateModel.matchScore(model); bestMatchingModel = candidateModel; } } if (bestMatchingModel != null) { LogManager.d(TAG, "found a match with score %s", highestScore); LogManager.d( TAG, "Finding best distance calculator for %s, %s, %s, %s", bestMatchingModel.getVersion(), bestMatchingModel.getBuildNumber(), bestMatchingModel.getModel(), bestMatchingModel.getManufacturer()); mModel = bestMatchingModel; } else { mModel = mDefaultModel; LogManager.w(TAG, "Cannot find match for this device. Using default"); } return mModelMap.get(mModel); }
/** * Called when the beacon service is running and ready to accept your commands through the * BeaconManager 开始查找beacon */ @Override public void onBeaconServiceConnect() { /** * Tells the BeaconService to start looking for beacons that match the passed Region * object. Note that the Region's unique identifier must be retained保存 to later call the * stopMonitoringBeaconsInRegion method. */ try { // 通知BeaconService,开始监控特定区域的Beacons,一旦检测到beacons,执行MonitorNotifier接口中的回调(进入,离开,临界) mBeaconManager.startMonitoringBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION); } catch (RemoteException e) { LogManager.d(TAG, "RemoteException:" + e.toString()); } }
@TargetApi(Build.VERSION_CODES.HONEYCOMB) private void requestModelMapFromWeb() { if (mContext.checkCallingOrSelfPermission("android.permission.INTERNET") != PackageManager.PERMISSION_GRANTED) { LogManager.w( TAG, "App has no android.permission.INTERNET permission. Cannot check for distance model updates"); return; } new ModelSpecificDistanceUpdater( mContext, mRemoteUpdateUrlString, new ModelSpecificDistanceUpdater.CompletionHandler() { @Override public void onComplete(String body, Exception ex, int code) { if (ex != null) { LogManager.w( TAG, "Cannot updated distance models from online database at %s", ex, mRemoteUpdateUrlString); } else if (code != 200) { LogManager.w( TAG, "Cannot updated distance models from online database at %s " + "due to HTTP status code %s", mRemoteUpdateUrlString, code); } else { LogManager.d(TAG, "Successfully downloaded distance models from online database"); try { buildModelMap(body); if (saveJson(body)) { loadModelMapFromFile(); mDistanceCalculator = findCalculatorForModel(mRequestedModel); LogManager.i( TAG, "Successfully updated distance model with latest from online database"); } } catch (JSONException e) { LogManager.w(e, TAG, "Cannot parse json from downloaded distance model"); } } } }) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }
/** 开始查找附近beacons */ public void onMonitoringStart(View view) { logToDisplay("onMonitoringStart(),startMonitoringBeaconsInRegion"); LogManager.d(TAG, "onMonitoringStart(),startMonitoringBeaconsInRegion"); // 根据编辑框,设置前台扫描周期,default 1.1s onForegroundScanPeriod(null); // 根据编辑框,设置rssi采样周期,即,计算该时间段内的平均RSSI(首末各去掉10%),缺省是20秒(20000毫秒) onSamplePeriod(null); mFindBeacons.openSearcher(); mStart_btn.setEnabled(false); mStop_btn.setEnabled(true); // 记录开始采样时间 startSample = System.currentTimeMillis(); // 设置记录标志 isRecorded = false; }
@TargetApi(5) protected Beacon fromScanData( byte[] bytesToProcess, int rssi, BluetoothDevice device, Beacon beacon) { BleAdvertisement advert = new BleAdvertisement(bytesToProcess); boolean parseFailed = false; Pdu pduToParse = null; int startByte = 0; ArrayList<Identifier> identifiers = new ArrayList<Identifier>(); ArrayList<Long> dataFields = new ArrayList<Long>(); for (Pdu pdu : advert.getPdus()) { if (pdu.getType() == Pdu.GATT_SERVICE_UUID_PDU_TYPE || pdu.getType() == Pdu.MANUFACTURER_DATA_PDU_TYPE) { pduToParse = pdu; if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Processing pdu type %02X: %s with startIndex: %d, endIndex: %d", pdu.getType(), bytesToHex(bytesToProcess), pdu.getStartIndex(), pdu.getEndIndex()); } break; } else { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d(TAG, "Ignoring pdu type %02X", pdu.getType()); } } } if (pduToParse == null) { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d(TAG, "No PDUs to process in this packet."); } parseFailed = true; } else { byte[] serviceUuidBytes = null; byte[] typeCodeBytes = longToByteArray( getMatchingBeaconTypeCode(), mMatchingBeaconTypeCodeEndOffset - mMatchingBeaconTypeCodeStartOffset + 1); if (getServiceUuid() != null) { serviceUuidBytes = longToByteArray( getServiceUuid(), mServiceUuidEndOffset - mServiceUuidStartOffset + 1, false); } startByte = pduToParse.getStartIndex(); boolean patternFound = false; if (getServiceUuid() == null) { if (byteArraysMatch( bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes, 0)) { patternFound = true; } } else { if (byteArraysMatch( bytesToProcess, startByte + mServiceUuidStartOffset, serviceUuidBytes, 0) && byteArraysMatch( bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes, 0)) { patternFound = true; } } if (patternFound == false) { // This is not a beacon if (getServiceUuid() == null) { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "This is not a matching Beacon advertisement. (Was expecting %s. " + "The bytes I see are: %s", byteArrayToString(typeCodeBytes), bytesToHex(bytesToProcess)); } } else { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "This is not a matching Beacon advertisement. Was expecting %s at offset %d and %s at offset %d. " + "The bytes I see are: %s", byteArrayToString(serviceUuidBytes), startByte + mServiceUuidStartOffset, byteArrayToString(typeCodeBytes), startByte + mMatchingBeaconTypeCodeStartOffset, bytesToHex(bytesToProcess)); } } parseFailed = true; beacon = null; } else { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "This is a recognized beacon advertisement -- %s seen", byteArrayToString(typeCodeBytes)); } } if (patternFound) { if (bytesToProcess.length <= startByte + mLayoutSize && mAllowPduOverflow) { // If the layout size is bigger than this PDU, and we allow overflow. Make sure // the byte buffer is big enough by zero padding the end so we don't try to read // outside the byte array of the advertisement if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Expanding buffer because it is too short to parse: " + bytesToProcess.length + ", needed: " + (startByte + mLayoutSize)); } bytesToProcess = ensureMaxSize(bytesToProcess, startByte + mLayoutSize); } for (int i = 0; i < mIdentifierEndOffsets.size(); i++) { int endIndex = mIdentifierEndOffsets.get(i) + startByte; if (endIndex > pduToParse.getEndIndex() && mIdentifierVariableLengthFlags.get(i)) { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Need to truncate identifier by " + (endIndex - pduToParse.getEndIndex())); } // If this is a variable length identifier, we truncate it to the size that // is available in the packet Identifier identifier = Identifier.fromBytes( bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, pduToParse.getEndIndex() + 1, mIdentifierLittleEndianFlags.get(i)); identifiers.add(identifier); } else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) { parseFailed = true; if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Cannot parse identifier " + i + " because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex()); } } else { Identifier identifier = Identifier.fromBytes( bytesToProcess, mIdentifierStartOffsets.get(i) + startByte, endIndex + 1, mIdentifierLittleEndianFlags.get(i)); identifiers.add(identifier); } } for (int i = 0; i < mDataEndOffsets.size(); i++) { int endIndex = mDataEndOffsets.get(i) + startByte; if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) { if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Cannot parse data field " + i + " because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex() + ". Setting value to 0"); } dataFields.add(new Long(0l)); } else { String dataString = byteArrayToFormattedString( bytesToProcess, mDataStartOffsets.get(i) + startByte, endIndex, mDataLittleEndianFlags.get(i)); dataFields.add(Long.parseLong(dataString)); } } if (mPowerStartOffset != null) { int endIndex = mPowerEndOffset + startByte; int txPower = 0; try { if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) { parseFailed = true; if (LogManager.isVerboseLoggingEnabled()) { LogManager.d( TAG, "Cannot parse power field because PDU is too short. endIndex: " + endIndex + " PDU endIndex: " + pduToParse.getEndIndex()); } } else { String powerString = byteArrayToFormattedString( bytesToProcess, mPowerStartOffset + startByte, mPowerEndOffset + startByte, false); txPower = Integer.parseInt(powerString) + mDBmCorrection; // make sure it is a signed integer if (txPower > 127) { txPower -= 256; } beacon.mTxPower = txPower; } } catch (NumberFormatException e1) { // keep default value } catch (NullPointerException e2) { // keep default value } } } } if (parseFailed) { beacon = null; } else { int beaconTypeCode = 0; String beaconTypeString = byteArrayToFormattedString( bytesToProcess, mMatchingBeaconTypeCodeStartOffset + startByte, mMatchingBeaconTypeCodeEndOffset + startByte, false); beaconTypeCode = Integer.parseInt(beaconTypeString); // TODO: error handling needed on the parse int manufacturer = 0; String manufacturerString = byteArrayToFormattedString(bytesToProcess, startByte, startByte + 1, true); manufacturer = Integer.parseInt(manufacturerString); String macAddress = null; String name = null; if (device != null) { macAddress = device.getAddress(); name = device.getName(); } beacon.mIdentifiers = identifiers; beacon.mDataFields = dataFields; beacon.mRssi = rssi; beacon.mBeaconTypeCode = beaconTypeCode; if (mServiceUuid != null) { beacon.mServiceUuid = (int) mServiceUuid.longValue(); } else { beacon.mServiceUuid = -1; } beacon.mBluetoothAddress = macAddress; beacon.mBluetoothName = name; beacon.mManufacturer = manufacturer; beacon.mParserIdentifier = mIdentifier; } return beacon; }
/** * Defines a beacon field parsing algorithm based on a string designating the zero-indexed offsets * to bytes within a BLE advertisement. * * <p>If you want to see examples of how other folks have set up BeaconParsers for different kinds * of beacons, try doing a Google search for "getBeaconParsers" (include the quotes in the * search.) * * <p>Four prefixes are allowed in the string: * * <pre> * m - matching byte sequence for this beacon type to parse (exactly one required) * s - ServiceUuid for this beacon type to parse (optional, only for Gatt-based beacons) * i - identifier (at least one required, multiple allowed) * p - power calibration field (exactly one required) * d - data field (optional, multiple allowed) * x - extra layout. Signifies that the layout is secondary to a primary layout with the same * matching byte sequence (or ServiceUuid). Extra layouts do not require power or * identifier fields and create Beacon objects without identifiers. * </pre> * * <p>Each prefix is followed by a colon, then an inclusive decimal byte offset for the field from * the beginning of the advertisement. In the case of the m prefix, an = sign follows the byte * offset, followed by a big endian hex representation of the bytes that must be matched for this * beacon type. When multiple i or d entries exist in the string, they will be added in order of * definition to the identifier or data array for the beacon when parsing the beacon * advertisement. Terms are separated by commas. * * <p>All offsets from the start of the advertisement are relative to the first byte of the two * byte manufacturer code. The manufacturer code is therefore always at position 0-1 * * <p>All data field and identifier expressions may be optionally suffixed with the letter l, * which indicates the field should be parsed as little endian. If not present, the field will be * presumed to be big endian. Note: serviceUuid fields are always little endian. * * <p>Identifier fields may be optionally suffixed with the letter v, which indicates the field is * variable length, and may be shorter than the declared length if the parsed PDU for the * advertisement is shorter than needed to parse the full identifier. * * <p>If the expression cannot be parsed, a <code>BeaconLayoutException</code> is thrown. * * <p>Example of a parser string for AltBeacon: </pre> * * "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25" </pre> * * <p>This signifies that the beacon type will be decoded when an advertisement is found with * 0xbeac in bytes 2-3, and a three-part identifier will be pulled out of bytes 4-19, bytes 20-21 * and bytes 22-23, respectively. A signed power calibration value will be pulled out of byte 24, * and a data field will be pulled out of byte 25. Note: bytes 0-1 of the BLE manufacturer * advertisements are the two byte manufacturer code. Generally you should not match on these two * bytes when using a BeaconParser, because it will limit your parser to matching only a * transmitter made by a specific manufacturer. Software and operating systems that scan for * beacons typically ignore these two bytes, allowing beacon manufacturers to use their own * company code assigned by Bluetooth SIG. The default parser implementation will already pull out * this company code and store it in the beacon.mManufacturer field. Matcher expressions should * therefore start with "m2-3:" followed by the multi-byte hex value that signifies the beacon * type. * * @param beaconLayout * @return the BeaconParser instance */ public BeaconParser setBeaconLayout(String beaconLayout) { Log.d(TAG, "Parsing beacon layout: " + beaconLayout); String[] terms = beaconLayout.split(","); mExtraFrame = false; // this is not an extra frame by default for (String term : terms) { boolean found = false; Matcher matcher = I_PATTERN.matcher(term); while (matcher.find()) { found = true; try { int startOffset = Integer.parseInt(matcher.group(1)); int endOffset = Integer.parseInt(matcher.group(2)); Boolean littleEndian = matcher.group(3).contains(LITTLE_ENDIAN_SUFFIX); mIdentifierLittleEndianFlags.add(littleEndian); Boolean variableLength = matcher.group(3).contains(VARIABLE_LENGTH_SUFFIX); mIdentifierVariableLengthFlags.add(variableLength); mIdentifierStartOffsets.add(startOffset); mIdentifierEndOffsets.add(endOffset); } catch (NumberFormatException e) { throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term); } } matcher = D_PATTERN.matcher(term); while (matcher.find()) { found = true; try { int startOffset = Integer.parseInt(matcher.group(1)); int endOffset = Integer.parseInt(matcher.group(2)); Boolean littleEndian = matcher.group(3).contains("l"); mDataLittleEndianFlags.add(littleEndian); mDataStartOffsets.add(startOffset); mDataEndOffsets.add(endOffset); } catch (NumberFormatException e) { throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term); } } matcher = P_PATTERN.matcher(term); while (matcher.find()) { found = true; try { int startOffset = Integer.parseInt(matcher.group(1)); int endOffset = Integer.parseInt(matcher.group(2)); int dBmCorrection = 0; if (matcher.group(3) != null) { dBmCorrection = Integer.parseInt(matcher.group(3)); } mDBmCorrection = dBmCorrection; mPowerStartOffset = startOffset; mPowerEndOffset = endOffset; } catch (NumberFormatException e) { throw new BeaconLayoutException( "Cannot parse integer power byte offset in term: " + term); } } matcher = M_PATTERN.matcher(term); while (matcher.find()) { found = true; try { int startOffset = Integer.parseInt(matcher.group(1)); int endOffset = Integer.parseInt(matcher.group(2)); mMatchingBeaconTypeCodeStartOffset = startOffset; mMatchingBeaconTypeCodeEndOffset = endOffset; } catch (NumberFormatException e) { throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term); } String hexString = matcher.group(3); try { mMatchingBeaconTypeCode = Long.decode("0x" + hexString); } catch (NumberFormatException e) { throw new BeaconLayoutException( "Cannot parse beacon type code: " + hexString + " in term: " + term); } } matcher = S_PATTERN.matcher(term); while (matcher.find()) { found = true; try { int startOffset = Integer.parseInt(matcher.group(1)); int endOffset = Integer.parseInt(matcher.group(2)); mServiceUuidStartOffset = startOffset; mServiceUuidEndOffset = endOffset; } catch (NumberFormatException e) { throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term); } String hexString = matcher.group(3); try { mServiceUuid = Long.decode("0x" + hexString); } catch (NumberFormatException e) { throw new BeaconLayoutException( "Cannot parse serviceUuid: " + hexString + " in term: " + term); } } matcher = X_PATTERN.matcher(term); while (matcher.find()) { found = true; mExtraFrame = true; } if (!found) { LogManager.d(TAG, "cannot parse term %s", term); throw new BeaconLayoutException("Cannot parse beacon layout term: " + term); } } if (!mExtraFrame) { // extra frames do not have to have identifiers or power fields, but other types do if (mIdentifierStartOffsets.size() == 0 || mIdentifierEndOffsets.size() == 0) { throw new BeaconLayoutException( "You must supply at least one identifier offset with a prefix of 'i'"); } if (mPowerStartOffset == null || mPowerEndOffset == null) { throw new BeaconLayoutException("You must supply a power byte offset with a prefix of 'p'"); } } if (mMatchingBeaconTypeCodeStartOffset == null || mMatchingBeaconTypeCodeEndOffset == null) { throw new BeaconLayoutException( "You must supply a matching beacon type expression with a prefix of 'm'"); } mLayoutSize = calculateLayoutSize(); return this; }
/** * Get BLE advertisement bytes for a Beacon * * @param beacon the beacon containing the data to be transmitted * @return the byte array of the advertisement */ @TargetApi(Build.VERSION_CODES.GINGERBREAD) public byte[] getBeaconAdvertisementData(Beacon beacon) { byte[] advertisingBytes; if (beacon.getIdentifiers().size() != getIdentifierCount()) { throw new IllegalArgumentException( "Beacon has " + beacon.getIdentifiers().size() + " identifiers but format requires " + getIdentifierCount()); } int lastIndex = -1; if (mMatchingBeaconTypeCodeEndOffset != null && mMatchingBeaconTypeCodeEndOffset > lastIndex) { lastIndex = mMatchingBeaconTypeCodeEndOffset; } if (mPowerEndOffset != null && mPowerEndOffset > lastIndex) { lastIndex = mPowerEndOffset; } for (int identifierNum = 0; identifierNum < this.mIdentifierEndOffsets.size(); identifierNum++) { if (this.mIdentifierEndOffsets.get(identifierNum) != null && this.mIdentifierEndOffsets.get(identifierNum) > lastIndex) { lastIndex = this.mIdentifierEndOffsets.get(identifierNum); } } for (int identifierNum = 0; identifierNum < this.mDataEndOffsets.size(); identifierNum++) { if (this.mDataEndOffsets.get(identifierNum) != null && this.mDataEndOffsets.get(identifierNum) > lastIndex) { lastIndex = this.mDataEndOffsets.get(identifierNum); } } // we must adjust the lastIndex to account for variable length identifiers, if there are any. int adjustedIdentifiersLength = 0; for (int identifierNum = 0; identifierNum < this.mIdentifierStartOffsets.size(); identifierNum++) { if (mIdentifierVariableLengthFlags.get(identifierNum)) { int declaredIdentifierLength = (this.mIdentifierEndOffsets.get(identifierNum) - this.mIdentifierStartOffsets.get(identifierNum) + 1); int actualIdentifierLength = beacon.getIdentifier(identifierNum).getByteCount(); adjustedIdentifiersLength += actualIdentifierLength; adjustedIdentifiersLength -= declaredIdentifierLength; } } lastIndex += adjustedIdentifiersLength; advertisingBytes = new byte[lastIndex + 1 - 2]; long beaconTypeCode = this.getMatchingBeaconTypeCode(); // set type code for (int index = this.mMatchingBeaconTypeCodeStartOffset; index <= this.mMatchingBeaconTypeCodeEndOffset; index++) { byte value = (byte) (this.getMatchingBeaconTypeCode() >> (8 * (this.mMatchingBeaconTypeCodeEndOffset - index)) & 0xff); advertisingBytes[index - 2] = value; } // set identifiers for (int identifierNum = 0; identifierNum < this.mIdentifierStartOffsets.size(); identifierNum++) { byte[] identifierBytes = beacon .getIdentifier(identifierNum) .toByteArrayOfSpecifiedEndianness( !this.mIdentifierLittleEndianFlags.get(identifierNum)); // If the identifier we are trying to stuff into the space is different than the space // available // adjust it if (identifierBytes.length < getIdentifierByteCount(identifierNum)) { if (!mIdentifierVariableLengthFlags.get(identifierNum)) { // Pad it, but only if this is not a variable length identifier if (mIdentifierLittleEndianFlags.get(identifierNum)) { // this is little endian. Pad at the end of the array identifierBytes = Arrays.copyOf(identifierBytes, getIdentifierByteCount(identifierNum)); } else { // this is big endian. Pad at the beginning of the array byte[] newIdentifierBytes = new byte[getIdentifierByteCount(identifierNum)]; System.arraycopy( identifierBytes, 0, newIdentifierBytes, getIdentifierByteCount(identifierNum) - identifierBytes.length, identifierBytes.length); identifierBytes = newIdentifierBytes; } } LogManager.d( TAG, "Expanded identifier because it is too short. It is now: " + byteArrayToString(identifierBytes)); } else if (identifierBytes.length > getIdentifierByteCount(identifierNum)) { if (mIdentifierLittleEndianFlags.get(identifierNum)) { // Truncate it at the beginning for big endian identifierBytes = Arrays.copyOfRange( identifierBytes, getIdentifierByteCount(identifierNum) - identifierBytes.length, getIdentifierByteCount(identifierNum)); } else { // Truncate it at the end for little endian identifierBytes = Arrays.copyOf(identifierBytes, getIdentifierByteCount(identifierNum)); } LogManager.d( TAG, "Truncated identifier because it is too long. It is now: " + byteArrayToString(identifierBytes)); } else { LogManager.d(TAG, "Identifier size is just right: " + byteArrayToString(identifierBytes)); } for (int index = this.mIdentifierStartOffsets.get(identifierNum); index <= this.mIdentifierStartOffsets.get(identifierNum) + identifierBytes.length - 1; index++) { advertisingBytes[index - 2] = (byte) identifierBytes[index - this.mIdentifierStartOffsets.get(identifierNum)]; } } // set power for (int index = this.mPowerStartOffset; index <= this.mPowerEndOffset; index++) { advertisingBytes[index - 2] = (byte) (beacon.getTxPower() >> (8 * (index - this.mPowerStartOffset)) & 0xff); } // set data fields for (int dataFieldNum = 0; dataFieldNum < this.mDataStartOffsets.size(); dataFieldNum++) { long dataField = beacon.getDataFields().get(dataFieldNum); for (int index = this.mDataStartOffsets.get(dataFieldNum); index <= this.mDataEndOffsets.get(dataFieldNum); index++) { int endianCorrectedIndex = index; if (this.mDataLittleEndianFlags.get(dataFieldNum)) { endianCorrectedIndex = this.mDataEndOffsets.get(dataFieldNum) - index; } advertisingBytes[endianCorrectedIndex - 2] = (byte) (dataField >> (8 * (index - this.mDataStartOffsets.get(dataFieldNum))) & 0xff); } } return advertisingBytes; }
@Override protected void onCreate(Bundle savedInstanceState) { // 建议使用org.altbeacon.beacon.logging.LogManager.javaLogManager输出日志,altbeacon就是使用这种机制,便于发布版本时,减少输出日志信息。 // 输出所有ERROR(Log.e()), WARN(Log.w()), INFO(Log.i()), DEBUG(Log.d()), VERBOSE(Log.v()) // 对应日志级别由高到低 LogManager.setLogger(Loggers.verboseLogger()); // 全部不输出,在release版本中设置 // LogManager.setLogger(Loggers.empty()); // 输出ERROR(Log.e()), WARN(Log.w()),缺省状态,仅输出错误和警告信息,即输出警告级别以上的日志 // LogManager.setLogger(Loggers.warningLogger()); // 试验日志输出 // LogManager.e(TAG,"Error"); // LogManager.w(TAG,"Warn"); // LogManager.i(TAG,"info"); // LogManager.d(TAG,"debug"); // LogManager.v(TAG,"verbose"); LogManager.d(TAG, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 日志文件 start_logfile = (Button) findViewById(R.id.start_log); end_logfile = (Button) findViewById(R.id.end_log); // 设置SD卡中的日志文件,sd卡根目录/rssiRecord/mydistance.log loghelper = LogcatHelper.getInstance(this, "rssiRecord", "mydistance.log"); // 设置SD卡中的日志文件,sd卡根目录/mydistance.log // loghelper = LogcatHelper.getInstance(this,"","mydistance.log"); // 打印D级以上(包括D,I,W,E,F)的TAG,其它tag不打印 // Logformat = TAG + ":D *:S"; // 打印D级以上的TAG,和LogcatHelper全部,其它tag不打印 // Logformat = TAG + ":D LogcatHelper:V *:S"; // 打印D以上的TAG和RunningAverageRssiFilter,其他tag不打印(*:S) // Logformat = TAG + ":D RunningAverageRssiFilter:D *:S"; // 打印D以上的TAG和RssiDbManager,其他tag不打印(*:S) Logformat = TAG + ":D RssiDbManager:D *:S"; // 打印D以上的FindBeacons,其他tag不打印(*:S) // Logformat = "FindBeacons:D *:S"; // Logformat = "RangedBeacon:V *:S"; // 打印所有日志, priority=V | D | I | W | E ,级别由低到高 // Logformat = ""; // 日志文件 loghelper.start(Logformat); // "开始记录日志"按钮失效,此时已经开始记录日志 start_logfile.setEnabled(false); // 开始/停止监控(查找)beacons mStart_btn = (Button) findViewById(R.id.Mstart); mStop_btn = (Button) findViewById(R.id.Mstop); mStop_btn.setEnabled(false); // 获取FindBeacons唯一实例 mFindBeacons = FindBeacons.getInstance(this); // 设置默认前台扫描周期,default 1.1s ScanPeriod_edit = (EditText) findViewById(R.id.ScanPeriod_edit); ScanPeriod_edit.setText("1.1"); onForegroundScanPeriod(null); // rssi采样周期,即,计算该时间段内的平均RSSI(首末各去掉10%),缺省是20秒(20000毫秒) SamplePeriod_edit = (EditText) findViewById(R.id.SamplePeriod_edit); SamplePeriod_edit.setText("60"); // 在此设定停留时间,默认为1分钟。 onSamplePeriod(null); // 定位参考点名称 reference_point_edit = (EditText) findViewById(R.id.RPname); reference_point_edit.setText(reference_pointPerf + reference_pointNum); // 数据库管理 mRssiDbManager = new RssiDbManager(trainingActivity.this); // 设置获取附近所有beacons的监听对象,在每个扫描周期结束,通过该接口获取找到的所有beacons mFindBeacons.setBeaconsListener(mBeaconsListener); // 查看手机蓝牙是否可用,若当前状态为不可用,则默认调用意图请求打开系统蓝牙 mFindBeacons.checkBLEEnable(); logToDisplay("Mstart,Mstop分别代表查找beacon的开始和结束"); }