Android Question Beacon Radar view

mike1967

Active Member
Licensed User
Longtime User
Hello,Is there some One that has sample code in order to realize a Beacon scanning Radar view ? Thanks in advances.
 

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

mike1967

Active Member
Licensed User
Longtime User

Attachments

  • Screenshot_2024-08-29-11-32-54-31_40deb401b9ffe8e1df2f1cc5ba480b12.jpg
    Screenshot_2024-08-29-11-32-54-31_40deb401b9ffe8e1df2f1cc5ba480b12.jpg
    272.5 KB · Views: 26
Upvote 0

mike1967

Active Member
Licensed User
Longtime User
Java:
/*
 *
 *  Copyright (c) 2015 SameBits UG. All rights reserved.
 *
 *  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 com.samebits.beacon.locator.ui.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.SystemClock;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import com.samebits.beacon.locator.R;
import com.samebits.beacon.locator.model.DetectedBeacon;
import com.samebits.beacon.locator.util.AngleLowpassFilter;

import org.altbeacon.beacon.Beacon;

import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

public class RadarScanView extends View implements SensorEventListener {
    private final static int RADAR_RADIS_VISION_METERS = 15;
    private static String mMetricDisplayFormat = "%.0fm";
    private static String mEnglishDisplayFormat = "%.0fft";
    private static float METER_PER_FEET = 0.3048f;
    private static float FEET_PER_METER = 3.28084f;

    private float mDistanceRatio = 1.0f;
    private float[] mLastAccelerometer = new float[3];
    private float[] mLastMagnetometer = new float[3];
    private boolean mLastAccelerometerSet = false;
    private boolean mLastMagnetometerSet = false;
    private float[] mOrientation = new float[3];
    private AngleLowpassFilter angleLowpassFilter = new AngleLowpassFilter();
    private float mLast_bearing;
    private Context mContext;
    private WindowManager mWindowManager;
    private Map<String, DetectedBeacon> mBeacons = new LinkedHashMap();
    private boolean mHaveDetected = false;
    private TextView mInfoView;
    private Rect mTextBounds = new Rect();
    private Paint mGridPaint;
    private Paint mErasePaint;
    private Bitmap mBlip;
    private boolean mUseMetric;
    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint0;
    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint1;
    /**
     * Used to draw the animated ring that sweeps out from the center
     */
    private Paint mSweepPaint2;
    /**
     * Used to draw a beacon
     */
    private Paint mBeaconPaint;
    /**
     * Time in millis when the most recent sweep began
     */
    private long mSweepTime;
    /**
     * True if the sweep has not yet intersected the blip
     */
    private boolean mSweepBefore;
    /**
     * Time in millis when the sweep last crossed the blip
     */
    private long mBlipTime;

    public RadarScanView(Context context) {
        this(context, null);
    }

    public RadarScanView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RadarScanView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mContext = context;
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);

        // Paint used for the rings and ring text
        mGridPaint = new Paint();
        mGridPaint.setColor(getResources().getColor(R.color.hn_orange_lighter));

        mGridPaint.setAntiAlias(true);
        mGridPaint.setStyle(Style.STROKE);
        mGridPaint.setStrokeWidth(2.0f);
        mGridPaint.setTextSize(16.0f);
        mGridPaint.setTextAlign(Align.CENTER);

        // Paint used to erase the rectangle behind the ring text
        mErasePaint = new Paint();
        mErasePaint.setColor(getResources().getColor(R.color.white));
        //mErasePaint.setColor(getResources().getColor(R.color.hn_orange_lighter));

        mErasePaint.setStyle(Style.FILL);

        mBeaconPaint = new Paint();
        mBeaconPaint.setColor(getResources().getColor(R.color.white));
        mBeaconPaint.setAntiAlias(true);
        mBeaconPaint.setStyle(Style.FILL_AND_STROKE);

        // Outer ring of the sweep
        mSweepPaint0 = new Paint();
        mSweepPaint0.setColor(ContextCompat.getColor(context, R.color.hn_orange));
        mSweepPaint0.setAntiAlias(true);
        mSweepPaint0.setStyle(Style.STROKE);
        mSweepPaint0.setStrokeWidth(3f);

        // Middle ring of the sweep
        mSweepPaint1 = new Paint();
        mSweepPaint1.setColor(ContextCompat.getColor(context, R.color.hn_orange));

        mSweepPaint1.setAntiAlias(true);
        mSweepPaint1.setStyle(Style.STROKE);
        mSweepPaint1.setStrokeWidth(3f);

        // Inner ring of the sweep
        mSweepPaint2 = new Paint();
        mSweepPaint2.setColor(ContextCompat.getColor(context, R.color.hn_orange));

        mSweepPaint2.setAntiAlias(true);
        mSweepPaint2.setStyle(Style.STROKE);
        mSweepPaint2.setStrokeWidth(3f);

        mBlip = ((BitmapDrawable) ContextCompat.getDrawable(context, R.drawable.ic_location_on_black_24dp)).getBitmap();

    }

    /**
     * Sets the view that we will use to report distance
     *
     * @param t The text view used to report distance
     */
    public void setDistanceView(TextView t) {
        mInfoView = t;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int center = getWidth() / 2;
        int radius = center - 8;

        // Draw the rings
        final Paint gridPaint = mGridPaint;
        canvas.drawCircle(center, center, radius, gridPaint);
        canvas.drawCircle(center, center, radius * 3 / 4, gridPaint);
        canvas.drawCircle(center, center, radius >> 1, gridPaint);
        canvas.drawCircle(center, center, radius >> 2, gridPaint);

        int blipRadius = (int) (mDistanceRatio * radius);

        final long now = SystemClock.uptimeMillis();
        if (mSweepTime > 0) {
            // Draw the sweep. Radius is determined by how long ago it started
            long sweepDifference = now - mSweepTime;
            if (sweepDifference < 512L) {
                int sweepRadius = (int) (((radius + 6) * sweepDifference) >> 9);
                canvas.drawCircle(center, center, sweepRadius, mSweepPaint0);
                canvas.drawCircle(center, center, sweepRadius - 2, mSweepPaint1);
                canvas.drawCircle(center, center, sweepRadius - 4, mSweepPaint2);

                // Note when the sweep has passed the blip
                boolean before = sweepRadius < blipRadius;
                if (!before && mSweepBefore) {
                    mSweepBefore = false;
                    mBlipTime = now;
                }
            } else {
                mSweepTime = now + 1000;
                mSweepBefore = true;
            }
            postInvalidate();
        }

        // Draw horizontal and vertical lines
        canvas.drawLine(center, center - (radius >> 2) + 6, center, center - radius - 6, gridPaint);
        canvas.drawLine(center, center + (radius >> 2) - 6, center, center + radius + 6, gridPaint);
        canvas.drawLine(center - (radius >> 2) + 6, center, center - radius - 6, center, gridPaint);
        canvas.drawLine(center + (radius >> 2) - 6, center, center + radius + 6, center, gridPaint);

        // Draw X in the center of the screen
        canvas.drawLine(center - 4, center - 4, center + 4, center + 4, gridPaint);
        canvas.drawLine(center - 4, center + 4, center + 4, center - 4, gridPaint);

        if (mHaveDetected) {

            // Draw the blip. Alpha is based on how long ago the sweep crossed the blip
            long blipDifference = now - mBlipTime;
            gridPaint.setAlpha(255 - (int) ((128 * blipDifference) >> 10));

            double bearingToTarget = mLast_bearing;
            double drawingAngle = Math.toRadians(bearingToTarget) - (Math.PI / 2);
            float cos = (float) Math.cos(drawingAngle);
            float sin = (float) Math.sin(drawingAngle);

            addText(canvas, getRatioDistanceText(0.25f), center, center + (radius >> 2));
            addText(canvas, getRatioDistanceText(0.5f), center, center + (radius >> 1));
            addText(canvas, getRatioDistanceText(0.75f), center, center + radius * 3 / 4);
            addText(canvas, getRatioDistanceText(1.0f), center, center + radius);

            for (Map.Entry<String, DetectedBeacon> entry : mBeacons.entrySet()) {
                //String key = entry.getKey();
                DetectedBeacon dBeacon = entry.getValue();
                System.out.println("value: " + dBeacon);

                // drawing the beacon
                if (((System.currentTimeMillis() - dBeacon.getTimeLastSeen()) / 1000 < 5)) {
                    canvas.drawBitmap(mBlip, center + (cos * distanceToPix(dBeacon.getDistance())) - 8,
                            center + (sin * distanceToPix(dBeacon.getDistance())) - 8, gridPaint);
                }
            }

            gridPaint.setAlpha(255);
        }
    }

    private String getRatioDistanceText(float ringRation) {
        return new DecimalFormat("##0.00").format(RADAR_RADIS_VISION_METERS * mDistanceRatio * ringRation);
    }

    /**
     * max radar range is 15 meters
     *
     * @param distance
     * @return distance in px
     */
    private float distanceToPix(double distance) {
        int center = getWidth() / 2;
        int radius = center - 8;
        return Math.round((radius * distance) / RADAR_RADIS_VISION_METERS * mDistanceRatio);
    }

    private void addText(Canvas canvas, String str, int x, int y) {
        mGridPaint.getTextBounds(str, 0, str.length(), mTextBounds);
        mTextBounds.offset(x - (mTextBounds.width() >> 1), y);
        mTextBounds.inset(-2, -2);
        canvas.drawRect(mTextBounds, mErasePaint);
        canvas.drawText(str, x, y, mGridPaint);
    }


    /**
     * Update state to reflect whether we are using metric or standard units.
     *
     * @param useMetric True if the display should use metric units
     */
    public void setUseMetric(boolean useMetric) {
        mUseMetric = useMetric;
        if (mHaveDetected) {
            // TODO
        }
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        calcBearing(event);
    }

    private synchronized void calcBearing(SensorEvent event) {

        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, mLastAccelerometer, 0, event.values.length);
            mLastAccelerometerSet = true;
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, mLastMagnetometer, 0, event.values.length);
            mLastMagnetometerSet = true;
        }
        if (mLastAccelerometerSet && mLastMagnetometerSet) {

            /* Create rotation Matrix */
            float[] rotationMatrix = new float[9];
            if (SensorManager.getRotationMatrix(rotationMatrix, null,
                    mLastAccelerometer, mLastMagnetometer)) {
                SensorManager.getOrientation(rotationMatrix, mOrientation);

                float azimuthInRadians = mOrientation[0];

                angleLowpassFilter.add(azimuthInRadians);

                mLast_bearing = (float) (Math.toDegrees(angleLowpassFilter.average()) + 360) % 360;

                postInvalidate();

                //Log.d(Constants.TAG, "orientation bearing: " + mLast_bearing);

            }
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    private void insertBeacons(Collection<Beacon> beacons) {
        Iterator<Beacon> iterator = beacons.iterator();
        while (iterator.hasNext()) {
            DetectedBeacon dBeacon = new DetectedBeacon(iterator.next());
            dBeacon.setTimeLastSeen(System.currentTimeMillis());
            this.mBeacons.put(dBeacon.getId(), dBeacon);
        }
    }

    public void onDetectedBeacons(final Collection<Beacon> beacons) {

        insertBeacons(beacons);

        updateDistances();

        updateBeaconsInfo(beacons);

        invalidate();
    }

    private void updateBeaconsInfo(final Collection<Beacon> beacons) {
        mInfoView.setText(String.format(getResources().getString(R.string.text_scanner_found_beacons_size), beacons.size()));
    }

    /**
     * update on radar
     */
    private void updateDistances() {
        if (!mHaveDetected) {
            mHaveDetected = true;
        }
    }

    private void updateDistanceText(double distanceM) {
        String displayDistance;
        if (mUseMetric) {
            displayDistance = String.format(mMetricDisplayFormat, distanceM);
        } else {
            displayDistance = String.format(mEnglishDisplayFormat, distanceM * FEET_PER_METER);
        }
        mInfoView.setText(displayDistance);
    }

    /**
     * Turn on the sweep animation starting with the next draw
     */
    public void startSweep() {
        mInfoView.setText(R.string.text_scanning);
        mSweepTime = SystemClock.uptimeMillis();
        mSweepBefore = true;
    }

    /**
     * Turn off the sweep animation
     */
    public void stopSweep() {
        mSweepTime = 0L;
        mInfoView.setText("");
    }

}
 
