diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 00000000..44943151 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d..00000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1c3392df..40325a72 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,10 +5,13 @@ - + + + + - + + + \ No newline at end of file diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java new file mode 100644 index 00000000..6faf4e85 --- /dev/null +++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationData.java @@ -0,0 +1,46 @@ +package com.schneewittchen.rosandroid.widgets.location; + +import com.schneewittchen.rosandroid.model.repositories.rosRepo.node.BaseData; +import com.schneewittchen.rosandroid.model.entities.widgets.BaseEntity; + +import org.ros.internal.message.Message; +import org.ros.node.topic.Publisher; + +import sensor_msgs.NavSatFix; + + +/** + * TODO: Description + * + * @author Gennaro Raiola + * @version 0.0.1 + * @created on 19.11.22 + */ + +public class LocationData extends BaseData { + + public double latitude; + public double longitude; + public double altitude; + public String type; + + public LocationData(double latitude, double longitude, double altitude, String type) { + this.latitude = latitude; + this.longitude = longitude; + this.altitude = altitude; + this.type = type; + } + + @Override + public Message toRosMessage(Publisher publisher, BaseEntity widget) { + + sensor_msgs.NavSatFix message = (NavSatFix) publisher.newMessage(); + + message.getHeader().setFrameId(type); + message.setLatitude(latitude); + message.setLongitude(longitude); + message.setAltitude(altitude); + + return message; + } +} diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java new file mode 100644 index 00000000..5d1a020a --- /dev/null +++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationDetailVH.java @@ -0,0 +1,66 @@ +package com.schneewittchen.rosandroid.widgets.location; + +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; + +import com.schneewittchen.rosandroid.R; +import com.schneewittchen.rosandroid.model.entities.widgets.BaseEntity; +import com.schneewittchen.rosandroid.ui.views.details.PublisherWidgetViewHolder; +import com.schneewittchen.rosandroid.utility.Utils; + +import java.util.Collections; +import java.util.List; + +import sensor_msgs.NavSatFix; + +/** + * TODO: Description + * + * @author Gennaro Raiola + * @version 0.0.1 + * @created on 19.11.22 + */ +public class LocationDetailVH extends PublisherWidgetViewHolder { + + private EditText textText; + private Spinner rotationSpinner; + private ArrayAdapter rotationAdapter; + + @Override + public void initView(View view) { + textText = view.findViewById(R.id.btnTextTypeText); + rotationSpinner = view.findViewById(R.id.btnTextRotation); + + // Init spinner + rotationAdapter = ArrayAdapter.createFromResource(view.getContext(), + R.array.button_rotation, android.R.layout.simple_spinner_dropdown_item); + + rotationSpinner.setAdapter(rotationAdapter); + } + + @Override + public void bindEntity(BaseEntity entity) { + LocationEntity locationEntity = (LocationEntity) entity; + + textText.setText(locationEntity.text); + String degrees = Utils.numberToDegrees(locationEntity.rotation); + rotationSpinner.setSelection(rotationAdapter.getPosition(degrees)); + } + + @Override + public void updateEntity(BaseEntity entity) { + LocationEntity locationEntity = (LocationEntity) entity; + + locationEntity.text = textText.getText().toString(); + String degrees = rotationSpinner.getSelectedItem().toString(); + locationEntity.rotation = Utils.degreesToNumber(degrees); + } + + @Override + public List getTopicTypes() { + return Collections.singletonList(NavSatFix._TYPE); + } + +} diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java new file mode 100644 index 00000000..b1adeae3 --- /dev/null +++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationEntity.java @@ -0,0 +1,32 @@ +package com.schneewittchen.rosandroid.widgets.location; + +import com.schneewittchen.rosandroid.model.entities.widgets.PublisherWidgetEntity; +import com.schneewittchen.rosandroid.model.repositories.rosRepo.message.Topic; + +import sensor_msgs.NavSatFix; + +/** + * TODO: Description + * + * @author Gennaro Raiola + * @version 0.0.1 + * @created on 19.11.22 + */ + +public class LocationEntity extends PublisherWidgetEntity { + + public String text; + public int rotation; + public boolean buttonPressed; + + public LocationEntity() { + this.width = 4; + this.height = 4; + this.topic = new Topic("location", NavSatFix._TYPE); + this.immediatePublish = true; + //this.publishRate = 20f; + this.text = "Publish phone's location to ROS"; + this.rotation = 0; + this.buttonPressed = false; + } +} diff --git a/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java new file mode 100644 index 00000000..d8dd22af --- /dev/null +++ b/app/src/main/java/com/schneewittchen/rosandroid/widgets/location/LocationView.java @@ -0,0 +1,227 @@ +package com.schneewittchen.rosandroid.widgets.location; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.location.LocationListener; +import android.location.LocationManager; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.schneewittchen.rosandroid.R; +import com.schneewittchen.rosandroid.ui.views.widgets.PublisherWidgetView; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.location.Location; +import android.util.Log; + +import java.util.ArrayList; + + +/** + * TODO: Description + * + * @author Gennaro Raiola + * @version 0.0.1 + * @created on 19.11.22 + */ +public class LocationView extends PublisherWidgetView { + + public static final String TAG = LocationView.class.getSimpleName(); + + Context context; + + Paint buttonPaint; + TextPaint textPaint; + StaticLayout staticLayout; + + protected LocationManager locationManager; + double gpsLatitude; + double gpsLongitude; + double gpsAltitude; + double networkLatitude; + double networkLongitude; + double networkAltitude; + long gpsTime = 0; + long networkTime = 0; + + LocationListener locationListenerGps = new LocationListener() { + public void onLocationChanged(Location location) { + gpsTime = location.getTime(); + gpsLatitude = location.getLatitude(); + gpsLongitude = location.getLongitude(); + gpsAltitude = location.getAltitude(); + Log.d(TAG,"GPS - Longitude: "+ gpsLongitude +" Latitude: " + gpsLatitude +" Altitude: " + gpsAltitude); + publishCoordinates(); + } + }; + + LocationListener locationListenerNetwork = new LocationListener() { + public void onLocationChanged(Location location) { + networkTime = location.getTime(); + networkLatitude = location.getLatitude(); + networkLongitude = location.getLongitude(); + networkAltitude = location.getAltitude(); + Log.d(TAG,"NETWORK - Longitude: "+networkLongitude+" Latitude: " + networkLatitude +" Altitude: " + networkAltitude); + publishCoordinates(); + } + }; + + public LocationView(Context context) { + super(context); + this.context = context; + init(); + } + + public LocationView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.context = context; + init(); + } + + private void init() { + + buttonPaint = new Paint(); + buttonPaint.setColor(getResources().getColor(R.color.colorPrimary)); + buttonPaint.setStyle(Paint.Style.FILL_AND_STROKE); + + textPaint = new TextPaint(); + textPaint.setColor(Color.BLACK); + textPaint.setStyle(Paint.Style.FILL_AND_STROKE); + textPaint.setTextSize(26 * getResources().getDisplayMetrics().density); + + locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { + requestPermissionsIfNecessary(new String[]{ + Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION + }); + } + + final boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + final boolean networkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); + + if(gpsEnabled) + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps); + if (networkEnabled) + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork); + + if(!gpsEnabled && !networkEnabled) + { + Log.e(TAG,"gps and network locations are not enabled!"); + return; + } + } + + private void requestPermissionsIfNecessary(String[] permissions) { + ArrayList permissionsToRequest = new ArrayList<>(); + for (String permission : permissions) { + if (ContextCompat.checkSelfPermission(this.getContext(), permission) + != PackageManager.PERMISSION_GRANTED) { + // Permission is not granted + permissionsToRequest.add(permission); + } + } + } + + private void changeState(boolean pressed) { + LocationEntity entity = (LocationEntity) widgetEntity; + entity.buttonPressed = pressed; + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (this.editMode) { + return super.onTouchEvent(event); + } + + LocationEntity entity = (LocationEntity) widgetEntity; + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if(entity.buttonPressed) + changeState(false); + else + changeState(true); + break; + default: + return false; + } + + return true; + } + + @Override + public void onDraw(Canvas canvas) { + float width = getWidth(); + float height = getHeight(); + float textLayoutWidth = width; + + LocationEntity entity = (LocationEntity) widgetEntity; + + if (entity.rotation == 90 || entity.rotation == 270) { + textLayoutWidth = height; + } + + if(!entity.buttonPressed) + buttonPaint.setColor(getResources().getColor(R.color.colorPrimary)); + else + buttonPaint.setColor(getResources().getColor(R.color.color_attention)); + + canvas.drawRect(new Rect(0, 0, (int) width, (int) height), buttonPaint); + + staticLayout = new StaticLayout(entity.text, + textPaint, + (int) textLayoutWidth, + Layout.Alignment.ALIGN_CENTER, + 1.0f, + 0, + false); + canvas.save(); + canvas.rotate(entity.rotation, width / 2, height / 2); + canvas.translate(((width / 2) - staticLayout.getWidth() / 2), height / 2 - staticLayout.getHeight() / 2); + staticLayout.draw(canvas); + canvas.restore(); + } + + public void publishCoordinates() { + + LocationEntity entity = (LocationEntity) widgetEntity; + + if(entity.buttonPressed) { + double latitude; + double longitude; + double altitude; + String type; + Log.d(TAG, "time network " + networkTime + " time GPS "+ gpsTime); + if( 0 < gpsTime - networkTime) { + latitude = gpsLatitude; + longitude = gpsLongitude; + altitude = gpsAltitude; + type = "GPS"; + } + else + { + latitude = networkLatitude; + longitude = networkLongitude; + altitude = networkAltitude; + type = "NETWORK"; + } + Log.d(TAG, type + " Longitude: " + longitude + " Latitude: " + latitude + " Altitude " + altitude); + this.publishViewData(new LocationData(latitude, longitude, altitude, type)); + } + } + +} diff --git a/app/src/main/res/layout/widget_detail_location.xml b/app/src/main/res/layout/widget_detail_location.xml new file mode 100644 index 00000000..bddc5e46 --- /dev/null +++ b/app/src/main/res/layout/widget_detail_location.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/widgets.xml b/app/src/main/res/values/widgets.xml index 93278309..67b0fd68 100644 --- a/app/src/main/res/values/widgets.xml +++ b/app/src/main/res/values/widgets.xml @@ -12,6 +12,7 @@ Logger RqtPlot Viz2D + Location @@ -99,6 +100,18 @@ You can zoom in and out by pinching 2 or more fingers together or apart. + + + The Location node is designed to publish the phone GPS and or network coordinates to ROS as a sensor_msgs/NavSatFix message. + The publisher can be started or stopped by pressing its button in the Viz tab. + + + + + 90° + 180° + 270° + @@ -145,4 +158,4 @@ 2D View of multiple layers. - \ No newline at end of file + diff --git a/jcraft/src/main/AndroidManifest.xml b/jcraft/src/main/AndroidManifest.xml index 1438840c..29a0cd81 100644 --- a/jcraft/src/main/AndroidManifest.xml +++ b/jcraft/src/main/AndroidManifest.xml @@ -1,2 +1,8 @@ + package="com.jcraft" > + + + + + +