/* * MagnetometerDemoScreen.java * * Copyright � 1998-2011 Research In Motion Limited * * 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. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.magnetometerdemo; import javax.microedition.location.Location; import javax.microedition.location.LocationException; import javax.microedition.location.LocationProvider; import javax.microedition.location.QualifiedCoordinates; import net.rim.device.api.gps.BlackBerryCriteria; import net.rim.device.api.gps.BlackBerryLocationProvider; import net.rim.device.api.location.GeomagneticField; import net.rim.device.api.system.Application; import net.rim.device.api.system.DeviceInfo; import net.rim.device.api.system.MagnetometerChannelConfig; import net.rim.device.api.system.MagnetometerData; import net.rim.device.api.system.MagnetometerListener; import net.rim.device.api.system.MagnetometerSensor; import net.rim.device.api.system.MagnetometerSensor.Channel; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.FieldChangeListener; import net.rim.device.api.ui.component.ButtonField; import net.rim.device.api.ui.component.LabelField; import net.rim.device.api.ui.component.SeparatorField; import net.rim.device.api.ui.component.TextField; import net.rim.device.api.ui.container.HorizontalFieldManager; import net.rim.device.api.ui.container.MainScreen; import net.rim.device.api.ui.container.VerticalFieldManager; /** * GUI screen for the Magnetometer Demo application */ public final class MagnetometerDemoScreen extends MainScreen implements FieldChangeListener, MagnetometerListener { private final TextField _headingField; private final TextField _angleField; private final TextField _quaternionField; private final TextField _strengthField; private final TextField _calibrationQualityField; private TextField _declinationField; private TextField _latitude; private TextField _longitude; private TextField _altitude; private TextField _locationInfo; private final TextField _snapshotAngleField; private final TextField _snapshotStrengthField; private final TextField _snapshotQualityField; private final TextField _snapshotHeadingField; private TextField _snapshotDeclinationField; private final TextField _snapshotQuaternionField; private final ButtonField _snapshotButton; private final ButtonField _calibrateButton; private HorizontalFieldManager _locationManager; private final VerticalFieldManager _streamingManager; private final VerticalFieldManager _snapshotManager; private final Channel _magnetometerChannel; private GeomagneticField _geoField; private final Application _app; private final float[] _quaternion; private final float[] _rotationMatrix; /** * Creates a new MagetometerDemoScreen object * * @param app * The application associated with this screen */ public MagnetometerDemoScreen(final Application app) { // Initialize UI setTitle("Magnetometer Demo"); _quaternion = new float[4]; _rotationMatrix = new float[9]; // Cache if the device is a simulator or not final boolean isSim = DeviceInfo.isSimulator(); // Add fields for displaying real time data _streamingManager = new VerticalFieldManager(); _headingField = new TextField("Heading: ", ""); _strengthField = new TextField("Field strength: ", ""); _angleField = new TextField("Angle: ", ""); _quaternionField = new TextField("Quaternion: ", ""); _calibrationQualityField = new TextField("Calibration quality: ", ""); _streamingManager.add(_headingField); _streamingManager.add(_angleField); _streamingManager.add(_quaternionField); _streamingManager.add(_strengthField); _streamingManager.add(_calibrationQualityField); _streamingManager.setPadding(4, 4, 4, 4); add(_streamingManager); // Add HorizontalFieldManager for buttons final HorizontalFieldManager buttonManager = new HorizontalFieldManager(FIELD_HCENTER); _snapshotButton = new ButtonField("Snapshot"); _calibrateButton = new ButtonField("Calibrate"); _snapshotButton.setChangeListener(this); _calibrateButton.setChangeListener(this); buttonManager.add(_snapshotButton); buttonManager.add(_calibrateButton); add(buttonManager); add(new SeparatorField()); // Add fields for displaying snapshot data _snapshotManager = new VerticalFieldManager(); _snapshotHeadingField = new TextField("Snapshot heading: ", ""); _snapshotAngleField = new TextField("Snapshot angle: ", ""); _snapshotStrengthField = new TextField("Snapshot field strength: ", ""); _snapshotQualityField = new TextField("Snapshot calibration quality: ", ""); _snapshotQuaternionField = new TextField("Snapshot quaternion: ", ""); _snapshotManager.add(_snapshotHeadingField); _snapshotManager.add(_snapshotAngleField); _snapshotManager.add(_snapshotQuaternionField); _snapshotManager.add(_snapshotStrengthField); _snapshotManager.add(_snapshotQualityField); _snapshotManager.setPadding(4, 4, 4, 4); add(_snapshotManager); add(new SeparatorField()); // The magnetometer channel will be opened with a sampling // frequency of 10 hertz and will be active only when the app // is in the foreground. This is the default configuration // that would be set automatically if the no arg constructor // for MagnetometerChannelConfig was used. final MagnetometerChannelConfig mcc = new MagnetometerChannelConfig(10, true, false); // Open up the magnetometer channel for reading data and // set this class as a MagnetometerListener. _magnetometerChannel = MagnetometerSensor.openChannel(app, mcc); _magnetometerChannel.addMagnetometerListener(this); // Cache the application for use later _app = app; // Start looking for the device's location and initialize // the GeomagneticField if on a real device. if (!isSim) { // Add the declination fields only if on a real device _declinationField = new TextField("Declination: ", ""); _streamingManager.add(_declinationField); _snapshotDeclinationField = new TextField("Snapshot declination: ", ""); _snapshotManager.add(_snapshotDeclinationField); // Add field for displaying location status _locationInfo = new TextField("Location status: ", ""); add(_locationInfo); // Add HorizontalFieldManager for the location information _locationManager = new HorizontalFieldManager(FIELD_HCENTER); add(_locationManager); _latitude = new TextField("Latitude : ", ""); _longitude = new TextField("Longitude : ", ""); _altitude = new TextField("Altitude : ", ""); // Initialize the GeomagneticField in a non-event thread final Thread initializer = new Thread(new Runnable() { public void run() { getLocation(); } }); initializer.start(); } } /** * Displays magnetometer data on the screen * * @param magData * magnetometerData * @param declination * Difference between magnetic north and true north at the * device's snapshot location */ public void printStreaming(final MagnetometerData magData, final float declination) { _angleField.setText(magData.getDirectionTop() + "�"); magData.getRotationMatrix(_rotationMatrix); getNormalizedQuaternion(_quaternion, _rotationMatrix); _quaternionField.setText("(" + _quaternion[0] + "," + _quaternion[1] + "," + _quaternion[2] + "," + _quaternion[3] + ")"); if (!Float.isNaN(declination)) { // If the declination is valid, print it to the screen. // It will become valid once the GeomagneticField has been // initialized. _declinationField.setText(declination + "�"); } _strengthField.setText(Float.toString(magData.getFieldStrength())); _calibrationQualityField.setText(Integer.toString(magData .getCalibrationQuality())); _headingField.setText(getHeadingName(MagnetometerData .getHeading(magData.getDirectionTop()))); } /** * Displays a snapshot of magnetometer data * * @param magData * magnetometerData * @param declination * Snapshot of the difference between magnetic north and true * north at teh device's snapshot location */ public void printSnapshot(final MagnetometerData magData, final float declination) { _snapshotAngleField.setText(magData.getDirectionTop() + "�"); magData.getRotationMatrix(_rotationMatrix); getNormalizedQuaternion(_quaternion, _rotationMatrix); _snapshotQuaternionField.setText("(" + _quaternion[0] + "," + _quaternion[1] + "," + _quaternion[2] + "," + _quaternion[3] + ")"); if (!Float.isNaN(declination)) { // If the declination is valid, print it to the screen. // It will become valid once the GeomagneticField has been // initialized. _declinationField.setText(declination + "�"); } _snapshotStrengthField.setText(Float.toString(magData .getFieldStrength())); _snapshotQualityField.setText(Integer.toString(magData .getCalibrationQuality())); _snapshotHeadingField.setText(getHeadingName(MagnetometerData .getHeading(magData.getDirectionTop()))); } /** * Attempt to get the devices current location. If a valid location is found * then the GeomagneticField will be initialized. Otherwise, the declination * will be NaN. */ public void getLocation() { try { final BlackBerryCriteria criteria = new BlackBerryCriteria(); criteria.enableGeolocationWithGPS(); _locationInfo.setText("Searching for location..."); final BlackBerryLocationProvider bbProvider = (BlackBerryLocationProvider) LocationProvider .getInstance(criteria); final Location loc = bbProvider.getLocation(-1); synchronized (_app.getAppEventLock()) { _locationInfo.setText("Location has been found"); } if (loc.isValid()) { final QualifiedCoordinates coordinates = loc.getQualifiedCoordinates(); final float altitude = coordinates.getAltitude(); final double latitude = coordinates.getLatitude(); final double longitude = coordinates.getLongitude(); // Set the text for the location information fields _altitude.setText(Float.toString(altitude)); _latitude.setText(Double.toString(latitude)); _longitude.setText(Double.toString(longitude)); synchronized (_app.getAppEventLock()) { // Add the location information fields to the manager _locationManager.add(_altitude); _locationManager.add(_latitude); _locationManager.add(_longitude); } // Initialize the GeomangeticField with the device's // latitude, longitude, and altitude. _geoField = new GeomagneticField(latitude, longitude, (int) altitude); } } catch (final InterruptedException iex) { synchronized (_app.getAppEventLock()) { add(new LabelField("Interrupted : " + iex.toString())); } } catch (final LocationException lex) { synchronized (_app.getAppEventLock()) { add(new LabelField("Location Error: " + lex.toString())); } } } /** * Retrieves the string version of the heading based on a 16 point compass * rose. * * @param headingCode * One of the integer constants representing a heading from the * list in the MagnetometerData class * @return String version of the heading (e.g. North, North North East, etc) */ public String getHeadingName(final int headingCode) { String headingName; switch (headingCode) { case MagnetometerData.MAGNETOMETER_HEADING_EAST: headingName = "East"; break; case MagnetometerData.MAGNETOMETER_HEADING_EAST_NORTH_EAST: headingName = "East North East"; break; case MagnetometerData.MAGNETOMETER_HEADING_EAST_SOUTH_EAST: headingName = "East South East"; break; case MagnetometerData.MAGNETOMETER_HEADING_NORTH: headingName = "North"; break; case MagnetometerData.MAGNETOMETER_HEADING_NORTH_EAST: headingName = "North East"; break; case MagnetometerData.MAGNETOMETER_HEADING_NORTH_NORTH_EAST: headingName = "North North East"; break; case MagnetometerData.MAGNETOMETER_HEADING_NORTH_NORTH_WEST: headingName = "North North West"; break; case MagnetometerData.MAGNETOMETER_HEADING_NORTH_WEST: headingName = "North West"; break; case MagnetometerData.MAGNETOMETER_HEADING_SOUTH: headingName = "South"; break; case MagnetometerData.MAGNETOMETER_HEADING_SOUTH_EAST: headingName = "South East"; break; case MagnetometerData.MAGNETOMETER_HEADING_SOUTH_SOUTH_EAST: headingName = "South South East"; break; case MagnetometerData.MAGNETOMETER_HEADING_SOUTH_SOUTH_WEST: headingName = "South South West"; break; case MagnetometerData.MAGNETOMETER_HEADING_SOUTH_WEST: headingName = "South West"; break; case MagnetometerData.MAGNETOMETER_HEADING_WEST: headingName = "West"; break; case MagnetometerData.MAGNETOMETER_HEADING_WEST_NORTH_WEST: headingName = "West North West"; break; case MagnetometerData.MAGNETOMETER_HEADING_WEST_SOUTH_WEST: headingName = "West South West"; break; default: headingName = Integer.toString(headingCode); break; } return headingName; } /** * @see net.rim.device.api.ui.container.MainScreen#onSavePrompt() */ protected boolean onSavePrompt() { // Prevent the save dialog from being displayed return true; } /** * @see net.rim.device.api.ui.Screen#close() */ public void close() { // Close the magnetometer channel and deregister listener _magnetometerChannel.close(); _magnetometerChannel.removeMagnetometerListener(this); super.close(); } /** * @see net.rim.device.api.ui.FieldChangeListener#fieldChanged(Field, int) */ public void fieldChanged(final Field field, final int context) { if (field == _snapshotButton) { doSnapshot(); } else if (field == _calibrateButton) { calibrate(); } } /** * @see net.rim.device.api.system.MagnetometerListener#onData(MagnetometerData) */ public void onData(final MagnetometerData magData) { // Check for calibration if (_magnetometerChannel.isCalibrating()) { // Stop calibrating when the desired quality achieved if (magData.getCalibrationQuality() == MagnetometerData.MAGNETOMETER_QUALITY_HIGH) { try { _magnetometerChannel.stopCalibration(); } catch (final Throwable t) { } } } float declination; try { // Get the declination from the GeomagneticField declination = _geoField.getDeclination(); } catch (final Exception e) { // Indicate that we don't have a valid declination value yet declination = Float.NaN; } // Print the data on the display printStreaming(magData, declination); } /** * Retrieves a snapshot of data from the magnetometer and prints it to the * display. */ public void doSnapshot() { float declination; try { // Get the declination from the GeomagneticField declination = _geoField.getDeclination(); } catch (final Exception e) { // Indicate that we don't have a valid declination value yet declination = Float.NaN; } printSnapshot(_magnetometerChannel.getData(), declination); } /** * Starts the magnetometer calibration process */ public void calibrate() { try { _magnetometerChannel.startCalibration(); } catch (final Exception e) { MagnetometerDemo.errorDialog("Error calibrating: " + e.toString()); } } /** * Calculates a normalized quaternion from a rotation matrix * * @param q * Stores the normalized quaternion * @param rm * Rotation matrix */ public static boolean getNormalizedQuaternion(final float[] q, final float[] rm) { float Rx, Ry, Rz, Ux, Uy, Uz, Bx, By, Bz; if (rm.length == 9) { Rx = rm[0]; Ry = rm[1]; Rz = rm[2]; Ux = rm[3]; Uy = rm[4]; Uz = rm[5]; Bx = rm[6]; By = rm[7]; Bz = rm[8]; } else if (rm.length == 16) { Rx = rm[0]; Ry = rm[1]; Rz = rm[2]; Ux = rm[4]; Uy = rm[5]; Uz = rm[6]; Bx = rm[8]; By = rm[9]; Bz = rm[10]; } else { return false; } final float qw = (float) Math.sqrt(clamp(Rx + Uy + Bz + 1) * 0.25f); float qx = (float) Math.sqrt(clamp(Rx - Uy - Bz + 1) * 0.25f); float qy = (float) Math.sqrt(clamp(-Rx + Uy - Bz + 1) * 0.25f); float qz = (float) Math.sqrt(clamp(-Rx - Uy + Bz + 1) * 0.25f); qx = copySign(qx, By - Uz); qy = copySign(qy, Rz - Bx); qz = copySign(qz, Ux - Ry); // [w, x, y, z] q[0] = qw; q[1] = qx; q[2] = qy; q[3] = qz; return true; } /** * Clamp a float value so that it's never negative */ private static float clamp(final float f) { return f < 0 ? 0 : f; } /** * Assigns the sign (positive/negative) to magitude * * @param magnitude * Magnitude to assume the provided sign * @param sign * The sign to be applied * @return Magnitude with sign provided */ private static float copySign(final float magnitude, final float sign) { int magnitudeBits = Float.floatToIntBits(magnitude); final int signBits = Float.floatToIntBits(sign); magnitudeBits = magnitudeBits & ~0x80000000 | signBits & 0x80000000; return Float.intBitsToFloat(magnitudeBits); } }