Upvote 0

mike1967

Active Member
Licensed User
Longtime User
Tò test:
'Main Activity Module
Sub Process_Globals
    Private manager As BleManager2
    Private scanCallback As BleScanCallback
    Private beacons As Map
    Private SQL1 As SQL
End Sub

Sub Globals
    Private pnlRadar As Panel
    Private mBase As B4XView
    Private radar As RadarScanView
    Private mInfoView As Label
    Private tmr As Timer
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
    
    ' Initialize Bluetooth Manager
    manager.Initialize("manager")
    
    ' Initialize scan callback
    scanCallback.Initialize("scanCallback")
    
    ' Initialize beacon map
    beacons.Initialize
    
    ' Initialize RadarScanView
    radar.Initialize(pnlRadar)
    radar.StartSweep
    
    ' Initialize the SQLite database
    SQL1.Initialize(File.DirInternal, "beacon_data.db", True)
    SQL1.ExecNonQuery("CREATE TABLE IF NOT EXISTS BeaconData (ID INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT, Distance REAL, Time TEXT)")
    
    ' Initialize timer for smooth animation
    tmr.Initialize("tmr", 16) ' 60 FPS
    tmr.Enabled = True
    
    ' Start scanning for beacons
    StartScanning
End Sub

Sub StartScanning
    manager.Scan2(scanCallback, Null, True)
