Part 1 - GPS Project Overview and Required Hardware - GPS and USB
Part 2 - Install Serial Communication Library into RICOH THETA
This is a machine translation of a Japanese article by KA-2. The original article is here.
I will put a set of files of plugins created this time here . I touched two files in HttpConnector.java
, MainActivity.java
, among the files in the THETA plug-in SDK based on it. I will explain the code which added and modified in each file in the following into the block separately.
HttpConnector.java
Processing to execute webAPI from the plugin is summarized. It is like a parts group. In addition to manipulating THETA from the outside, webAPI can also be executed from a plugin. Please refer here for the command specification .
We added a command execution part necessary for making this plugin. It is better to review the structure of the program more carefully , but this time we are adding a new process with Copy & Minor modification, and there are many duplicated codes.
Code for HttpConnector.java
/ **
* Set GPS (GNSS) Info
*
* @ return Error message (null is returned if successful)
* /
Public String SetGpsInfo ( String Lat , String Lng , String Height , String DateTimeZone ) {
HttpURLConnection PostConnection = CreateHttpConnection ( "POST" , "/ Osc / Commands / Execute" );
JSONObject Input = New JSONObject ();
String ResponseData ;
String errorMessage = null ;
InputStream is = null;
Try {
// Send HTTP POST
Input . Put ( "Name" , "Camera.SetOptions" );
JSONObject Parameters = New JSONObject ();
JSONObject Options = New JSONObject ();
JSONObject GpsInfo = New JSONObject ();
GpsInfo . Put ( "Lat" , Lat );
GpsInfo . Put ( "Lng" , Lng );
GpsInfo . Put ( "_Altitude" , Height );
GpsInfo . Put ( "_DateTimeZone" , DateTimeZone );
GpsInfo . Put ( "_Datum " , " WGS 84 " );
Options . Put ( "GpsInfo" , GpsInfo );
Parameters . Put ( "Options" , Options );
Input . Put ( "Parameters" , Parameters );
OutputStream os = PostConnection . GetOutputStream ();
os . Write ( input The . ToString . () GetBytes ());
PostConnection . The connect ();
os . Flush ();
os . Close The ();
is = postConnection . getInputStream ();
responseData = InputStreamToString ( is );
// parse JSON data
JSONObject output = new JSONObject ( responseData );
String status = output . getString ( "state" );
an if ( status . the equals ( "error" )) {
JSONObject errors The = output . GetJSONObject ( "error" );
errorMessage = errors The . getString ( "message" );
}
} catch ( IOException e ) {
e . printStackTrace ();
errorMessage = e . toString ();
InputStream es = postConnection .GetErrorStream ();
Try {
If ( Es =! Null ) {
String ErrorData = InputStreamToString ( Es );
JSONObject Output = New JSONObject ( ErrorData );
JSONObject Errors = Output . GetJSONObject ( "Error" );
ErrorMessage = Errors . GetString ( " message " );
}
} catch ( IOException e1 ) {
e1 . printStackTrace ();
} catch ( JSONException e1 ) {
e1 . printStackTrace ();
} a finally {
an if ( es =! null ) {
the try {
es . close The ();
} catch ( IOException e1 ) {
e1 . printStackTrace ();
}
}
}
} catch ( JSONException E ) {
E . PrintStackTrace ();
ErrorMessage = E . ToString ();
} Finally {
If ( Is =! Null ) {
Try {
Is . Close ();
} Catch ( IOException E ) {
E . PrintStackTrace ();
}
}
}
return errorMessage ;
}
Processing of JSONObject will be described afterward, and processing of Exception system will be omitted.
Code to execute settings related to interval shooting
/ **
* Set Interval Parameters
*
* @ return Error message (null is returned if successful)
* /
public String setIntervalParam ( int capInterval , int capNumber ) {
HttpURLConnection postConnection = createHttpConnection ( "POST" , "/ osc / commands / execute" );
JSONObject input = new JSONObject ();
String responseData ;
String errorMessage = null ;
InputStream is = null ;
try {
// send HTTP POST
input . put ( "name" , "camera.setOptions" );
JSONObject parameters = new JSONObject ();
JSONObject options = new JSONObject ();
Options . Put ( "CaptureInterval" , String . valueOf ( CapInterval ));
Options . Put ( "CaptureNumber" , String . valueOf ( CapNumber ));
Parameters . Put ( "Options" , Options );
Input . Put ( "Parameters" , parameters );
OutputStream os = PostConnection . GetOutputStream ();
os . Write ( input The . ToString . () GetBytes ());
PostConnection . The connect ();
os . Flush ();
os . Close The ();
is = postConnection . getInputStream ();
responseData = InputStreamToString ( is );
// parse JSON data
JSONObject output = new JSONObject ( responseData );
String status = output . getString ( "state" );
an if ( status . the equals ( "error" )) {
JSONObject errors The = output . GetJSONObject ( "error" );
errorMessage = errors The . getString ( "message" );
}
} catch ( IOException e ) {
: approximately
}
return errorMessage ;
}
Code for executing interval shooting start
/ **
* startCapture
*
* @ return Error message (null is returned if successful)
* /
Public String StartCapture ( String Mode ) {
HttpURLConnection PostConnection = CreateHttpConnection ( "POST" , "/ Osc / Commands / Execute" );
JSONObject Input = New JSONObject ();
String ResponseData ;
String ErrorMessage = Null ;
InputStream Is = Null ;
try {
// send HTTP POST
input . put ( "name" , "camera.startCapture" );
JSONObject parameters = new JSONObject ();
parameters . put ( "_mode" , mode );
input . put ( "parameters" , parameters );
OutputStream os = PostConnection . GetOutputStream ();
os . Write ( input The . ToString . () GetBytes ());
PostConnection . The connect ();
os . Flush ();
os . Close The ();
is = postConnection . getInputStream ();
responseData = InputStreamToString ( is );
// parse JSON data
JSONObject output = new JSONObject ( responseData );
String status = output . getString ( "state" );
an if ( status . the equals ( "error" )) {
JSONObject errors The = output . GetJSONObject ( "error" );
errorMessage = errors The . getString ( "message" );
}
} catch ( IOException e ) {
: approximately
}
return errorMessage ;
}
Code to execute interval stop shooting
/ **
* stopCapture
*
* @ return Error message (null is returned if successful)
* /
Public String StopCapture () {
HttpURLConnection PostConnection = CreateHttpConnection ( "POST" , "/ Osc / Commands / Execute" );
JSONObject Input = New JSONObject ();
String ResponseData ;
String ErrorMessage = Null ;
InputStream Is = Null ;
try {
// send HTTP POST
input . put ( "name" , "camera.stopCapture" );
OutputStream os = PostConnection . GetOutputStream ();
os . Write ( input The . ToString . () GetBytes ());
PostConnection . The connect ();
os . Flush ();
os . Close The ();
is = postConnection . getInputStream ();
responseData = InputStreamToString ( is );
// parse JSON data
JSONObject output = new JSONObject ( responseData );
String status = output . getString ( "state" );
an if ( status . the equals ( "error" )) {
JSONObject errors The = output . GetJSONObject ( "error" );
errorMessage = errors The . getString ( "message" );
}
} catch ( IOException e ) {
: approximately
}
return errorMessage ;
}
Code for checking the state of interval shooting
/ **
* Acquire device status
*
* @return _captureStatus String
* /
public String getCaptureStatus () {
HttpURLConnection postConnection = createHttpConnection ( "POST" , "/ osc / state" );
String responseData ;
String capStat = "" ;
InputStream is = null ;
try {
// send HTTP POST
postConnection . connect ();
is = postConnection . getInputStream ();
responseData = InputStreamToString ( is );
Parse JSON data //
JSONObject output = new new JSONObject ( responseData );
MFingerPrint = output . GetString ( "fingerprint" );
JSONObject status = output . GetJSONObject ( "state" );
CapStat = status . GetString ( "_CaptureStatus" );
} catch ( IOException e ) {
: Abbreviation (It differs somewhat from the processing described above. Please download the complete set of projects and confirm it.)
}
return capStat ;
MainActivity.java
The main body of processing is here.
MainActivity is like a screen in smartphone application.
Although it dispatches processing when receiving an event of member operation, it can not directly perform serial communication or perform HTTP communication.
In this time, we decided to divide threads for serial communication at plug-in startup (strictly onResume).
In the thread, polling reception from the GPS / GNSS receiver and data for updating the position information inside THETA are prepared, the data is edited and set up to the inside.
Moreover, although there is a feeling of a backing up a little, I also perform the start / stop processing etc. of the interval shooting buffered at the reception of member operation.
Only the task of executing takePicture already described in the base SDK is handled, but it is safer to combine the internal web API command sender into one thread / task.
Although it is from the program structure talk, as a bonus function, processing to save the raw data of the NMEA 0183 format received from the GPS / GNSS receiver is also described. This is because THETA V becomes “GPS / GNSS logger that functions without photography”. It was convenient for debugging.
Definition of variables
Variables necessary for processing moromoro are arranged near the beginning of MainActivity.
// serial communication related
private boolean mFinished ;
private UsbSerialPort port ;
// Grant permission to USB device
PendingIntent mPermissionIntent ;
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION" ;
// Interval shooting related
private boolean isIntervalMode = false ;
private boolean isIntervalStat = false ;
private boolean sendReq = false ;
Processing related to operation acceptance (onKeyDown (), onKeyUp ())
OnKeyDown () has been modified as follows to accept normal / interval shooting switching operation and interval shooting start / stop operation.
@Override
public void onKeyDown ( int keyCode , KeyEvent event ) {
// --------------- customized code ---------------
if ( keyCode == KeyReceiver . KEYCODE_CAMERA ) {
/ *
* To take a static picture, use the takePicture method.
* You can receive a fileUrl of the static picture in the callback.
* /
An if ( IsIntervalMode ) {
an if ( IsIntervalStat ) {
IsIntervalStat = false ;
} the else {
IsIntervalStat = true ;
}
SENDREQ = true ;
} the else {
new new TakePictureTask ( MTakePictureTaskCallback .) The execute ();
}
} the else an if ( keyCode == KeyReceiver . KEYCODE_MEDIA_RECORD ) {
an if ( IsIntervalMode ) {
an if ( IsIntervalStat ) {
IsIntervalStat = false ;
SENDREQ = true ;
}
IsIntervalMode = false ;
NotificationLedHide ( LedTarget . LEDs 7 );
} the else {
IsIntervalMode = true ;
NotificationLedShow ( LedTarget . LEDs 7 );
}
}
// - ----------------------------------------
}
OnKeyUp () was left untouched.
@Override
public void onKeyUp ( int keyCode , KeyEvent event ) {
/ **
* You can control the LED of the camera.
* It is possible to change the way of lighting, the cycle of blinking, the color of light emission.
* Light emitting color can be changed only LED 3.
* / / /
Please remove code which was attached to SDK because it is unnecessary.
//notificationLedBlink(LedTarget.LED3, LedColor.BLUE, 1000);
}
Processing onResume ()
Processing related to serial communication initialization is described in onResume ().
The following three lines concerning the initialization of USB serial communication at the beginning are the arguments given from the argument “Process corresponding to vendor ID, product ID, USB device class” that we had prepared, then use that list after adding it to the device list. I will.
final ProbeTable probeTable = UsbSerialProber . getDefaultProbeTable ();
probeTable . addProduct ( 0x1546 , 0x01a7 , CdcAcmSerialDriver . class );
List < UsbSerialDriver > usb = new UsbSerialProber ( probeTable ). findAllDrivers ( manager );
This is a modified version of the following code in README.md “4. Use it! Example code snippet:” of library " usb-serial-for-android ".
List < UsbSerialDriver > Usb = UsbSerialProber . GetDefaultProber . () FindAllDrivers ( Manager );
By doing this, when you use a device which is not defined in the library beforehand, you do not need to put your hand in the source code of the library side. To refer to the
library itself, please refer to this site and so on.
The baud rate setting for serial communication is the following line near the end of onResume ().
When changing the baud rate setting of the GPS / GNSS receiver, or when using different baud rates with other serial communication devices, please indicate the number appropriate for the device.
port . setParameters ( 9600 , 8 , UsbSerialPort . STOPBITS_ 1 , UsbSerialPort . PARITY_NONE );
Below, the entire code of onResume () is posted.
@Override
protected void onResume () {
Log . D ( "GNSS" , "M: onResume ()" );
// --------------- added code ---------------
mFinished = true ;
Find All Available // Drivers From Attached Devices.
UsbManager Manager = ( UsbManager ) GetSystemService ( Context . USB_SERVICE );
. // List <UsbSerialDriver> Usb = UsbSerialProber.GetDefaultProber () FindAllDrivers (Manager);
Final ProbeTable ProbeTable = UsbSerialProber . GetDefaultProbeTable ( );
probeTable . addProduct ( 0x1546 , 0x01a7 , CdcAcmSerialDriver . class );
List < UsbSerialDriver > usb = new UsbSerialProber ( probeTable ). findAllDrivers ( manager );
//
Check the number of devices recognized for debugging int usbNum = usb . Size ();
Log . D ( "GNSS" , "usb num =" + usbNum );
If ( Usb . IsEmpty ()) {
Log . D ( "GNSS" , "Usb Device Is Not Connect." );
NotificationLedBlink ( LedTarget . LED3 , LedColor . RED , 1000 );
// Return;
// Port = Null;
} else {
// Open a connection to the first available driver.
UsbSerialDriver driver = usb . get ( 0 );
// For granting permission to the USB device (Even if you thru the device, only giving a chance at the time of launching the application it is not necessary to give it a chance.)
MPermissionIntent = PendingIntent . GetBroadcast ( this , 0 , new Intent ( ACTION_USB_PERMISSION ), 0 );
Manager . RequestPermission ( Driver . getDevice () , MPermissionIntent );
UsbDeviceConnection connection = manager . OpenDevice ( driver . GetDevice ());
if ( connection == null ) {
// You probably need to call UsbManager.requestPermission (driver.getDevice (), ..)
// even after giving permission , become null that it is power Off-> on the left USB device is connected ... if able to re stabbed OK
NotificationLedBlink ( LedTarget . LED3 , LedColor . YELLOW , 500 );
Log . D ( "GNSS" , "M: Can not open usb device. \ N " );
port = null ;
} else {
port = driver . getPorts (). get ( 0 );
the try {
port . open ( connection );
//Port.SetParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
port . setParameters ( 9600 , 8 , UsbSerialPort . STOPBITS_1 , UsbSerialPort . PARITY_NONE );
mFinished = false ;
start_read_thread ();
} Catch ( IOException e ) {
. // Deal with error
e . PrintStackTrace ();
Log . D ( "GNSS" , "M: IOException" );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . YELLOW , 1000 );
// return ;
} finally {
Log . d ( "GNSS" , "M: finally" );
}
}
}
// -----------------------------------------
super . onResume ();
}
Process of onPause ()
OnPause () describes the termination of serial communication, and the cleaning-up completion process of the plug-in is described.
@Override
protected void onPause () {
// Do end processing
// close ();
// --------------- added code ---------------
Log . d ( "GNSS" , "M: on Pause ()" ) ;
// clean up the case of the interval shooting Allowed
If ( IsIntervalMode ) {
NotificationLedHide ( LedTarget . LED7 );
If ( IsIntervalStat ) {
IsIntervalStat = False ;
SendReq = True ;
Try {
// thread end waiting for just in case
thread . Sleep ( 20 );
} catch (InterruptedException e ) {
. // Deal with error
e . PrintStackTrace ();
Log . D ( "GNSS" , "T: InterruptedException" );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . YELLOW , 200 );
}
}
}
// Instructions to end the thread. I have not waited for completion.
mFinished = true ;
// Do not
close if you can not open the serial communication. If ( port ! = Null ) {
try {
port . Close ();
Log . D ( "GNSS" , "M: onDestroy () port.close ) " );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . BLUE , 200 );
} catch ( IOException e ) {
Log . d ( " GNSS " , " M: onDestroy () IOException " );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . RED , 300 );
}
} the else {
Log . d ( "GNSS" , "M: port = null \ n" );
}
// ----------- ------------------------------
super . onPause ();
}
Processing of added thread
serial reception, positioning log (TXT format) saving, interpretation of NMEA 0183, editing and sending of gpsInfo
It is a function that only developers can use, but processing is also made to create a file in the application area each time the plug-in is activated and save the raw data of the received positioning information.
The number of this file is restricted so that up to 20 files are saved in “int maxFileNum = 20;”. Please customize as necessary.
Although the extension of the file is “.txt”, setting it as “.ubx” makes it easier to handle a little from u-blox’s tool described later.
Where interval shooting is to be started, use the following code to set the shooting interval to 4 seconds (the shortest shooting interval of THETA V, the camera side will automatically act as the shortest shooting interval automatically if the shutter speed is longer than 4 seconds) Is set to unlimited (the number of sheets 0 setting means unlimited meaning).
Result = camera . SetIntervalParam ( 4 , 0 );
Below is an overview of thread processing.
// read thread
public void start_read_thread () {
new Thread ( new Runnable () {
@Override
public void run () {
FileOutputStream out ;
the try {
NotificationLedBlink ( LedTarget . LED 3 , LedColor . MAGENTA , 500 );
Log . d ( "GNSS" , "Thread Start" );
Restriction On The // Number Of Log Files
String [] Files = FileList ();
Arrays . Sort ( Files );
Int MaxFileNum = 20 ;
If ( Files . Length > = MaxFileNum ) {
For ( Int I = 0 ; I <= ( files . length - maxFileNum ); i ++ ) {
Log . d ( "GNSS", "Delet file:" + files [ i ] );
deleteFile ( files [ i ]);
}
}
Create Logfile //
SimpleDateFormat Df = New SimpleDateFormat ( "YyyyMMdd_HHmmss" );
Final Date Date = New Date ( System . CurrentTimeMillis ());
String FileName = "GNSS_Log_" Tasu Df . Format ( Date ) Tasu ".Txt" ;
Out = openFileOutput ( FileName , MODE_PRIVATE | MODE_APPEND );
// gpsInfo editing buffer
String Lat = "" ;
String Lng = "" ;
String _Altitude = "" ;
String _DateTimeZone = "" ;
String UtcYYYYMMDD = "" ;
String UtcHHMMSS = "" ;
while ( mFinished == false ) {
// serial communication receiving polling unit
byte buff [] = new new byte [ 256 ];
int num = port . Read ( buff , buff . Length );
an if ( num > 0 ) {
String RcvStr = new new String ( buff , 0 , num );
String [] SplitSentence = RcvStr . Split ( "," , 0);
Log . D ( "GNSS" , RcvStr );
Out . Write ( Buff , 0 , RcvStr . Length ());
// [RMC sentence]
//
Year / month / date is only in RMC sentence // reliability, latitude, longitude, time is overlapped with GGA sentence
// no altitude
// received before GGA sentence
if ( splitSentence [ 0 ]. ContentEquals ( "$ GPRMC" ) Andoando ( SplitSentence . Length == 13 ) ) {
// GpsInfo: UTC date of editing
If ( SplitSentence [ 9 .] Length () == 6 ) {
UtcYYYYMMDD = " 20 " + splitSentence [ 9 ].substring ( 4 , 6 ) + ":" + SplitSentence [ 9 ]. substring ( 2 , 4 ) + ":" + SplitSentence [ 9 .] substring ( 0 , 2 );
//Log.D("GNSS "," YY: MM: DD (UTC) = "+ utcYYMMDD);
} else {
utcYYYYMMDD = " " ;
}
}
// Execute setGpsInfo if reliable location information
/ / [GGA sentence]
// UTC: used
// latitude: ddmm.mmmmm is separated dd + (mm.mmmmm / 60)
// N / S:
Google
north latitude as it is, south latitude giving minus // longitude: dddmm.mmmmm is separated Google draft map format with ddd + (mm.mmmmm / 60) // E / W: east long as it is, west long gives a minus
// locate Quality: 0 can not be measured, 1or2 can be measured if it is
used // number of satellites used: unused 3 altitude accuracy is not high
/ horizontal accuracy drop rate
/
unused // antenna elevation height: use // above unit: m is meters
// geoid height: unused
// above unit: m in meters
reference point for the // DGPS not use the blank
// * checksum: do not check this time
if ( splitSentence [ 0 ]. contentEquals ( "$ GPGGA" ) && ( splitSentence .length == 15 ) ) {
// authenticity-checking
an if ( SplitSentence [ 6 ]. contentEquals ( "1" ) || SplitSentence [ 6 ]. contentEquals ( "2" ) ) {
NotificationLedBlink ( LedTarget . LED 3 , LedColor . GREEN , 500 );
// GpsInfo: Lat of editing
Double LatTop = Double . valueOf ( SplitSentence[ 2 ]. Substring ( 0 , 2 ) );
the Double LatEnd = the Double . ValueOf ( SplitSentence [ 2 ]. Substring ( 2 , SplitSentence [ 2 ]. Length ()) );
the Double LatResult = LatTop + ( LatEnd / 60 ) ;
lat = String . format ( "% 02.06 f" , latResult);
An if ( SplitSentence [ 3 .] ContentEquals ( "S" ) ) {
lat = "-" + lat ;
}
// GPSInfo: lng editing
the Double LngTop = the Double . ValueOf ( SplitSentence [ 4 .] Substring ( 0 , 3 ) );
the Double LngEnd = the Double . ValueOf ( SplitSentence [ 4 .] Substring ( 3 , SplitSentence [ 4 .] Length ( )) );
Double lngResult = lngTop + ( lngEnd / 60) ;
Lng = String . Format ( "% 02.06F" , LngResult );
an if ( SplitSentence [ 5 .] ContentEquals ( "W" ) ) {
lng = "-" + lng ;
}
// gpsInfo: Edit
_altitude _ altitude = splitSentence [ 9 ]; // This time do not check the range and use as it is
// gpsInfo: UTC hours and minutes After editing _dateTimeZone after obtaining seconds
if ( splitSentence [ 1 ]. length () == 9 ) {
utcHHMMSS = splitSentence [ 1 ]. substring ( 0 , 2 ) + ":" + splitSentence [ 1 ] substring ( 2 , 4 ) + ":" + splitSentence [ 1 ]. substring ( 4 , 6 ) ;
}
_dateTimeZone = utcYYYYMMDD + "" + utcHHMMSS + "+ 09: 00" ; // In this example, the time zone is fixed to JST.
// Log.d("GNSS "," UTC = "+ _dateTimeZone);
// editing of debug table information
String LogStr = "Lat =" Tasu Lat Tasu ", Lng =" Tasu Lng Tasu ", Height =" Tasu _Altitude Tasu ", DateTime =" Tasu _DateTimeZone Tasu "\ N" ;
Log . D ( "GNSS" , "gpsInfo =" + logStr );
// gpsInfo setting
HttpConnector camera = new HttpConnector ( "127.0.0.1 :
8080 " ); String setGpsInfoResult = camera . setGpsInfo ( lat , lng , _altitude , _dateTimeZone );
Log . d ( "GNSS" , "setGpsInfoResult:" + setGpsInfoResult ) ;
} else {
notificationLedBlink ( LedTarget . LED 3 , LedColor . MAGENTA, 500 );
}
}
}
// Sleep 10 ms to prevent polling from becoming too frequent
Thread . Sleep ( 10 );
// execution of interval shooting start / stop instruction
an if ( SENDREQ == true ) {
String the Result ;
HttpConnector camera = new new HttpConnector ( "127.0.0.1:8080" );
an if ( IsIntervalStat ) {
the Result = camera . SetIntervalParam ( 4 , 0 );
Log . D ( "GNSS" , "T: Camera.SetIntervalParam () =" Tasu Result );
Result = Camera .StartCapture ( "interval" );
Log . d ( "GNSS" , "T: Camera.StartCapture () =" + the Result );
NotificationLedShow ( LedTarget . LED 6 );
} the else {
the Result = camera . StopCapture ();
Log . d ( "GNSS" , "T: camera.stopCapture () =" + Result );
notificationLedHide ( LedTarget . LED 6 );
}
sendReq = false ;
}
}
out . close ();
} Catch ( IOException e ) {
. // Deal with error
e . PrintStackTrace ();
Log . D ( "GNSS" , "T: IOException" );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . YELLOW , 100 );
} catch ( InterruptedException e ) {
. // Deal with error
e . printStackTrace ();
Log . d ("GNSS" , "T: InterruptedException" );
NotificationLedBlink ( LedTarget . LED 3 , LedColor . YELLOW , 200 );
} a finally {
Log . D ( "GNSS" , "T: a finally" );
}
}
.}) Start () ;
}