End Sub

Sub scanCallback_DeviceFound (Name As String, DeviceId As String, AdvertisingData As List, RSSI As Int)
    Log("Device found: " & Name & " - RSSI: " & RSSI)
    
    ' Calculate approximate distance
    Dim distance As Float = CalculateDistance(RSSI)
    
    If Not(beacons.ContainsKey(DeviceId)) Then
        ' New beacon detected
        Dim beacon As Map
        beacon.Initialize
        beacon.Put("Distance", distance)
        beacons.Put(DeviceId, beacon)
        
        ' Play a beep sound
        Dim beep As Beeper
        beep.Beep
        
        ' Show a notification
        ToastMessageShow($"New beacon detected: ${Name}"$, True)
        
        ' Save the beacon data
        SaveBeaconData(Name, distance)
    End If
    
    ' Update radar information
    radar.UpdateBeaconsInfo(beacons)
End Sub

Sub CalculateDistance(rssi As Int) As Float
    Dim txPower As Int = -59 ' Preset RSSI at 1 meter distance
    If rssi = 0 Then Return -1.0 ' Signal strength error
    
    Dim ratio As Float = rssi * 1.0 / txPower
    If ratio < 1.0 Then
        Return Power(ratio, 10)
    Else
        Return (0.89976) * Power(ratio, 7.7095) + 0.111
    End If
End Sub

Sub SaveBeaconData(Name As String, Distance As Float)
    Dim now As String = DateTime.Now
    SQL1.ExecNonQuery2($"INSERT INTO BeaconData (Name, Distance, Time) VALUES (?, ?, ?)"$, Array As Object(Name, Distance, now))
End Sub

Sub DrawRadar
    Dim center As Int = mBase.Width / 2
    Dim radius As Int = center - 8dip
    
    ' Disegna i cerchi concentrici e i beacon
    For Each dBeacon As Map In beacons.Values
        Dim distance As Float = dBeacon.Get("Distance")
        radar.DrawBeacon(center, distance)
    Next
    
    mBase.Invalidate
End Sub

Sub tmr_Tick
    DrawRadar
End Sub

' RadarScanView class implementation
' You should create a new class module and paste the below code

'Class RadarScanView
Sub Class_Globals
    Private mBase As B4XView
    Private mCanvas As B4XCanvas
    Private mBeacons As Map
    Private mSweepTime As Long
    Private mSweepBefore As Boolean
    Private mInfoView As B4XView
    Private mDistanceRatio As Float = 1.0f
    Private RADAR_RADIS_VISION_METERS As Int = 15
End Sub

Public Sub Initialize(Parent As B4XView)
    mBase = Parent
    mCanvas.Initialize(mBase)
    mBeacons.Initialize
End Sub

Public Sub StartSweep
    mSweepTime = DateTime.Now
    mSweepBefore = True
End Sub

Public Sub UpdateBeaconsInfo(beacons As Map)
    mBeacons = beacons
    mBase.Invalidate
End Sub

Public Sub DrawBeacon(center As Int, distance As Float)
    Dim radius As Float = distanceToPix(distance)
    Dim color As Int
    
    If distance < 5 Then
        color = Colors.Green
    ElseIf distance < 10 Then
        color = Colors.Yellow
    Else
        color = Colors.Red
    End If
    
    mCanvas.DrawCircle(center, center, radius, color, True, 2)
End Sub

Private Sub distanceToPix(distance As Float) As Float
    Dim center As Int = mBase.Width / 2
    Dim radius As Int = center - 8dip
    Return (radius * distance) / RADAR_RADIS_VISION_METERS * mDistanceRatio
End Sub

Public Sub Invalidate
    mCanvas.Invalidate
End Sub

Can some One test this code b4a? Thanks in advances
 
Upvote 0

mike1967

Active Member
Licensed User
Longtime User
Test this code:
Sub Process_Globals
    Private rp As RuntimePermissions
    Private sensorManager As SensorManager
    Private accelerometer As Object
    Private magnetometer As Object
    Private azimuth As Float
    Private orientationValues(3) As Float
    Private gravity(3) As Float
    Private geomagnetic(3) As Float
    Private beacons As Map
End Sub

Sub Globals
    Private pnlRadar As Panel
    Private lblDistance As Label
    Private tmrRefresh As Timer
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
    
    ' Inizializza sensori e mappe
    sensorManager.Initialize("sensorManager")
    accelerometer = sensorManager.GetDefaultSensor(sensorManager.TYPE_ACCELEROMETER)
    magnetometer = sensorManager.GetDefaultSensor(sensorManager.TYPE_MAGNETIC_FIELD)
    
    If accelerometer = Null Or magnetometer = Null Then
        ToastMessageShow("Sensori accelerometro o magnetometro non disponibili.", True)
        Return
    End If
    
    beacons.Initialize
    tmrRefresh.Initialize("tmrRefresh", 100)
    tmrRefresh.Enabled = True
    
    ' Verifica e richiede i permessi necessari
    CheckAndRequestPermissions
End Sub

Sub CheckAndRequestPermissions
    Dim permissions As List
    permissions.Initialize
    permissions.Add(rp.PERMISSION_ACCESS_FINE_LOCATION)
    permissions.Add(rp.PERMISSION_ACCESS_COARSE_LOCATION)
    permissions.Add("android.permission.BLUETOOTH_SCAN")
    permissions.Add("android.permission.BLUETOOTH_CONNECT")
    
    If rp.Check(permissions.Get(0)) = False Or rp.Check(permissions.Get(1)) = False Or rp.Check(permissions.Get(2)) = False Or rp.Check(permissions.Get(3)) = False Then
        rp.CheckAndRequest(permissions.Get(0))
    End If
End Sub

Sub Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result Then
        ' Permesso concesso
        Log(Permission & " concesso.")
        ' Avvia la scansione dei beacon
        StartScanning
    Else
        ' Permesso negato
        ToastMessageShow("Permesso negato: " & Permission, True)
        ' Mostra un dialogo o informazione per spiegare l'importanza del permesso
    End If
End Sub

Sub StartScanning
    ' Codice per iniziare la scansione dei beacon BLE
    ' Utilizzare una libreria come AltBeacon o simile
    ' Per esempio, puoi simulare l'aggiornamento dei beacon con un timer
End Sub

Sub tmrRefresh_Tick
    ' Aggiorna la visualizzazione del radar
    pnlRadar.Invalidate
End Sub

Sub pnlRadar_Draw (Canvas As Canvas)
    ' Disegna il radar
    Dim cx As Int = pnlRadar.Width / 2
    Dim cy As Int = pnlRadar.Height / 2
    Dim radius As Int = Min(cx, cy) - 10
    
    Canvas.DrawCircle(cx, cy, radius, Colors.Gray, True, 5dip)
    
    ' Disegna i beacon rilevati
    For Each beaconId As String In beacons.Keys
        Dim beacon As Map = beacons.Get(beaconId)
        Dim distance As Float = beacon.Get("distance")
        Dim bearing As Float = beacon.Get("bearing")
        
        Dim radian As Float = DegToRad(bearing - azimuth)
        Dim bx As Int = cx + Sin(radian) * distanceToPixels(distance, radius)
        Dim by As Int = cy - Cos(radian) * distanceToPixels(distance, radius)
        
        Canvas.DrawCircle(bx, by, 10dip, Colors.Blue, True, 2dip)
    Next
End Sub

Sub distanceToPixels(distance As Float, maxRadius As Int) As Int
    ' Scala la distanza in metri in base al raggio massimo del radar
    Dim maxDistance As Float = 15 ' Massimo 15 metri
    Return (distance / maxDistance) * maxRadius
End Sub

Sub Activity_Resume
    sensorManager.RegisterListener("sensor", accelerometer, sensorManager.SENSOR_DELAY_UI)
    sensorManager.RegisterListener("sensor", magnetometer, sensorManager.SENSOR_DELAY_UI)
End Sub

Sub Activity_Pause (UserClosed As Boolean)
    sensorManager.UnregisterListener("sensor")
End Sub

Sub SensorManager_SensorChanged (Sensor As Object, Values() As Float)
    If Sensor = accelerometer Then
        gravity = Values
    Else If Sensor = magnetometer Then
        geomagnetic = Values
    End If
    
    If gravity <> Null And geomagnetic <> Null Then
        Dim R(9) As Float
        Dim I(9) As Float
        If sensorManager.GetRotationMatrix(R, I, gravity, geomagnetic) Then
            sensorManager.GetOrientation(R, orientationValues)
            azimuth = orientationValues(0) * 180 / cPI
        End If
    End If
End Sub
 
Upvote 0
Top