diff options
author | Keith Packard <keithp@keithp.com> | 2013-04-22 17:20:51 -0500 |
---|---|---|
committer | Keith Packard <keithp@keithp.com> | 2013-04-22 17:20:51 -0500 |
commit | 33c3b2c57d0d4285b75d4dcf7ca67ad19da08c86 (patch) | |
tree | a9ff47afab29fe141dcd1da0ca276b367f911cb6 | |
parent | 1a84db819a359be39be51c9105039ab28c9fc894 (diff) | |
parent | 90b0db1ae53182c94bf12d661446fc369d916366 (diff) |
Merge branch 'master' into stm-flash
Conflicts:
ao-tools/ao-stmload/ao-stmload.c
148 files changed, 7511 insertions, 945 deletions
@@ -29,6 +29,7 @@ ao-tidongle.h ao-tools/ao-bitbang/ao-bitbang ao-tools/ao-dbg/ao-dbg ao-tools/ao-dumplog/ao-dumplog +ao-tools/ao-dump-up/ao-dump-up ao-tools/ao-eeprom/ao-eeprom ao-tools/ao-edit-telem/ao-edit-telem ao-tools/ao-list/ao-list diff --git a/altosdroid/AndroidManifest.xml b/altosdroid/AndroidManifest.xml index 237c6dcf..04a679e1 100644 --- a/altosdroid/AndroidManifest.xml +++ b/altosdroid/AndroidManifest.xml @@ -17,8 +17,8 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.altusmetrum.AltosDroid" - android:versionCode="1" - android:versionName="1.1.9.3"> + android:versionCode="3" + android:versionName="1.2"> <uses-sdk android:targetSdkVersion="10" android:minSdkVersion="10"/> <!-- Google Maps --> <uses-feature android:glEsVersion="0x00020000" android:required="true"/> @@ -31,7 +31,6 @@ <!-- Permissions needed for GoogleMaps --> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <permission android:name="org.altusmetrum.AltosDroid.permission.MAPS_RECEIVE" diff --git a/altosdroid/Notebook b/altosdroid/Notebook new file mode 100644 index 00000000..912a835c --- /dev/null +++ b/altosdroid/Notebook @@ -0,0 +1,22 @@ +Desired AltosDroid feature list + + *) GPS satellite status tab. Monitor GPS C/N0 numbers and + SVIDs. Provides more info before GPS lock is acquired. + + *) Channel scanning. Provide the ability to search for telemetry + signals like AltosUI does. + + *) Highlight current frequency in the frequency list. + + *) Random frequency selection. Provide some mechanism to input + arbitrary radio frequencies. Could be like AltosUI which allows + you to edit the list of frequencies and assign names to them, + or perhaps something better. + + *) TM configuration from the droid + + *) Monitor-idle mode + + *) Frequency scanning + + *) Select satellite imaging mode diff --git a/altosdroid/res/layout/altosdroid.xml b/altosdroid/res/layout/altosdroid.xml index 364f6ba6..71b7ad05 100644 --- a/altosdroid/res/layout/altosdroid.xml +++ b/altosdroid/res/layout/altosdroid.xml @@ -47,7 +47,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/callsign_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -67,7 +67,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/serial_label" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -87,7 +87,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/flight_label" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -107,7 +107,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/state_label" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -127,7 +127,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/rssi_label" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -147,7 +147,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/age_label" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> diff --git a/altosdroid/res/layout/tab_ascent.xml b/altosdroid/res/layout/tab_ascent.xml index b7e97086..b21ec426 100644 --- a/altosdroid/res/layout/tab_ascent.xml +++ b/altosdroid/res/layout/tab_ascent.xml @@ -47,7 +47,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/height_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -68,7 +68,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_height_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -99,7 +99,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/speed_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -120,7 +120,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_speed_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -151,7 +151,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/accel_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -172,7 +172,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_accel_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -194,7 +194,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/lat_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -215,7 +215,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/lon_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -228,7 +228,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/apogee_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/apogee_greenled" @@ -254,7 +254,7 @@ android:layout_below="@id/apogee_voltage_label" android:layout_toRightOf="@id/apogee_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -267,7 +267,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/main_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/main_greenled" @@ -293,7 +293,7 @@ android:layout_below="@id/main_voltage_label" android:layout_toRightOf="@id/main_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/tab_descent.xml b/altosdroid/res/layout/tab_descent.xml index 4b62acf9..9e1fc820 100644 --- a/altosdroid/res/layout/tab_descent.xml +++ b/altosdroid/res/layout/tab_descent.xml @@ -46,7 +46,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/speed_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -67,7 +67,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/height_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -99,7 +99,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/elevation_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -120,7 +120,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/range_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -152,7 +152,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/bearing_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -173,7 +173,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/compass_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -205,7 +205,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/distance_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <TextView @@ -234,7 +234,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/lat_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -255,7 +255,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/lon_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -268,7 +268,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/apogee_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/apogee_greenled" @@ -294,7 +294,7 @@ android:layout_below="@id/apogee_voltage_label" android:layout_toRightOf="@id/apogee_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -307,7 +307,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/main_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/main_greenled" @@ -333,7 +333,7 @@ android:layout_below="@id/main_voltage_label" android:layout_toRightOf="@id/main_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> diff --git a/altosdroid/res/layout/tab_landed.xml b/altosdroid/res/layout/tab_landed.xml index b5c8d8c2..f27baa9e 100644 --- a/altosdroid/res/layout/tab_landed.xml +++ b/altosdroid/res/layout/tab_landed.xml @@ -37,7 +37,7 @@ android:layout_alignParentRight="true" android:layout_below="@+id/bearing_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -58,7 +58,7 @@ android:layout_alignParentRight="true" android:layout_below="@+id/distance_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -67,19 +67,19 @@ android:paddingTop="5dp" > <TextView - android:id="@+id/lat_label" + android:id="@+id/target_lat_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/latitude_label" /> + android:text="@string/target_latitude_label" /> <TextView - android:id="@+id/lat_value" + android:id="@+id/target_lat_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" - android:layout_below="@id/lat_label" + android:layout_below="@id/target_lat_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -88,19 +88,61 @@ android:paddingTop="5dp" > <TextView - android:id="@+id/lon_label" + android:id="@+id/target_lon_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/longitude_label" /> + android:text="@string/target_longitude_label" /> <TextView - android:id="@+id/lon_value" + android:id="@+id/target_lon_value" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" - android:layout_below="@id/lon_label" + android:layout_below="@id/target_lon_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> + </RelativeLayout> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="5dp" > + + <TextView + android:id="@+id/receiver_lat_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/receiver_latitude_label" /> + + <TextView + android:id="@+id/receiver_lat_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/receiver_lat_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </RelativeLayout> + + <RelativeLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingTop="5dp" > + + <TextView + android:id="@+id/receiver_lon_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/receiver_longitude_label" /> + + <TextView + android:id="@+id/receiver_lon_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@id/receiver_lon_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -121,7 +163,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_height_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -142,7 +184,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_speed_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -163,7 +205,7 @@ android:layout_alignParentRight="true" android:layout_below="@id/max_accel_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/tab_map.xml b/altosdroid/res/layout/tab_map.xml index b9f4e69e..f611ae48 100644 --- a/altosdroid/res/layout/tab_map.xml +++ b/altosdroid/res/layout/tab_map.xml @@ -46,16 +46,16 @@ android:id="@+id/distance_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingRight="4dp" android:text="@string/distance_label" /> <TextView android:id="@+id/distance_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@+id/distance_label" + android:layout_toRightOf="@+id/distance_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -68,16 +68,16 @@ android:id="@+id/bearing_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingRight="4dp" android:text="@string/bearing_label" /> <TextView android:id="@+id/bearing_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@+id/bearing_label" + android:layout_toRightOf="@+id/bearing_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> @@ -96,19 +96,19 @@ android:paddingTop="5dp" > <TextView - android:id="@+id/lat_label" + android:id="@+id/target_lat_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/latitude_label" /> + android:paddingRight="4dp" + android:text="@string/target_latitude_label" /> <TextView - android:id="@+id/lat_value" + android:id="@+id/target_lat_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@id/lat_label" + android:layout_toRightOf="@id/target_lat_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -118,19 +118,71 @@ android:paddingTop="5dp" > <TextView - android:id="@+id/lon_label" + android:id="@+id/target_lon_label" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/longitude_label" /> + android:paddingRight="4dp" + android:text="@string/target_longitude_label" /> <TextView - android:id="@+id/lon_value" + android:id="@+id/target_lon_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_below="@id/lon_label" + android:layout_toRightOf="@id/target_lon_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> + </RelativeLayout> + </LinearLayout> + + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:baselineAligned="true" + android:orientation="horizontal" > + + <RelativeLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="5dp" > + + <TextView + android:id="@+id/receiver_lat_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingRight="4dp" + android:text="@string/receiver_latitude_label" /> + + <TextView + android:id="@+id/receiver_lat_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/receiver_lat_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> + </RelativeLayout> + + <RelativeLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:paddingTop="5dp" > + + <TextView + android:id="@+id/receiver_lon_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingRight="4dp" + android:text="@string/receiver_longitude_label" /> + + <TextView + android:id="@+id/receiver_lon_value" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toRightOf="@id/receiver_lon_label" + android:text="" + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/layout/tab_pad.xml b/altosdroid/res/layout/tab_pad.xml index f5ec46b5..38e61f83 100644 --- a/altosdroid/res/layout/tab_pad.xml +++ b/altosdroid/res/layout/tab_pad.xml @@ -30,7 +30,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/battery_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/battery_greenled" @@ -55,7 +55,7 @@ android:layout_below="@id/battery_voltage_label" android:layout_toRightOf="@id/battery_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -68,7 +68,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/apogee_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/apogee_greenled" @@ -93,7 +93,7 @@ android:layout_below="@id/apogee_voltage_label" android:layout_toRightOf="@id/apogee_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -106,7 +106,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/main_voltage_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/main_greenled" @@ -131,7 +131,7 @@ android:layout_below="@id/main_voltage_label" android:layout_toRightOf="@id/main_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -144,7 +144,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/logging_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/logging_greenled" @@ -169,7 +169,7 @@ android:layout_below="@id/logging_label" android:layout_toRightOf="@id/logging_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -182,7 +182,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/gps_locked_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/gps_locked_greenled" @@ -207,7 +207,7 @@ android:layout_below="@id/gps_locked_label" android:layout_toRightOf="@id/gps_locked_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout @@ -220,7 +220,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/gps_ready_label" - android:src="@drawable/redled" /> + android:src="@drawable/grayled" /> <ImageView android:id="@+id/gps_ready_greenled" @@ -245,19 +245,20 @@ android:layout_below="@id/gps_ready_label" android:layout_toRightOf="@id/gps_ready_greenled" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingLeft="69dp" - android:paddingTop="5dp" > + android:paddingLeft="69dp"> <TextView android:id="@+id/pad_lat_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:width="100sp" + android:paddingRight="10sp" android:layout_toRightOf="@id/gps_ready_greenled" android:text="@string/pad_lat_label" /> @@ -265,22 +266,22 @@ android:id="@+id/pad_lat_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/pad_lat_label" - android:layout_toRightOf="@id/gps_ready_greenled" + android:layout_toRightOf="@id/pad_lat_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingLeft="69dp" - android:paddingTop="5dp" > + android:paddingLeft="69dp"> <TextView android:id="@+id/pad_lon_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:width="100sp" + android:paddingRight="10sp" android:layout_toRightOf="@id/gps_ready_greenled" android:text="@string/pad_lon_label" /> @@ -288,22 +289,22 @@ android:id="@+id/pad_lon_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/pad_lon_label" - android:layout_toRightOf="@id/gps_ready_greenled" + android:layout_toRightOf="@id/pad_lon_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:paddingLeft="69dp" - android:paddingTop="5dp" > + android:paddingLeft="69dp"> <TextView android:id="@+id/pad_alt_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:width="100sp" + android:paddingRight="10sp" android:layout_toRightOf="@id/gps_ready_greenled" android:text="@string/pad_alt_label" /> @@ -311,10 +312,9 @@ android:id="@+id/pad_alt_value" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@id/pad_alt_label" - android:layout_toRightOf="@id/gps_ready_greenled" + android:layout_toRightOf="@id/pad_alt_label" android:text="" - android:textAppearance="?android:attr/textAppearanceLarge" /> + android:textAppearance="?android:attr/textAppearanceSmall" /> </RelativeLayout> </LinearLayout>
\ No newline at end of file diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml index 639611c2..90da617b 100644 --- a/altosdroid/res/values/strings.xml +++ b/altosdroid/res/values/strings.xml @@ -72,8 +72,12 @@ <string name="gps_ready_label">GPS Ready</string> <string name="latitude_label">Latitude</string> <string name="longitude_label">Longitude</string> - <string name="pad_lat_label">Pad Latitude</string> - <string name="pad_lon_label">Pad Longitude</string> - <string name="pad_alt_label">Pad Altitude</string> + <string name="target_latitude_label">Tar Lat</string> + <string name="target_longitude_label">Tar Lon</string> + <string name="receiver_latitude_label">My Lat</string> + <string name="receiver_longitude_label">My Lon</string> + <string name="pad_lat_label">Pad Lat</string> + <string name="pad_lon_label">Pad Lon</string> + <string name="pad_alt_label">Pad Alt</string> </resources> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index c9ce46a0..e10982f7 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -37,7 +37,7 @@ import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.support.v4.app.FragmentActivity; -import android.support.v4.view.ViewPager; +import android.util.DisplayMetrics; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; @@ -47,6 +47,7 @@ import android.widget.TabHost; import android.widget.TextView; import android.widget.Toast; import android.app.AlertDialog; +import android.location.Location; import org.altusmetrum.altoslib_1.*; @@ -59,6 +60,8 @@ public class AltosDroid extends FragmentActivity { public static final int MSG_STATE_CHANGE = 1; public static final int MSG_TELEMETRY = 2; public static final int MSG_UPDATE_AGE = 3; + public static final int MSG_LOCATION = 4; + public static final int MSG_CRC_ERROR = 5; // Intent request codes private static final int REQUEST_CONNECT_DEVICE = 1; @@ -79,14 +82,16 @@ public class AltosDroid extends FragmentActivity { private TextView mVersion; // Tabs - TabHost mTabHost; - AltosViewPager mViewPager; - TabsAdapter mTabsAdapter; + TabHost mTabHost; + AltosViewPager mViewPager; + TabsAdapter mTabsAdapter; ArrayList<AltosDroidTab> mTabs = new ArrayList<AltosDroidTab>(); + int tabHeight; // Timer and Saved flight state for Age calculation private Timer timer = new Timer(); AltosState saved_state; + Location saved_location; // Service private boolean mIsBound = false; @@ -122,7 +127,6 @@ public class AltosDroid extends FragmentActivity { ad.mTitle.setText(R.string.title_connected_to); ad.mTitle.append(str); Toast.makeText(ad.getApplicationContext(), "Connected to " + str, Toast.LENGTH_SHORT).show(); - ad.mAltosVoice.speak("Connected"); break; case TelemetryService.STATE_CONNECTING: ad.mTitle.setText(R.string.title_connecting); @@ -137,6 +141,10 @@ public class AltosDroid extends FragmentActivity { case MSG_TELEMETRY: ad.update_ui((AltosState) msg.obj); break; + case MSG_LOCATION: + ad.set_location((Location) msg.obj); + break; + case MSG_CRC_ERROR: case MSG_UPDATE_AGE: if (ad.saved_state != null) { ad.mAgeView.setText(String.format("%d", (System.currentTimeMillis() - ad.saved_state.report_time + 500) / 1000)); @@ -196,8 +204,13 @@ public class AltosDroid extends FragmentActivity { mTabs.remove(mTab); } + void set_location(Location location) { + saved_location = location; + update_ui(saved_state); + } + void update_ui(AltosState state) { - if (saved_state != null) { + if (state != null && saved_state != null) { if (saved_state.state != state.state) { String currentTab = mTabHost.getCurrentTabTag(); switch (state.state) { @@ -215,16 +228,33 @@ public class AltosDroid extends FragmentActivity { } saved_state = state; - mCallsignView.setText(state.data.callsign); - mSerialView.setText(String.format("%d", state.data.serial)); - mFlightView.setText(String.format("%d", state.data.flight)); - mStateView.setText(state.data.state()); - mRSSIView.setText(String.format("%d", state.data.rssi)); + AltosGreatCircle from_receiver = null; + + if (state != null && saved_location != null && state.gps != null && state.gps.locked) { + double altitude = 0; + if (saved_location.hasAltitude()) + altitude = saved_location.getAltitude(); + from_receiver = new AltosGreatCircle(saved_location.getLatitude(), + saved_location.getLongitude(), + altitude, + state.gps.lat, + state.gps.lon, + state.gps.alt); + } + + if (state != null) { + mCallsignView.setText(state.data.callsign); + mSerialView.setText(String.format("%d", state.data.serial)); + mFlightView.setText(String.format("%d", state.data.flight)); + mStateView.setText(state.data.state()); + mRSSIView.setText(String.format("%d", state.data.rssi)); + } for (AltosDroidTab mTab : mTabs) - mTab.update_ui(state); + mTab.update_ui(state, from_receiver, saved_location); - mAltosVoice.tell(state); + if (state != null) + mAltosVoice.tell(state); } private void onTimerTick() { @@ -236,13 +266,27 @@ public class AltosDroid extends FragmentActivity { static String pos(double p, String pos, String neg) { String h = pos; + if (p == AltosRecord.MISSING) + return ""; if (p < 0) { h = neg; p = -p; } int deg = (int) Math.floor(p); double min = (p - Math.floor(p)) * 60.0; - return String.format("%d° %9.6f\" %s", deg, min, h); + return String.format("%d°%9.4f\" %s", deg, min, h); + } + + static String number(String format, double value) { + if (value == AltosRecord.MISSING) + return ""; + return String.format(format, value); + } + + static String integer(String format, int value) { + if (value == AltosRecord.MISSING) + return ""; + return String.format(format, value); } @Override @@ -269,6 +313,7 @@ public class AltosDroid extends FragmentActivity { setContentView(R.layout.altosdroid); getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title); + // Create the Tabs and ViewPager mTabHost = (TabHost)findViewById(android.R.id.tabhost); mTabHost.setup(); @@ -284,6 +329,27 @@ public class AltosDroid extends FragmentActivity { mTabsAdapter.addTab(mTabHost.newTabSpec("map").setIndicator("Map"), TabMap.class, null); + // Scale the size of the Tab bar for different screen densities + // This probably won't be needed when we start supporting ICS+ tabs. + DisplayMetrics metrics = new DisplayMetrics(); + getWindowManager().getDefaultDisplay().getMetrics(metrics); + int density = metrics.densityDpi; + + if (density==DisplayMetrics.DENSITY_XHIGH) + tabHeight = 65; + else if (density==DisplayMetrics.DENSITY_HIGH) + tabHeight = 45; + else if (density==DisplayMetrics.DENSITY_MEDIUM) + tabHeight = 35; + else if (density==DisplayMetrics.DENSITY_LOW) + tabHeight = 25; + else + tabHeight = 65; + + for (int i = 0; i < 5; i++) + mTabHost.getTabWidget().getChildAt(i).getLayoutParams().height = tabHeight; + + // Set up the custom title mTitle = (TextView) findViewById(R.id.title_left_text); mTitle.setText(R.string.app_name); @@ -348,7 +414,7 @@ public class AltosDroid extends FragmentActivity { super.onDestroy(); if(D) Log.e(TAG, "--- ON DESTROY ---"); - mAltosVoice.stop(); + if (mAltosVoice != null) mAltosVoice.stop(); } public void onActivityResult(int requestCode, int resultCode, Intent data) { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java index 68bbe593..6ebb47f7 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java @@ -17,8 +17,9 @@ package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_1.AltosState; +import org.altusmetrum.altoslib_1.*; +import android.location.Location; public interface AltosDroidTab { - public void update_ui(AltosState state); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver); } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java index 7da5c4a9..b3dba626 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java @@ -38,7 +38,6 @@ public class AltosVoice { public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) tts_enabled = true;
if (tts_enabled) {
- speak("AltosDroid ready");
idle_thread = new IdleThread();
}
}
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java index 7b9cbde7..71692122 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/DeviceListActivity.java @@ -109,9 +109,10 @@ public class DeviceListActivity extends Activity { // If there are paired devices, add each one to the ArrayAdapter if (pairedDevices.size() > 0) { findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); - for (BluetoothDevice device : pairedDevices) { - mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); - } + for (BluetoothDevice device : pairedDevices) + if (device.getName().startsWith("TeleBT")) + mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); + } else { String noDevices = getResources().getText(R.string.none_paired).toString(); mPairedDevicesArrayAdapter.add(noDevices); @@ -185,7 +186,8 @@ public class DeviceListActivity extends Activity { // Get the BluetoothDevice object from the Intent BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); // If it's already paired, skip it, because it's been listed already - if (device.getBondState() != BluetoothDevice.BOND_BONDED) { + if ( device.getBondState() != BluetoothDevice.BOND_BONDED + && device.getName().startsWith("TeleBT") ) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } // When discovery is finished, change the Activity title diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java index 0f95bc22..8e8d9c03 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java @@ -23,6 +23,8 @@ import android.widget.ImageView; public class GoNoGoLights { private Boolean state; + private Boolean missing; + private Boolean set; private ImageView red; private ImageView green; @@ -35,16 +37,23 @@ public class GoNoGoLights { red = in_red; green = in_green; state = false; + missing = true; + set = false; dRed = r.getDrawable(R.drawable.redled); dGreen = r.getDrawable(R.drawable.greenled); dGray = r.getDrawable(R.drawable.grayled); } - public void set(Boolean s) { - if (s == state) return; + public void set(Boolean s, Boolean m) { + if (set && s == state && m == missing) return; state = s; - if (state) { + missing = m; + set = true; + if (missing) { + red.setImageDrawable(dGray); + green.setImageDrawable(dGray); + } else if (state) { red.setImageDrawable(dGray); green.setImageDrawable(dGreen); } else { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java index bda6b1fd..0e141ae4 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java @@ -17,7 +17,7 @@ package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_1.AltosState; +import org.altusmetrum.altoslib_1.*; import android.app.Activity; import android.os.Bundle; @@ -27,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import android.location.Location; public class TabAscent extends Fragment implements AltosDroidTab { AltosDroid mAltosDroid; @@ -84,21 +85,28 @@ public class TabAscent extends Fragment implements AltosDroidTab { mAltosDroid = null; } - public void update_ui(AltosState state) { - mHeightView.setText(String.format("%6.0f m", state.height)); - mMaxHeightView.setText(String.format("%6.0f m", state.max_height)); - mSpeedView.setText(String.format("%6.0f m/s", state.speed())); - mMaxSpeedView.setText(String.format("%6.0f m/s", state.max_speed())); - mAccelView.setText(String.format("%6.0f m/s²", state.acceleration)); - mMaxAccelView.setText(String.format("%6.0f m/s²", state.max_acceleration)); - - mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); - mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); - - mApogeeVoltageView.setText(String.format("%4.2f V", state.drogue_sense)); - mApogeeLights.set(state.drogue_sense > 3.2); - - mMainVoltageView.setText(String.format("%4.2f V", state.main_sense)); - mMainLights.set(state.main_sense > 3.2); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver) { + if (state != null) { + mHeightView.setText(AltosDroid.number("%6.0f m", state.height)); + mMaxHeightView.setText(AltosDroid.number("%6.0f m", state.max_height)); + mSpeedView.setText(AltosDroid.number("%6.0f m/s", state.speed())); + mMaxSpeedView.setText(AltosDroid.number("%6.0f m/s", state.max_speed())); + mAccelView.setText(AltosDroid.number("%6.0f m/s²", state.acceleration)); + mMaxAccelView.setText(AltosDroid.number("%6.0f m/s²", state.max_acceleration)); + + if (state.gps != null) { + mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); + mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); + } else { + mLatitudeView.setText(""); + mLongitudeView.setText(""); + } + + mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.drogue_sense)); + mApogeeLights.set(state.drogue_sense > 3.2, state.drogue_sense == AltosRecord.MISSING); + + mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_sense)); + mMainLights.set(state.main_sense > 3.2, state.main_sense == AltosRecord.MISSING); + } } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java index 3805b7e7..09e7169b 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java @@ -17,8 +17,7 @@ package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_1.AltosGreatCircle; -import org.altusmetrum.altoslib_1.AltosState; +import org.altusmetrum.altoslib_1.*; import android.app.Activity; import android.os.Bundle; @@ -28,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import android.location.Location; public class TabDescent extends Fragment implements AltosDroidTab { AltosDroid mAltosDroid; @@ -89,24 +89,34 @@ public class TabDescent extends Fragment implements AltosDroidTab { mAltosDroid = null; } - public void update_ui(AltosState state) { - mSpeedView.setText(String.format("%6.0f m/s", state.speed())); - mHeightView.setText(String.format("%6.0f m", state.height)); - mElevationView.setText(String.format("%3.0f°", state.elevation)); - mRangeView.setText(String.format("%6.0f m", state.range)); - if (state.from_pad != null) { - mBearingView.setText(String.format("%3.0f°", state.from_pad.bearing)); - mCompassView.setText(state.from_pad.bearing_words(AltosGreatCircle.BEARING_LONG)); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver) { + if (state != null) { + mSpeedView.setText(AltosDroid.number("%6.0f m/s", state.speed())); + mHeightView.setText(AltosDroid.number("%6.0f m", state.height)); + if (from_receiver != null) { + mElevationView.setText(AltosDroid.number("%3.0f°", from_receiver.elevation)); + mRangeView.setText(AltosDroid.number("%6.0f m", from_receiver.range)); + mBearingView.setText(AltosDroid.number("%3.0f°", from_receiver.bearing)); + mCompassView.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG)); + mDistanceView.setText(AltosDroid.number("%6.0f m", from_receiver.distance)); + } else { + mElevationView.setText("<unknown>"); + mRangeView.setText("<unknown>"); + mBearingView.setText("<unknown>"); + mCompassView.setText("<unknown>"); + mDistanceView.setText("<unknown>"); + } + if (state.gps != null) { + mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); + mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); + } + + mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.drogue_sense)); + mApogeeLights.set(state.drogue_sense > 3.2, state.drogue_sense == AltosRecord.MISSING); + + mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_sense)); + mMainLights.set(state.main_sense > 3.2, state.main_sense == AltosRecord.MISSING); } - mDistanceView.setText(String.format("%6.0f m", state.range)); - mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); - mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); - - mApogeeVoltageView.setText(String.format("%4.2f V", state.drogue_sense)); - mApogeeLights.set(state.drogue_sense > 3.2); - - mMainVoltageView.setText(String.format("%4.2f V", state.main_sense)); - mMainLights.set(state.main_sense > 3.2); } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java index a95e9145..f42b46b5 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java @@ -17,7 +17,7 @@ package org.altusmetrum.AltosDroid; -import org.altusmetrum.altoslib_1.AltosState; +import org.altusmetrum.altoslib_1.*; import android.app.Activity; import android.os.Bundle; @@ -26,14 +26,17 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.location.Location; public class TabLanded extends Fragment implements AltosDroidTab { AltosDroid mAltosDroid; private TextView mBearingView; private TextView mDistanceView; - private TextView mLatitudeView; - private TextView mLongitudeView; + private TextView mTargetLatitudeView; + private TextView mTargetLongitudeView; + private TextView mReceiverLatitudeView; + private TextView mReceiverLongitudeView; private TextView mMaxHeightView; private TextView mMaxSpeedView; private TextView mMaxAccelView; @@ -52,8 +55,10 @@ public class TabLanded extends Fragment implements AltosDroidTab { mBearingView = (TextView) v.findViewById(R.id.bearing_value); mDistanceView = (TextView) v.findViewById(R.id.distance_value); - mLatitudeView = (TextView) v.findViewById(R.id.lat_value); - mLongitudeView = (TextView) v.findViewById(R.id.lon_value); + mTargetLatitudeView = (TextView) v.findViewById(R.id.target_lat_value); + mTargetLongitudeView = (TextView) v.findViewById(R.id.target_lon_value); + mReceiverLatitudeView = (TextView) v.findViewById(R.id.receiver_lat_value); + mReceiverLongitudeView = (TextView) v.findViewById(R.id.receiver_lon_value); mMaxHeightView = (TextView) v.findViewById(R.id.max_height_value); mMaxSpeedView = (TextView) v.findViewById(R.id.max_speed_value); mMaxAccelView = (TextView) v.findViewById(R.id.max_accel_value); @@ -68,16 +73,26 @@ public class TabLanded extends Fragment implements AltosDroidTab { mAltosDroid = null; } - public void update_ui(AltosState state) { - if (state.from_pad != null) { - mBearingView.setText(String.format("%3.0f°", state.from_pad.bearing)); - mDistanceView.setText(String.format("%6.0f m", state.from_pad.distance)); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver) { + if (from_receiver != null) { + mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); + mDistanceView.setText(String.format("%6.0f m", from_receiver.distance)); + } + if (state != null && state.gps != null) { + mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); + mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); + } + + if (receiver != null) { + mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); + mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "W", "E")); + } + + if (state != null) { + mMaxHeightView.setText(String.format("%6.0f m", state.max_height)); + mMaxAccelView.setText(String.format("%6.0f m/s²", state.max_acceleration)); + mMaxSpeedView.setText(String.format("%6.0f m/s", state.max_speed())); } - mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); - mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); - mMaxHeightView.setText(String.format("%6.0f m", state.max_height)); - mMaxAccelView.setText(String.format("%6.0f m/s²", state.max_acceleration)); - mMaxSpeedView.setText(String.format("%6.0f m/s", state.max_speed())); } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java index 8fc8f592..d831f117 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java @@ -35,11 +35,12 @@ import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentTransaction; +//import android.support.v4.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import android.location.Location; public class TabMap extends Fragment implements AltosDroidTab { AltosDroid mAltosDroid; @@ -54,8 +55,12 @@ public class TabMap extends Fragment implements AltosDroidTab { private TextView mDistanceView; private TextView mBearingView; - private TextView mLatitudeView; - private TextView mLongitudeView; + private TextView mTargetLatitudeView; + private TextView mTargetLongitudeView; + private TextView mReceiverLatitudeView; + private TextView mReceiverLongitudeView; + + private double mapAccuracy = -1; @Override public void onAttach(Activity activity) { @@ -83,8 +88,10 @@ public class TabMap extends Fragment implements AltosDroidTab { View v = inflater.inflate(R.layout.tab_map, container, false); mDistanceView = (TextView)v.findViewById(R.id.distance_value); mBearingView = (TextView)v.findViewById(R.id.bearing_value); - mLatitudeView = (TextView)v.findViewById(R.id.lat_value); - mLongitudeView = (TextView)v.findViewById(R.id.lon_value); + mTargetLatitudeView = (TextView)v.findViewById(R.id.target_lat_value); + mTargetLongitudeView = (TextView)v.findViewById(R.id.target_lon_value); + mReceiverLatitudeView = (TextView)v.findViewById(R.id.receiver_lat_value); + mReceiverLongitudeView = (TextView)v.findViewById(R.id.receiver_lon_value); return v; } @@ -113,7 +120,6 @@ public class TabMap extends Fragment implements AltosDroidTab { mMap.setMyLocationEnabled(true); mMap.getUiSettings().setTiltGesturesEnabled(false); mMap.getUiSettings().setZoomControlsEnabled(false); - mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(40.8,-104.7),8)); mRocketMarker = mMap.addMarker( // From: http://mapicons.nicolasmollet.com/markers/industry/military/missile-2/ @@ -139,26 +145,54 @@ public class TabMap extends Fragment implements AltosDroidTab { } } - public void update_ui(AltosState state) { - if (state.from_pad != null) { - mDistanceView.setText(String.format("%6.0f m", state.from_pad.distance)); - mBearingView.setText(String.format("%3.0f°", state.from_pad.bearing)); + private void center(double lat, double lon, double accuracy) { + if (mapAccuracy < 0 || accuracy < mapAccuracy/10) { + mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lon),14)); + mapAccuracy = accuracy; } - mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); - mLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); + } - if (mapLoaded) { - mRocketMarker.setPosition(new LatLng(state.gps.lat, state.gps.lon)); - mRocketMarker.setVisible(true); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver) { + if (from_receiver != null) { + mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); + mDistanceView.setText(String.format("%6.0f m", from_receiver.distance)); + } - mPolyline.setPoints(Arrays.asList(new LatLng(state.pad_lat, state.pad_lon), new LatLng(state.gps.lat, state.gps.lon))); - mPolyline.setVisible(true); + if (state != null) { + if (mapLoaded) { + if (state.gps != null) { + mRocketMarker.setPosition(new LatLng(state.gps.lat, state.gps.lon)); + mRocketMarker.setVisible(true); - if (state.state == AltosLib.ao_flight_pad) { - mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon)); - mPadMarker.setVisible(true); + mPolyline.setPoints(Arrays.asList(new LatLng(state.pad_lat, state.pad_lon), new LatLng(state.gps.lat, state.gps.lon))); + mPolyline.setVisible(true); + } + + if (state.state == AltosLib.ao_flight_pad) { + mPadMarker.setPosition(new LatLng(state.pad_lat, state.pad_lon)); + mPadMarker.setVisible(true); + } + } + if (state.gps != null) { + mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); + mTargetLongitudeView.setText(AltosDroid.pos(state.gps.lon, "W", "E")); + if (state.gps.locked && state.gps.nsat >= 4) + center (state.gps.lat, state.gps.lon, 10); } } + + if (receiver != null) { + double accuracy; + + if (receiver.hasAccuracy()) + accuracy = receiver.getAccuracy(); + else + accuracy = 1000; + mReceiverLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); + mReceiverLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "W", "E")); + center (receiver.getLatitude(), receiver.getLongitude(), accuracy); + } + } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java index 41776c10..066c1353 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java @@ -27,6 +27,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; +import android.location.Location; public class TabPad extends Fragment implements AltosDroidTab { AltosDroid mAltosDroid; @@ -100,40 +101,49 @@ public class TabPad extends Fragment implements AltosDroidTab { mAltosDroid = null; } - public void update_ui(AltosState state) { - mBatteryVoltageView.setText(String.format("%4.2f V", state.battery)); - mBatteryLights.set(state.battery > 3.7); - - mApogeeVoltageView.setText(String.format("%4.2f V", state.drogue_sense)); - mApogeeLights.set(state.drogue_sense > 3.2); - - mMainVoltageView.setText(String.format("%4.2f V", state.main_sense)); - mMainLights.set(state.main_sense > 3.2); - - if (state.data.flight != 0) { - if (state.data.state <= AltosLib.ao_flight_pad) - mDataLoggingView.setText("Ready to record"); - else if (state.data.state < AltosLib.ao_flight_landed) - mDataLoggingView.setText("Recording data"); - else - mDataLoggingView.setText("Recorded data"); - } else { - mDataLoggingView.setText("Storage full"); + public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver) { + if (state != null) { + mBatteryVoltageView.setText(AltosDroid.number("%4.2f V", state.battery)); + mBatteryLights.set(state.battery > 3.7, state.battery == AltosRecord.MISSING); + + mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.drogue_sense)); + mApogeeLights.set(state.drogue_sense > 3.2, state.drogue_sense == AltosRecord.MISSING); + + mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_sense)); + mMainLights.set(state.main_sense > 3.2, state.main_sense == AltosRecord.MISSING); + + if (state.data.flight != 0) { + if (state.data.state <= AltosLib.ao_flight_pad) + mDataLoggingView.setText("Ready to record"); + else if (state.data.state < AltosLib.ao_flight_landed) + mDataLoggingView.setText("Recording data"); + else + mDataLoggingView.setText("Recorded data"); + } else { + mDataLoggingView.setText("Storage full"); + } + mDataLoggingLights.set(state.data.flight != 0, state.data.flight == AltosRecord.MISSING); + + if (state.gps != null) { + mGPSLockedView.setText(AltosDroid.integer("%4d sats", state.gps.nsat)); + mGPSLockedLights.set(state.gps.locked && state.gps.nsat >= 4, false); + if (state.gps_ready) + mGPSReadyView.setText("Ready"); + else + mGPSReadyView.setText(AltosDroid.integer("Waiting %d", state.gps_waiting)); + } else + mGPSLockedLights.set(false, true); + mGPSReadyLights.set(state.gps_ready, state.gps == null); } - mDataLoggingLights.set(state.data.flight != 0); - mGPSLockedView.setText(String.format("%4d sats", state.gps.nsat)); - mGPSLockedLights.set(state.gps.locked && state.gps.nsat >= 4); - - if (state.gps_ready) - mGPSReadyView.setText("Ready"); - else - mGPSReadyView.setText(String.format("Waiting %d", state.gps_waiting)); - mGPSReadyLights.set(state.gps_ready); - - mPadLatitudeView.setText(AltosDroid.pos(state.pad_lat, "N", "S")); - mPadLongitudeView.setText(AltosDroid.pos(state.pad_lon, "W", "E")); - mPadAltitudeView.setText(String.format("%4.0f m", state.pad_alt)); + if (receiver != null) { + double altitude = 0; + if (receiver.hasAltitude()) + altitude = receiver.getAltitude(); + mPadLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); + mPadLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "W", "E")); + mPadAltitudeView.setText(AltosDroid.number("%4.0f m", altitude)); + } } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java index 9460bdbc..716ec589 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -68,12 +68,12 @@ public class TelemetryReader extends Thread { if (record == null)
break;
state = new AltosState(record, state);
-
handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();
} catch (ParseException pp) {
Log.e(TAG, String.format("Parse error: %d \"%s\"", pp.getErrorOffset(), pp.getMessage()));
} catch (AltosCRCException ce) {
++crc_errors;
+ handler.obtainMessage(TelemetryService.MSG_CRC_ERROR, new Integer(crc_errors)).sendToTarget();
}
}
} catch (InterruptedException ee) {
diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java index 5ff00a68..0236b537 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -29,7 +29,8 @@ import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothDevice; import android.content.Intent; -//import android.os.Bundle; +import android.content.Context; +import android.os.Bundle; import android.os.IBinder; import android.os.Handler; import android.os.Message; @@ -37,10 +38,14 @@ import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import android.widget.Toast; +import android.location.Location; +import android.location.LocationManager; +import android.location.LocationListener; import org.altusmetrum.altoslib_1.*; -public class TelemetryService extends Service { + +public class TelemetryService extends Service implements LocationListener { private static final String TAG = "TelemetryService"; private static final boolean D = true; @@ -53,6 +58,7 @@ public class TelemetryService extends Service { static final int MSG_DISCONNECTED = 6; static final int MSG_TELEMETRY = 7; static final int MSG_SETFREQUENCY = 8; + static final int MSG_CRC_ERROR = 9; public static final int STATE_NONE = 0; public static final int STATE_READY = 1; @@ -81,6 +87,12 @@ public class TelemetryService extends Service { // internally track state of bluetooth connection private int state = STATE_NONE; + // Last data seen; send to UI when it starts + + private AltosState last_state; + private Location last_location; + private int last_crc_errors; + // Handler of incoming messages from clients. static class IncomingHandler extends Handler { private final WeakReference<TelemetryService> service; @@ -96,6 +108,10 @@ public class TelemetryService extends Service { // Now we try to send the freshly connected UI any relavant information about what // we're talking to - Basically state and Config Data. msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, s.state, -1, s.mConfigData)); + // We also send any recent telemetry or location data that's cached + if (s.last_state != null) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_TELEMETRY, s.last_state )); + if (s.last_location != null) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_LOCATION , s.last_location )); + if (s.last_crc_errors != 0 ) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_CRC_ERROR, s.last_crc_errors)); } catch (RemoteException e) { s.mClients.remove(msg.replyTo); } @@ -126,8 +142,15 @@ public class TelemetryService extends Service { } break; case MSG_TELEMETRY: + // forward telemetry messages + s.last_state = (AltosState) msg.obj; s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_TELEMETRY, msg.obj)); break; + case MSG_CRC_ERROR: + // forward crc error messages + s.last_crc_errors = (Integer) msg.obj; + s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_CRC_ERROR, msg.obj)); + break; case MSG_SETFREQUENCY: if (s.state == STATE_CONNECTED) { try { @@ -180,6 +203,9 @@ public class TelemetryService extends Service { } private void startAltosBluetooth() { + if (device == null) { + return; + } if (mAltosBluetooth == null) { if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress())); mAltosBluetooth = new AltosBluetooth(device, mHandler); @@ -209,6 +235,8 @@ public class TelemetryService extends Service { private void connected() { try { + if (mAltosBluetooth == null) + throw new InterruptedException("no bluetooth"); mConfigData = mAltosBluetooth.config_data(); } catch (InterruptedException e) { } catch (TimeoutException e) { @@ -249,6 +277,11 @@ public class TelemetryService extends Service { // Start our timer - first event in 10 seconds, then every 10 seconds after that. timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L); + // Listen for GPS and Network position updates + LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); + + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this); } @Override @@ -281,6 +314,9 @@ public class TelemetryService extends Service { @Override public void onDestroy() { + // Stop listening for location updates + ((LocationManager) getSystemService(Context.LOCATION_SERVICE)).removeUpdates(this); + // Stop the bluetooth Comms threads stopAltosBluetooth(); @@ -300,4 +336,18 @@ public class TelemetryService extends Service { } + public void onLocationChanged(Location location) { + last_location = location; + sendMessageToClients(Message.obtain(null, AltosDroid.MSG_LOCATION, location)); + } + + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + public void onProviderEnabled(String provider) { + } + + public void onProviderDisabled(String provider) { + } + } diff --git a/altoslib/AltosConfigData.java b/altoslib/AltosConfigData.java index 12659d88..2ca5a7a5 100644 --- a/altoslib/AltosConfigData.java +++ b/altoslib/AltosConfigData.java @@ -135,6 +135,12 @@ public class AltosConfigData implements Iterable<String> { } } + public boolean has_monitor_battery() { + if (product.startsWith("TeleBT")) + return true; + return false; + } + int[] parse_version(String v) { String[] parts = v.split("\\."); int r[] = new int[parts.length]; @@ -476,7 +482,7 @@ public class AltosConfigData implements Iterable<String> { /* UI doesn't support AES key config */ /* AO_PYRO_NUM */ - if (pyros.length > 0) { + if (npyro > 0) { for (int p = 0; p < pyros.length; p++) { link.printf("c P %s\n", pyros[p].toString()); @@ -498,7 +504,7 @@ public class AltosConfigData implements Iterable<String> { switch (log_format) { case AltosLib.AO_LOG_FORMAT_FULL: case AltosLib.AO_LOG_FORMAT_TINY: - case AltosLib.AO_LOG_FORMAT_MEGAMETRUM: + case AltosLib.AO_LOG_FORMAT_TELEMEGA: link.printf("l\n"); read_link(link, "done"); default: diff --git a/altoslib/AltosEepromIterable.java b/altoslib/AltosEepromIterable.java index bc698c80..7a8bbdea 100644 --- a/altoslib/AltosEepromIterable.java +++ b/altoslib/AltosEepromIterable.java @@ -131,24 +131,34 @@ public class AltosEepromIterable extends AltosRecordIterable { case AltosLib.AO_LOG_GPS_LAT: eeprom.seen |= AltosRecord.seen_gps_lat; int lat32 = record.a | (record.b << 16); + if (state.gps == null) + state.gps = new AltosGPS(); state.gps.lat = (double) lat32 / 1e7; break; case AltosLib.AO_LOG_GPS_LON: eeprom.seen |= AltosRecord.seen_gps_lon; int lon32 = record.a | (record.b << 16); + if (state.gps == null) + state.gps = new AltosGPS(); state.gps.lon = (double) lon32 / 1e7; break; case AltosLib.AO_LOG_GPS_ALT: + if (state.gps == null) + state.gps = new AltosGPS(); state.gps.alt = record.a; break; case AltosLib.AO_LOG_GPS_SAT: if (state.tick == eeprom.gps_tick) { int svid = record.a; int c_n0 = record.b >> 8; + if (state.gps == null) + state.gps = new AltosGPS(); state.gps.add_sat(svid, c_n0); } break; case AltosLib.AO_LOG_GPS_DATE: + if (state.gps == null) + state.gps = new AltosGPS(); state.gps.year = (record.a & 0xff) + 2000; state.gps.month = record.a >> 8; state.gps.day = record.b & 0xff; diff --git a/altoslib/AltosFlightReader.java b/altoslib/AltosFlightReader.java index 3039b4dc..34526658 100644 --- a/altoslib/AltosFlightReader.java +++ b/altoslib/AltosFlightReader.java @@ -45,4 +45,8 @@ public class AltosFlightReader { public boolean supports_telemetry(int telemetry) { return false; } public File backing_file() { return null; } + + public boolean has_monitor_battery() { return false; } + + public double monitor_battery() { return AltosRecord.MISSING; } } diff --git a/altoslib/AltosGPS.java b/altoslib/AltosGPS.java index 068d8c9c..f23842f3 100644 --- a/altoslib/AltosGPS.java +++ b/altoslib/AltosGPS.java @@ -217,33 +217,38 @@ public class AltosGPS { } public AltosGPS(AltosGPS old) { - nsat = old.nsat; - locked = old.locked; - connected = old.connected; - lat = old.lat; /* degrees (+N -S) */ - lon = old.lon; /* degrees (+E -W) */ - alt = old.alt; /* m */ - year = old.year; - month = old.month; - day = old.day; - hour = old.hour; - minute = old.minute; - second = old.second; - - ground_speed = old.ground_speed; /* m/s */ - course = old.course; /* degrees */ - climb_rate = old.climb_rate; /* m/s */ - hdop = old.hdop; /* unitless? */ - h_error = old.h_error; /* m */ - v_error = old.v_error; /* m */ - - if (old.cc_gps_sat != null) { - cc_gps_sat = new AltosGPSSat[old.cc_gps_sat.length]; - for (int i = 0; i < old.cc_gps_sat.length; i++) { - cc_gps_sat[i] = new AltosGPSSat(); - cc_gps_sat[i].svid = old.cc_gps_sat[i].svid; - cc_gps_sat[i].c_n0 = old.cc_gps_sat[i].c_n0; + if (old != null) { + nsat = old.nsat; + locked = old.locked; + connected = old.connected; + lat = old.lat; /* degrees (+N -S) */ + lon = old.lon; /* degrees (+E -W) */ + alt = old.alt; /* m */ + year = old.year; + month = old.month; + day = old.day; + hour = old.hour; + minute = old.minute; + second = old.second; + + ground_speed = old.ground_speed; /* m/s */ + course = old.course; /* degrees */ + climb_rate = old.climb_rate; /* m/s */ + hdop = old.hdop; /* unitless? */ + h_error = old.h_error; /* m */ + v_error = old.v_error; /* m */ + + if (old.cc_gps_sat != null) { + cc_gps_sat = new AltosGPSSat[old.cc_gps_sat.length]; + for (int i = 0; i < old.cc_gps_sat.length; i++) { + cc_gps_sat[i] = new AltosGPSSat(); + cc_gps_sat[i].svid = old.cc_gps_sat[i].svid; + cc_gps_sat[i].c_n0 = old.cc_gps_sat[i].c_n0; + } } + } else { + ClearGPSTime(); + cc_gps_sat = null; } } } diff --git a/altoslib/AltosGreatCircle.java b/altoslib/AltosGreatCircle.java index 921356a5..f1cf0ae9 100644 --- a/altoslib/AltosGreatCircle.java +++ b/altoslib/AltosGreatCircle.java @@ -22,6 +22,8 @@ import java.lang.Math; public class AltosGreatCircle { public double distance; public double bearing; + public double range; + public double elevation; double sqr(double a) { return a * a; } @@ -54,9 +56,8 @@ public class AltosGreatCircle { return bearing_string[length][(int)((bearing / 90 * 8 + 1) / 2)%16]; } - public AltosGreatCircle (double start_lat, double start_lon, - double end_lat, double end_lon) - { + public AltosGreatCircle (double start_lat, double start_lon, double start_alt, + double end_lat, double end_lon, double end_alt) { double lat1 = rad * start_lat; double lon1 = rad * -start_lon; double lat2 = rad * end_lat; @@ -88,14 +89,25 @@ public class AltosGreatCircle { } distance = d * earth_radius; bearing = course * 180/Math.PI; + + double height_diff = end_alt - start_alt; + range = Math.sqrt(distance * distance + height_diff * height_diff); + elevation = Math.atan2(height_diff, distance) * 180 / Math.PI; + } + + public AltosGreatCircle (double start_lat, double start_lon, + double end_lat, double end_lon) { + this(start_lat, start_lon, 0, end_lat, end_lon, 0); } public AltosGreatCircle(AltosGPS start, AltosGPS end) { - this(start.lat, start.lon, end.lat, end.lon); + this(start.lat, start.lon, start.alt, end.lat, end.lon, end.alt); } public AltosGreatCircle() { distance = 0; bearing = 0; + range = 0; + elevation = 0; } } diff --git a/altoslib/AltosIdleMonitor.java b/altoslib/AltosIdleMonitor.java index f2f75bbb..2e4ddef2 100644 --- a/altoslib/AltosIdleMonitor.java +++ b/altoslib/AltosIdleMonitor.java @@ -29,6 +29,7 @@ public class AltosIdleMonitor extends Thread { double frequency; String callsign; AltosState previous_state; + AltosListenerState listener_state; AltosConfigData config_data; AltosGPS gps; @@ -51,11 +52,11 @@ public class AltosIdleMonitor extends Thread { } boolean has_sensor_mm(AltosConfigData config_data) { - return config_data.product.startsWith("MegaMetrum"); + return config_data.product.startsWith("TeleMega"); } boolean has_gps(AltosConfigData config_data) { - return config_data.product.startsWith("TeleMetrum") || config_data.product.startsWith("MegaMetrum"); + return config_data.product.startsWith("TeleMetrum") || config_data.product.startsWith("TeleMega"); } AltosRecord sensor_mm(AltosConfigData config_data) throws InterruptedException, TimeoutException { @@ -116,8 +117,10 @@ public class AltosIdleMonitor extends Thread { } finally { if (remote) { link.stop_remote(); - if (record != null) - record.rssi = AltosRSSI(); + if (record != null) { + record.rssi = link.rssi(); + listener_state.battery = link.monitor_battery(); + } } else { if (record != null) record.rssi = 0; @@ -137,7 +140,7 @@ public class AltosIdleMonitor extends Thread { } public void post_state() { - listener.update(state); + listener.update(state, listener_state); } public void abort() { @@ -172,5 +175,6 @@ public class AltosIdleMonitor extends Thread { link = in_link; remote = in_remote; state = null; + listener_state = new AltosListenerState(); } } diff --git a/altoslib/AltosIdleMonitorListener.java b/altoslib/AltosIdleMonitorListener.java index 7f58d61c..27e36dea 100644 --- a/altoslib/AltosIdleMonitorListener.java +++ b/altoslib/AltosIdleMonitorListener.java @@ -18,5 +18,5 @@ package org.altusmetrum.altoslib_1; public interface AltosIdleMonitorListener { - public void update(AltosState state); + public void update(AltosState state, AltosListenerState listener_state); }
\ No newline at end of file diff --git a/altoslib/AltosLib.java b/altoslib/AltosLib.java index 192da0a9..25d17e72 100644 --- a/altoslib/AltosLib.java +++ b/altoslib/AltosLib.java @@ -50,7 +50,7 @@ public class AltosLib { public static final int AO_LOG_SERIAL_NUMBER = 2002; public static final int AO_LOG_LOG_FORMAT = 2003; - /* Added for header fields in megametrum files */ + /* Added for header fields in telemega files */ public static final int AO_LOG_BARO_RESERVED = 3000; public static final int AO_LOG_BARO_SENS = 3001; public static final int AO_LOG_BARO_OFF = 3002; @@ -89,10 +89,11 @@ public class AltosLib { public final static int product_telelco = 0x0010; public final static int product_telescience = 0x0011; public final static int product_telepyro =0x0012; - public final static int product_megametrum = 0x0023; + public final static int product_telemega = 0x0023; public final static int product_megadongle = 0x0024; + public final static int product_telegps = 0x0025; public final static int product_altusmetrum_min = 0x000a; - public final static int product_altusmetrum_max = 0x0024; + public final static int product_altusmetrum_max = 0x0025; public final static int product_any = 0x10000; public final static int product_basestation = 0x10000 + 1; @@ -214,7 +215,7 @@ public class AltosLib { public static final int AO_LOG_FORMAT_TINY = 2; public static final int AO_LOG_FORMAT_TELEMETRY = 3; public static final int AO_LOG_FORMAT_TELESCIENCE = 4; - public static final int AO_LOG_FORMAT_MEGAMETRUM = 5; + public static final int AO_LOG_FORMAT_TELEMEGA = 5; public static final int AO_LOG_FORMAT_NONE = 127; public static boolean isspace(int c) { diff --git a/altoslib/AltosLink.java b/altoslib/AltosLink.java index 9eb25ce0..159ebfe1 100644 --- a/altoslib/AltosLink.java +++ b/altoslib/AltosLink.java @@ -364,6 +364,63 @@ public abstract class AltosLink implements Runnable { remote = false; } + public int rssi() throws TimeoutException, InterruptedException { + if (remote) + return 0; + printf("s\n"); + String line = get_reply_no_dialog(5000); + if (line == null) + throw new TimeoutException(); + String[] items = line.split("\\s+"); + if (items.length < 2) + return 0; + if (!items[0].equals("RSSI:")) + return 0; + int rssi = Integer.parseInt(items[1]); + return rssi; + } + + public String[] adc() throws TimeoutException, InterruptedException { + printf("a\n"); + for (;;) { + String line = get_reply_no_dialog(5000); + if (line == null) { + throw new TimeoutException(); + } + if (!line.startsWith("tick:")) + continue; + String[] items = line.split("\\s+"); + return items; + } + } + + public boolean has_monitor_battery() { + return config_data.has_monitor_battery(); + } + + public double monitor_battery() { + int monitor_batt = AltosRecord.MISSING; + + if (config_data.has_monitor_battery()) { + try { + String[] items = adc(); + for (int i = 0; i < items.length;) { + if (items[i].equals("batt")) { + monitor_batt = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + i++; + } + } catch (InterruptedException ie) { + } catch (TimeoutException te) { + } + } + if (monitor_batt == AltosRecord.MISSING) + return AltosRecord.MISSING; + return AltosConvert.cc_battery_to_voltage(monitor_batt); + } + public AltosLink() { callsign = ""; } diff --git a/altoslib/AltosListenerState.java b/altoslib/AltosListenerState.java new file mode 100644 index 00000000..2fb4673e --- /dev/null +++ b/altoslib/AltosListenerState.java @@ -0,0 +1,28 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.altoslib_1; + +public class AltosListenerState { + public int crc_errors; + public double battery; + + public AltosListenerState() { + crc_errors = 0; + battery = AltosRecord.MISSING; + } +} diff --git a/altoslib/AltosRecord.java b/altoslib/AltosRecord.java index f8c44cc5..07e910eb 100644 --- a/altoslib/AltosRecord.java +++ b/altoslib/AltosRecord.java @@ -17,7 +17,7 @@ package org.altusmetrum.altoslib_1; -public abstract class AltosRecord implements Comparable <AltosRecord>, Cloneable { +public class AltosRecord implements Comparable <AltosRecord>, Cloneable { public static final int seen_flight = 1; public static final int seen_sensor = 2; @@ -75,9 +75,9 @@ public abstract class AltosRecord implements Comparable <AltosRecord>, Cloneable * temperature: °C */ - abstract public double pressure(); - abstract public double ground_pressure(); - abstract public double acceleration(); + public double pressure() { return MISSING; } + public double ground_pressure() { return MISSING; } + public double acceleration() { return MISSING; } public double altitude() { double p = pressure(); @@ -126,7 +126,11 @@ public abstract class AltosRecord implements Comparable <AltosRecord>, Cloneable return tick - o.tick; } - abstract public AltosRecord clone(); + public AltosRecord clone() { + AltosRecord n = new AltosRecord(); + n.copy(this); + return n; + } public void copy(AltosRecord old) { seen = old.seen; @@ -150,13 +154,13 @@ public abstract class AltosRecord implements Comparable <AltosRecord>, Cloneable seen = 0; version = 0; callsign = "N0CALL"; - serial = 0; - flight = 0; + serial = MISSING; + flight = MISSING; rssi = 0; status = 0; state = AltosLib.ao_flight_startup; tick = 0; - gps = new AltosGPS(); + gps = null; new_gps = false; companion = null; diff --git a/altoslib/AltosSensorMM.java b/altoslib/AltosSensorMM.java index 8372d047..6d1b61c0 100644 --- a/altoslib/AltosSensorMM.java +++ b/altoslib/AltosSensorMM.java @@ -28,75 +28,65 @@ class AltosSensorMM { int accel_ref; public AltosSensorMM(AltosLink link) throws InterruptedException, TimeoutException { - link.printf("a\n"); - for (;;) { - String line = link.get_reply_no_dialog(5000); - if (line == null) { - throw new TimeoutException(); + String[] items = link.adc(); + sense = new int[6]; + for (int i = 0; i < items.length;) { + if (items[i].equals("tick:")) { + tick = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("0:")) { + sense[0] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("1:")) { + sense[1] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("2:")) { + sense[2] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("3:")) { + sense[3] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("4:")) { + sense[4] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("5:")) { + sense[5] = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("6:")) { + v_batt = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("7:")) { + v_pyro = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("8:")) { + accel = Integer.parseInt(items[i+1]); + i += 2; + continue; } - if (!line.startsWith("tick:")) + if (items[i].equals("9:")) { + accel_ref = Integer.parseInt(items[i+1]); + i += 2; continue; - String[] items = line.split("\\s+"); - sense = new int[6]; - for (int i = 0; i < items.length;) { - if (items[i].equals("tick:")) { - tick = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("0:")) { - sense[0] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("1:")) { - sense[1] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("2:")) { - sense[2] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("3:")) { - sense[3] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("4:")) { - sense[4] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("5:")) { - sense[5] = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("6:")) { - v_batt = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("7:")) { - v_pyro = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("8:")) { - accel = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("9:")) { - accel_ref = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - i++; } - break; + i++; } } } diff --git a/altoslib/AltosSensorTM.java b/altoslib/AltosSensorTM.java index f5fa83a5..754dc5bb 100644 --- a/altoslib/AltosSensorTM.java +++ b/altoslib/AltosSensorTM.java @@ -23,54 +23,44 @@ class AltosSensorTM extends AltosRecordTM { public AltosSensorTM(AltosLink link, AltosConfigData config_data) throws InterruptedException, TimeoutException { super(); - link.printf("a\n"); - for (;;) { - String line = link.get_reply_no_dialog(5000); - if (line == null) { - throw new TimeoutException(); + String[] items = link.adc(); + for (int i = 0; i < items.length;) { + if (items[i].equals("tick:")) { + tick = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("accel:")) { + accel = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("pres:")) { + pres = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("temp:")) { + temp = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("batt:")) { + batt = Integer.parseInt(items[i+1]); + i += 2; + continue; + } + if (items[i].equals("drogue:")) { + drogue = Integer.parseInt(items[i+1]); + i += 2; + continue; } - if (!line.startsWith("tick:")) + if (items[i].equals("main:")) { + main = Integer.parseInt(items[i+1]); + i += 2; continue; - String[] items = line.split("\\s+"); - for (int i = 0; i < items.length;) { - if (items[i].equals("tick:")) { - tick = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("accel:")) { - accel = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("pres:")) { - pres = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("temp:")) { - temp = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("batt:")) { - batt = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("drogue:")) { - drogue = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - if (items[i].equals("main:")) { - main = Integer.parseInt(items[i+1]); - i += 2; - continue; - } - i++; } - break; + i++; } ground_accel = config_data.accel_cal_plus; ground_pres = pres; diff --git a/altoslib/AltosState.java b/altoslib/AltosState.java index 5598a603..a3b9a8c0 100644 --- a/altoslib/AltosState.java +++ b/altoslib/AltosState.java @@ -98,12 +98,20 @@ public class AltosState { ground_altitude = data.ground_altitude(); altitude = data.altitude(); + if (altitude == AltosRecord.MISSING && data.gps != null) + altitude = data.gps.alt; + height = AltosRecord.MISSING; if (data.kalman_height != AltosRecord.MISSING) height = data.kalman_height; else { - if (prev_state != null) - height = (prev_state.height * 15 + altitude - ground_altitude) / 16.0; + if (altitude != AltosRecord.MISSING && ground_altitude != AltosRecord.MISSING) { + double cur_height = altitude - ground_altitude; + if (prev_state == null || prev_state.height == AltosRecord.MISSING) + height = cur_height; + else + height = (prev_state.height * 15 + cur_height) / 16.0; + } } report_time = System.currentTimeMillis(); @@ -147,38 +155,51 @@ public class AltosState { /* compute barometric speed */ double height_change = height - prev_state.height; + + double prev_baro_speed = prev_state.baro_speed; + if (prev_baro_speed == AltosRecord.MISSING) + prev_baro_speed = 0; + if (time_change > 0) - baro_speed = (prev_state.baro_speed * 3 + (height_change / time_change)) / 4.0; + baro_speed = (prev_baro_speed * 3 + (height_change / time_change)) / 4.0; else baro_speed = prev_state.baro_speed; + double prev_accel_speed = prev_state.accel_speed; + + if (prev_accel_speed == AltosRecord.MISSING) + prev_accel_speed = 0; + if (acceleration == AltosRecord.MISSING) { /* Fill in mising acceleration value */ accel_speed = baro_speed; - if (time_change > 0) - acceleration = (accel_speed - prev_state.accel_speed) / time_change; + + if (time_change > 0 && accel_speed != AltosRecord.MISSING) + acceleration = (accel_speed - prev_accel_speed) / time_change; else acceleration = prev_state.acceleration; } else { /* compute accelerometer speed */ - accel_speed = prev_state.accel_speed + acceleration * time_change; + accel_speed = prev_accel_speed + acceleration * time_change; } } - } else { npad = 0; ngps = 0; gps = null; - baro_speed = 0; - accel_speed = 0; + baro_speed = AltosRecord.MISSING; + accel_speed = AltosRecord.MISSING; + pad_alt = AltosRecord.MISSING; + max_baro_speed = 0; + max_accel_speed = 0; + max_height = 0; + max_acceleration = 0; time_change = 0; - if (acceleration == AltosRecord.MISSING) - acceleration = 0; } time = tick / 100.0; - if (cur.new_gps && (state == AltosLib.ao_flight_pad || state == AltosLib.ao_flight_idle)) { + if (cur.new_gps && (state < AltosLib.ao_flight_boost)) { /* Track consecutive 'good' gps reports, waiting for 10 of them */ if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) @@ -188,7 +209,7 @@ public class AltosState { /* Average GPS data while on the pad */ if (data.gps != null && data.gps.locked && data.gps.nsat >= 4) { - if (ngps > 1) { + if (ngps > 1 && state == AltosLib.ao_flight_pad) { /* filter pad position */ pad_lat = (pad_lat * 31.0 + data.gps.lat) / 32.0; pad_lon = (pad_lon * 31.0 + data.gps.lon) / 32.0; @@ -201,7 +222,7 @@ public class AltosState { ngps++; } } else { - if (ngps == 0) + if (ngps == 0 && ground_altitude != AltosRecord.MISSING) pad_alt = ground_altitude; } @@ -218,32 +239,30 @@ public class AltosState { boost = (AltosLib.ao_flight_boost == state); /* Only look at accelerometer data under boost */ - if (boost && acceleration > max_acceleration && acceleration != AltosRecord.MISSING) + if (boost && acceleration != AltosRecord.MISSING && (max_acceleration == AltosRecord.MISSING || acceleration > max_acceleration)) max_acceleration = acceleration; - if (boost && accel_speed > max_accel_speed && accel_speed != AltosRecord.MISSING) + if (boost && accel_speed != AltosRecord.MISSING && accel_speed > max_accel_speed) max_accel_speed = accel_speed; - if (boost && baro_speed > max_baro_speed && baro_speed != AltosRecord.MISSING) + if (boost && baro_speed != AltosRecord.MISSING && baro_speed > max_baro_speed) max_baro_speed = baro_speed; - if (height > max_height && height != AltosRecord.MISSING) + if (height != AltosRecord.MISSING && height > max_height) max_height = height; + elevation = 0; + range = -1; + gps_height = 0; if (data.gps != null) { if (gps == null || !gps.locked || data.gps.locked) gps = data.gps; if (ngps > 0 && gps.locked) { - from_pad = new AltosGreatCircle(pad_lat, pad_lon, gps.lat, gps.lon); - } - } - elevation = 0; - range = -1; - if (ngps > 0) { - gps_height = gps.alt - pad_alt; - if (from_pad != null) { - elevation = Math.atan2(height, from_pad.distance) * 180 / Math.PI; - range = Math.sqrt(height * height + from_pad.distance * from_pad.distance); + double h = height; + + if (h == AltosRecord.MISSING) h = 0; + from_pad = new AltosGreatCircle(pad_lat, pad_lon, 0, gps.lat, gps.lon, h); + elevation = from_pad.elevation; + range = from_pad.range; + gps_height = gps.alt - pad_alt; } - } else { - gps_height = 0; } } diff --git a/altoslib/AltosTelemetryReader.java b/altoslib/AltosTelemetryReader.java index f365b821..b4293c73 100644 --- a/altoslib/AltosTelemetryReader.java +++ b/altoslib/AltosTelemetryReader.java @@ -107,6 +107,14 @@ public class AltosTelemetryReader extends AltosFlightReader { return log.file(); } + public boolean has_monitor_battery() { + return link.has_monitor_battery(); + } + + public double monitor_battery() { + return link.monitor_battery(); + } + public AltosTelemetryReader (AltosLink in_link) throws IOException, InterruptedException, TimeoutException { link = in_link; diff --git a/altoslib/AltosTelemetryRecord.java b/altoslib/AltosTelemetryRecord.java index 01215968..fdc3c88e 100644 --- a/altoslib/AltosTelemetryRecord.java +++ b/altoslib/AltosTelemetryRecord.java @@ -80,25 +80,25 @@ public abstract class AltosTelemetryRecord { r = new AltosTelemetryRecordSensor(bytes, rssi); break; case packet_type_configuration: - r = new AltosTelemetryRecordConfiguration(bytes); + r = new AltosTelemetryRecordConfiguration(bytes, rssi); break; case packet_type_location: - r = new AltosTelemetryRecordLocation(bytes); + r = new AltosTelemetryRecordLocation(bytes, rssi); break; case packet_type_satellite: - r = new AltosTelemetryRecordSatellite(bytes); + r = new AltosTelemetryRecordSatellite(bytes, rssi); break; case packet_type_companion: - r = new AltosTelemetryRecordCompanion(bytes); + r = new AltosTelemetryRecordCompanion(bytes, rssi); break; case packet_type_MM_sensor: r = new AltosTelemetryRecordMegaSensor(bytes, rssi); break; case packet_type_MM_data: - r = new AltosTelemetryRecordMegaData(bytes); + r = new AltosTelemetryRecordMegaData(bytes, rssi); break; default: - r = new AltosTelemetryRecordRaw(bytes); + r = new AltosTelemetryRecordRaw(bytes, rssi); break; } break; diff --git a/altoslib/AltosTelemetryRecordCompanion.java b/altoslib/AltosTelemetryRecordCompanion.java index e016dd01..2231df13 100644 --- a/altoslib/AltosTelemetryRecordCompanion.java +++ b/altoslib/AltosTelemetryRecordCompanion.java @@ -21,8 +21,8 @@ public class AltosTelemetryRecordCompanion extends AltosTelemetryRecordRaw { AltosRecordCompanion companion; - public AltosTelemetryRecordCompanion(int[] in_bytes) { - super(in_bytes); + public AltosTelemetryRecordCompanion(int[] in_bytes, int rssi) { + super(in_bytes, rssi); int off = 0; if (uint8(6) == 0) diff --git a/altoslib/AltosTelemetryRecordConfiguration.java b/altoslib/AltosTelemetryRecordConfiguration.java index 472a6318..47fc3488 100644 --- a/altoslib/AltosTelemetryRecordConfiguration.java +++ b/altoslib/AltosTelemetryRecordConfiguration.java @@ -29,8 +29,8 @@ public class AltosTelemetryRecordConfiguration extends AltosTelemetryRecordRaw { String callsign; String version; - public AltosTelemetryRecordConfiguration(int[] in_bytes) { - super(in_bytes); + public AltosTelemetryRecordConfiguration(int[] in_bytes, int rssi) { + super(in_bytes, rssi); device_type = uint8(5); flight = uint16(6); diff --git a/altoslib/AltosTelemetryRecordLocation.java b/altoslib/AltosTelemetryRecordLocation.java index 469a5400..02999696 100644 --- a/altoslib/AltosTelemetryRecordLocation.java +++ b/altoslib/AltosTelemetryRecordLocation.java @@ -37,8 +37,8 @@ public class AltosTelemetryRecordLocation extends AltosTelemetryRecordRaw { int climb_rate; int course; - public AltosTelemetryRecordLocation(int[] in_bytes) { - super(in_bytes); + public AltosTelemetryRecordLocation(int[] in_bytes, int rssi) { + super(in_bytes, rssi); flags = uint8(5); altitude = int16(6); diff --git a/altoslib/AltosTelemetryRecordMegaData.java b/altoslib/AltosTelemetryRecordMegaData.java index 08df9ee1..a484ef4e 100644 --- a/altoslib/AltosTelemetryRecordMegaData.java +++ b/altoslib/AltosTelemetryRecordMegaData.java @@ -35,8 +35,8 @@ public class AltosTelemetryRecordMegaData extends AltosTelemetryRecordRaw { int speed; int height; - public AltosTelemetryRecordMegaData(int[] in_bytes) { - super(in_bytes); + public AltosTelemetryRecordMegaData(int[] in_bytes, int rssi) { + super(in_bytes, rssi); state = int8(5); diff --git a/altoslib/AltosTelemetryRecordMegaSensor.java b/altoslib/AltosTelemetryRecordMegaSensor.java index 7548d699..2a4b17a4 100644 --- a/altoslib/AltosTelemetryRecordMegaSensor.java +++ b/altoslib/AltosTelemetryRecordMegaSensor.java @@ -35,10 +35,8 @@ public class AltosTelemetryRecordMegaSensor extends AltosTelemetryRecordRaw { int mag_y; int mag_z; - int rssi; - - public AltosTelemetryRecordMegaSensor(int[] in_bytes, int in_rssi) { - super(in_bytes); + public AltosTelemetryRecordMegaSensor(int[] in_bytes, int rssi) { + super(in_bytes, rssi); accel = int16(6); pres = int32(8); @@ -55,8 +53,6 @@ public class AltosTelemetryRecordMegaSensor extends AltosTelemetryRecordRaw { mag_x = int16(26); mag_y = int16(28); mag_z = int16(30); - - rssi = in_rssi; } public AltosRecord update_state(AltosRecord previous) { @@ -85,8 +81,6 @@ public class AltosTelemetryRecordMegaSensor extends AltosTelemetryRecordRaw { next.mag.y = mag_y; next.mag.z = mag_z; - next.rssi = rssi; - next.seen |= AltosRecord.seen_sensor; return next; diff --git a/altoslib/AltosTelemetryRecordRaw.java b/altoslib/AltosTelemetryRecordRaw.java index a06348c1..f94789bb 100644 --- a/altoslib/AltosTelemetryRecordRaw.java +++ b/altoslib/AltosTelemetryRecordRaw.java @@ -22,6 +22,7 @@ public class AltosTelemetryRecordRaw extends AltosTelemetryRecord { int serial; int tick; int type; + int rssi; long received_time; @@ -53,11 +54,12 @@ public class AltosTelemetryRecordRaw extends AltosTelemetryRecord { return AltosLib.string(bytes, off + 1, l); } - public AltosTelemetryRecordRaw(int[] in_bytes) { + public AltosTelemetryRecordRaw(int[] in_bytes, int in_rssi) { bytes = in_bytes; serial = uint16(0); tick = uint16(2); type = uint8(4); + rssi = in_rssi; } public AltosRecord update_state(AltosRecord previous) { @@ -69,6 +71,7 @@ public class AltosTelemetryRecordRaw extends AltosTelemetryRecord { next = new AltosRecordNone(); next.serial = serial; next.tick = tick; + next.rssi = rssi; return next; } diff --git a/altoslib/AltosTelemetryRecordSatellite.java b/altoslib/AltosTelemetryRecordSatellite.java index 3e93b337..9835389b 100644 --- a/altoslib/AltosTelemetryRecordSatellite.java +++ b/altoslib/AltosTelemetryRecordSatellite.java @@ -21,8 +21,8 @@ public class AltosTelemetryRecordSatellite extends AltosTelemetryRecordRaw { int channels; AltosGPSSat[] sats; - public AltosTelemetryRecordSatellite(int[] in_bytes) { - super(in_bytes); + public AltosTelemetryRecordSatellite(int[] in_bytes, int rssi) { + super(in_bytes, rssi); channels = uint8(5); if (channels > 12) diff --git a/altoslib/AltosTelemetryRecordSensor.java b/altoslib/AltosTelemetryRecordSensor.java index 767a464a..e0e92c13 100644 --- a/altoslib/AltosTelemetryRecordSensor.java +++ b/altoslib/AltosTelemetryRecordSensor.java @@ -36,10 +36,8 @@ public class AltosTelemetryRecordSensor extends AltosTelemetryRecordRaw { int accel_plus_g; int accel_minus_g; - int rssi; - - public AltosTelemetryRecordSensor(int[] in_bytes, int in_rssi) { - super(in_bytes); + public AltosTelemetryRecordSensor(int[] in_bytes, int rssi) { + super(in_bytes, rssi); state = uint8(5); accel = int16(6); @@ -57,8 +55,6 @@ public class AltosTelemetryRecordSensor extends AltosTelemetryRecordRaw { ground_accel = int16(26); accel_plus_g = int16(28); accel_minus_g = int16(30); - - rssi = in_rssi; } public AltosRecord update_state(AltosRecord prev) { @@ -101,8 +97,6 @@ public class AltosTelemetryRecordSensor extends AltosTelemetryRecordRaw { next.accel_minus_g = AltosRecord.MISSING; } - next.rssi = rssi; - next.seen |= AltosRecord.seen_sensor | AltosRecord.seen_temp_volt; return next; diff --git a/altoslib/Makefile.am b/altoslib/Makefile.am index 5d6a5c08..18b028d6 100644 --- a/altoslib/Makefile.am +++ b/altoslib/Makefile.am @@ -41,6 +41,7 @@ altoslib_JAVA = \ AltosIMUQuery.java \ AltosLine.java \ AltosLink.java \ + AltosListenerState.java \ AltosLog.java \ AltosMs5607.java \ AltosMs5607Query.java \ diff --git a/altosui/AltosAscent.java b/altosui/AltosAscent.java index e90e0e23..4da4d591 100644 --- a/altosui/AltosAscent.java +++ b/altosui/AltosAscent.java @@ -42,7 +42,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { label.setVisible(false); } - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void show(String s) { show(); @@ -107,7 +107,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { public class AscentValue { JLabel label; JTextField value; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void reset() { value.setText(""); @@ -174,7 +174,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { JTextField max_value; double max; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void reset() { value.setText(""); @@ -239,7 +239,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { class Height extends AscentValueHold { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.height, state.height); } public Height (GridBagLayout layout, int y) { @@ -250,7 +250,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { Height height; class Speed extends AscentValueHold { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { double speed = state.accel_speed; if (!state.ascent) speed = state.baro_speed; @@ -264,7 +264,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { Speed speed; class Accel extends AscentValueHold { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.accel, state.acceleration); } public Accel (GridBagLayout layout, int y) { @@ -286,7 +286,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { } class Apogee extends AscentStatus { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show("%4.2f V", state.drogue_sense); lights.set(state.drogue_sense > 3.2); } @@ -298,7 +298,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { Apogee apogee; class Main extends AscentStatus { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show("%4.2f V", state.main_sense); lights.set(state.main_sense > 3.2); } @@ -310,7 +310,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { Main main; class Lat extends AscentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.gps != null) show(pos(state.gps.lat,"N", "S")); else @@ -324,7 +324,7 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { Lat lat; class Lon extends AscentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.gps != null) show(pos(state.gps.lon,"E", "W")); else @@ -359,25 +359,25 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { accel.set_font(); } - public void show(AltosState state, int crc_errors) { + public void show(AltosState state, AltosListenerState listener_state) { if (state.gps != null && state.gps.connected) { - lat.show(state, crc_errors); - lon.show(state, crc_errors); + lat.show(state, listener_state); + lon.show(state, listener_state); } else { lat.hide(); lon.hide(); } - height.show(state, crc_errors); + height.show(state, listener_state); if (state.main_sense != AltosRecord.MISSING) - main.show(state, crc_errors); + main.show(state, listener_state); else main.hide(); if (state.drogue_sense != AltosRecord.MISSING) - apogee.show(state, crc_errors); + apogee.show(state, listener_state); else apogee.hide(); - speed.show(state, crc_errors); - accel.show(state, crc_errors); + speed.show(state, listener_state); + accel.show(state, listener_state); } public void labels(GridBagLayout layout, int y) { @@ -398,6 +398,10 @@ public class AltosAscent extends JComponent implements AltosFlightDisplay { add(max); } + public String getName() { + return "Ascent"; + } + public AltosAscent() { layout = new GridBagLayout(); diff --git a/altosui/AltosCompanionInfo.java b/altosui/AltosCompanionInfo.java index 7dd36aec..ebe1d1f9 100644 --- a/altosui/AltosCompanionInfo.java +++ b/altosui/AltosCompanionInfo.java @@ -83,7 +83,7 @@ public class AltosCompanionInfo extends JTable { } } - public void show(AltosState state, int crc_errors) { + public void show(AltosState state, AltosListenerState listener_state) { if (state == null) return; if (state.data.companion != null) diff --git a/altosui/AltosConfigUI.java b/altosui/AltosConfigUI.java index 4fd0647e..11f40593 100644 --- a/altosui/AltosConfigUI.java +++ b/altosui/AltosConfigUI.java @@ -719,6 +719,7 @@ public class AltosConfigUI public void set_main_deploy(int new_main_deploy) { main_deploy_value.setSelectedItem(Integer.toString(new_main_deploy)); + main_deploy_value.setEnabled(new_main_deploy >= 0); } public int main_deploy() { diff --git a/altosui/AltosDataChooser.java b/altosui/AltosDataChooser.java index 7de18afb..f914f138 100644 --- a/altosui/AltosDataChooser.java +++ b/altosui/AltosDataChooser.java @@ -75,7 +75,7 @@ public class AltosDataChooser extends JFileChooser { "eeprom")); setFileFilter(new FileNameExtensionFilter("Telemetry file", "telem")); - setFileFilter(new FileNameExtensionFilter("MegaMetrum eeprom file", + setFileFilter(new FileNameExtensionFilter("TeleMega eeprom file", "mega")); setFileFilter(new FileNameExtensionFilter("Flight data file", "telem", "eeprom", "mega")); diff --git a/altosui/AltosDescent.java b/altosui/AltosDescent.java index 821e3963..29d33ddc 100644 --- a/altosui/AltosDescent.java +++ b/altosui/AltosDescent.java @@ -29,7 +29,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { JTextField value; AltosLights lights; - abstract void show(AltosState state, int crc_errors); + abstract void show(AltosState state, AltosListenerState listener_state); void show() { label.setVisible(true); @@ -108,7 +108,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { value.setText(""); } - abstract void show(AltosState state, int crc_errors); + abstract void show(AltosState state, AltosListenerState listener_state); void show() { label.setVisible(true); @@ -192,7 +192,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { value2.setFont(Altos.value_font); } - abstract void show(AltosState state, int crc_errors); + abstract void show(AltosState state, AltosListenerState listener_state); void show(String v1, String v2) { show(); @@ -244,7 +244,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { } class Height extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.height, state.height); } public Height (GridBagLayout layout, int x, int y) { @@ -255,7 +255,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Height height; class Speed extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { double speed = state.accel_speed; if (!state.ascent) speed = state.baro_speed; @@ -280,7 +280,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { } class Lat extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.gps != null && state.gps.connected) show(pos(state.gps.lat,"N", "S")); else @@ -294,7 +294,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Lat lat; class Lon extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.gps != null && state.gps.connected) show(pos(state.gps.lon,"W", "E")); else @@ -308,7 +308,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Lon lon; class Distance extends DescentValue { - void show(AltosState state, int crc_errors) { + void show(AltosState state, AltosListenerState listener_state) { if (state.from_pad != null) show(AltosConvert.distance, state.from_pad.distance); else @@ -324,7 +324,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { class Apogee extends DescentStatus { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show("%4.2f V", state.drogue_sense); lights.set(state.drogue_sense > 3.2); } @@ -336,7 +336,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Apogee apogee; class Main extends DescentStatus { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show("%4.2f V", state.main_sense); lights.set(state.main_sense > 3.2); } @@ -348,7 +348,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Main main; class Bearing extends DescentDualValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.from_pad != null) { show( String.format("%3.0f°", state.from_pad.bearing), state.from_pad.bearing_words( @@ -365,7 +365,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Bearing bearing; class Range extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.distance, state.range); } public Range (GridBagLayout layout, int x, int y) { @@ -376,7 +376,7 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { Range range; class Elevation extends DescentValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show("%3.0f°", state.elevation); } public Elevation (GridBagLayout layout, int x, int y) { @@ -412,16 +412,16 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { apogee.set_font(); } - public void show(AltosState state, int crc_errors) { - height.show(state, crc_errors); - speed.show(state, crc_errors); + public void show(AltosState state, AltosListenerState listener_state) { + height.show(state, listener_state); + speed.show(state, listener_state); if (state.gps != null && state.gps.connected) { - bearing.show(state, crc_errors); - range.show(state, crc_errors); - distance.show(state, crc_errors); - elevation.show(state, crc_errors); - lat.show(state, crc_errors); - lon.show(state, crc_errors); + bearing.show(state, listener_state); + range.show(state, listener_state); + distance.show(state, listener_state); + elevation.show(state, listener_state); + lat.show(state, listener_state); + lon.show(state, listener_state); } else { bearing.hide(); range.hide(); @@ -431,15 +431,19 @@ public class AltosDescent extends JComponent implements AltosFlightDisplay { lon.hide(); } if (state.main_sense != AltosRecord.MISSING) - main.show(state, crc_errors); + main.show(state, listener_state); else main.hide(); if (state.drogue_sense != AltosRecord.MISSING) - apogee.show(state, crc_errors); + apogee.show(state, listener_state); else apogee.hide(); } + public String getName() { + return "Descent"; + } + public AltosDescent() { layout = new GridBagLayout(); diff --git a/altosui/AltosDisplayThread.java b/altosui/AltosDisplayThread.java index 6f8aa9ee..095bed99 100644 --- a/altosui/AltosDisplayThread.java +++ b/altosui/AltosDisplayThread.java @@ -29,21 +29,17 @@ public class AltosDisplayThread extends Thread { IdleThread idle_thread; AltosVoice voice; AltosFlightReader reader; - int crc_errors; + AltosState old_state, state; + AltosListenerState listener_state; AltosFlightDisplay display; - void show_internal(AltosState state, int crc_errors) { - if (state != null) - display.show(state, crc_errors); - } - - void show_safely(AltosState in_state, int in_crc_errors) { - final AltosState state = in_state; - final int crc_errors = in_crc_errors; + synchronized void show_safely() { + final AltosState my_state = state; + final AltosListenerState my_listener_state = listener_state; Runnable r = new Runnable() { public void run() { try { - show_internal(state, crc_errors); + display.show(my_state, my_listener_state); } catch (Exception ex) { } } @@ -73,7 +69,6 @@ public class AltosDisplayThread extends Thread { class IdleThread extends Thread { boolean started; - private AltosState state; int reported_landing; int report_interval; long report_time; @@ -129,7 +124,7 @@ public class AltosDisplayThread extends Thread { ++reported_landing; if (state.state != Altos.ao_flight_landed) { state.state = Altos.ao_flight_landed; - show_safely(state, 0); + show_safely(); } } } @@ -145,6 +140,10 @@ public class AltosDisplayThread extends Thread { public void run () { try { for (;;) { + if (reader.has_monitor_battery()) { + listener_state.battery = reader.monitor_battery(); + show_safely(); + } set_report_time(); for (;;) { voice.drain(); @@ -155,6 +154,7 @@ public class AltosDisplayThread extends Thread { wait(sleep_time); } } + report(false); } } catch (InterruptedException ie) { @@ -164,18 +164,7 @@ public class AltosDisplayThread extends Thread { } } - public synchronized void notice(AltosState new_state, boolean spoken) { - AltosState old_state = state; - state = new_state; - if (!started && state.state > Altos.ao_flight_pad) { - started = true; - start(); - } - - if (state.state < Altos.ao_flight_drogue) - report_interval = 10000; - else - report_interval = 20000; + public synchronized void notice(boolean spoken) { if (old_state != null && old_state.state != state.state) { report_time = now(); this.notify(); @@ -184,13 +173,12 @@ public class AltosDisplayThread extends Thread { } public IdleThread() { - state = null; reported_landing = 0; report_interval = 10000; } } - boolean tell(AltosState state, AltosState old_state) { + synchronized boolean tell() { boolean ret = false; if (old_state == null || old_state.state != state.state) { voice.speak(state.data.state()); @@ -222,12 +210,10 @@ public class AltosDisplayThread extends Thread { public void run() { boolean interrupted = false; - //String line; - AltosState state = null; - AltosState old_state = null; boolean told; idle_thread = new IdleThread(); + idle_thread.start(); try { for (;;) { @@ -238,14 +224,14 @@ public class AltosDisplayThread extends Thread { old_state = state; state = new AltosState(record, state); reader.update(state); - show_safely(state, crc_errors); - told = tell(state, old_state); - idle_thread.notice(state, told); + show_safely(); + told = tell(); + idle_thread.notice(told); } catch (ParseException pp) { System.out.printf("Parse error: %d \"%s\"\n", pp.getErrorOffset(), pp.getMessage()); } catch (AltosCRCException ce) { - ++crc_errors; - show_safely(state, crc_errors); + ++listener_state.crc_errors; + show_safely(); } } } catch (InterruptedException ee) { @@ -264,6 +250,7 @@ public class AltosDisplayThread extends Thread { } public AltosDisplayThread(Frame in_parent, AltosVoice in_voice, AltosFlightDisplay in_display, AltosFlightReader in_reader) { + listener_state = new AltosListenerState(); parent = in_parent; voice = in_voice; display = in_display; diff --git a/altosui/AltosEepromDownload.java b/altosui/AltosEepromDownload.java index 801d4ec0..a0523b58 100644 --- a/altosui/AltosEepromDownload.java +++ b/altosui/AltosEepromDownload.java @@ -366,7 +366,7 @@ public class AltosEepromDownload implements Runnable { extension = "science"; CaptureTeleScience(eechunk); break; - case AltosLib.AO_LOG_FORMAT_MEGAMETRUM: + case AltosLib.AO_LOG_FORMAT_TELEMEGA: extension = "mega"; CaptureMega(eechunk); } diff --git a/altosui/AltosFlightDisplay.java b/altosui/AltosFlightDisplay.java index d1ed7d2f..4f4c158e 100644 --- a/altosui/AltosFlightDisplay.java +++ b/altosui/AltosFlightDisplay.java @@ -22,7 +22,7 @@ import org.altusmetrum.altoslib_1.*; public interface AltosFlightDisplay { void reset(); - void show(AltosState state, int crc_errors); + void show(AltosState state, AltosListenerState listener_state); void set_font(); } diff --git a/altosui/AltosFlightStats.java b/altosui/AltosFlightStats.java index da06bb3d..dee31a8d 100644 --- a/altosui/AltosFlightStats.java +++ b/altosui/AltosFlightStats.java @@ -150,7 +150,7 @@ public class AltosFlightStats { max_speed = state.max_baro_speed; max_acceleration = state.max_acceleration; } - if (state.gps.locked && state.gps.nsat >= 4) { + if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) { if (state.state <= Altos.ao_flight_pad) { pad_lat = state.gps.lat; pad_lon = state.gps.lon; diff --git a/altosui/AltosFlightStatus.java b/altosui/AltosFlightStatus.java index 20539a9f..d2910414 100644 --- a/altosui/AltosFlightStatus.java +++ b/altosui/AltosFlightStatus.java @@ -28,7 +28,7 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay JLabel label; JTextField value; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void reset() { value.setText(""); @@ -64,7 +64,7 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay } class Call extends FlightValue { - void show(AltosState state, int crc_errors) { + void show(AltosState state, AltosListenerState listener_state) { value.setText(state.data.callsign); } public Call (GridBagLayout layout, int x) { @@ -75,8 +75,11 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay Call call; class Serial extends FlightValue { - void show(AltosState state, int crc_errors) { - value.setText(String.format("%d", state.data.serial)); + void show(AltosState state, AltosListenerState listener_state) { + if (state.data.serial == AltosRecord.MISSING) + value.setText("none"); + else + value.setText(String.format("%d", state.data.serial)); } public Serial (GridBagLayout layout, int x) { super (layout, x, "Serial"); @@ -86,8 +89,11 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay Serial serial; class Flight extends FlightValue { - void show(AltosState state, int crc_errors) { - value.setText(String.format("%d", state.data.flight)); + void show(AltosState state, AltosListenerState listener_state) { + if (state.data.flight == AltosRecord.MISSING) + value.setText("none"); + else + value.setText(String.format("%d", state.data.flight)); } public Flight (GridBagLayout layout, int x) { super (layout, x, "Flight"); @@ -97,7 +103,7 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay Flight flight; class FlightState extends FlightValue { - void show(AltosState state, int crc_errors) { + void show(AltosState state, AltosListenerState listener_state) { value.setText(state.data.state()); } public FlightState (GridBagLayout layout, int x) { @@ -108,7 +114,7 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay FlightState flight_state; class RSSI extends FlightValue { - void show(AltosState state, int crc_errors) { + void show(AltosState state, AltosListenerState listener_state) { value.setText(String.format("%d", state.data.rssi)); } public RSSI (GridBagLayout layout, int x) { @@ -119,7 +125,7 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay RSSI rssi; class LastPacket extends FlightValue { - void show(AltosState state, int crc_errors) { + void show(AltosState state, AltosListenerState listener_state) { long secs = (System.currentTimeMillis() - state.report_time + 500) / 1000; value.setText(String.format("%d", secs)); } @@ -148,13 +154,13 @@ public class AltosFlightStatus extends JComponent implements AltosFlightDisplay last_packet.set_font(); } - public void show (AltosState state, int crc_errors) { - call.show(state, crc_errors); - serial.show(state, crc_errors); - flight.show(state, crc_errors); - flight_state.show(state, crc_errors); - rssi.show(state, crc_errors); - last_packet.show(state, crc_errors); + public void show (AltosState state, AltosListenerState listener_state) { + call.show(state, listener_state); + serial.show(state, listener_state); + flight.show(state, listener_state); + flight_state.show(state, listener_state); + rssi.show(state, listener_state); + last_packet.show(state, listener_state); } public int height() { diff --git a/altosui/AltosFlightStatusUpdate.java b/altosui/AltosFlightStatusUpdate.java index bf679b85..962a08f7 100644 --- a/altosui/AltosFlightStatusUpdate.java +++ b/altosui/AltosFlightStatusUpdate.java @@ -22,12 +22,16 @@ import org.altusmetrum.altoslib_1.*; public class AltosFlightStatusUpdate implements ActionListener { - public AltosState saved_state; - AltosFlightStatus flightStatus; + public AltosState saved_state; + public AltosListenerState saved_listener_state; + AltosFlightStatus flightStatus; public void actionPerformed (ActionEvent e) { - if (saved_state != null) - flightStatus.show(saved_state, 0); + if (saved_state != null) { + if (saved_listener_state == null) + saved_listener_state = new AltosListenerState(); + flightStatus.show(saved_state, saved_listener_state); + } } public AltosFlightStatusUpdate (AltosFlightStatus in_flightStatus) { diff --git a/altosui/AltosFlightUI.java b/altosui/AltosFlightUI.java index c8faab90..6d010d23 100644 --- a/altosui/AltosFlightUI.java +++ b/altosui/AltosFlightUI.java @@ -39,6 +39,7 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay, A AltosSiteMap sitemap; boolean has_map; boolean has_companion; + boolean has_state; private AltosFlightStatus flightStatus; private AltosInfoTable flightInfo; @@ -97,29 +98,44 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay, A AltosFlightStatusUpdate status_update; - public void show(AltosState state, int crc_errors) { + public void show(AltosState state, AltosListenerState listener_state) { status_update.saved_state = state; + + if (state == null) + state = new AltosState(new AltosRecord()); + + pad.show(state, listener_state); + + if (state.state != Altos.ao_flight_startup) { + if (!has_state) { + pane.setTitleAt(0, "Launch Pad"); + pane.add(ascent, 1); + pane.add(descent, 2); + pane.add(landed, 3); + has_state = true; + } + } + + ascent.show(state, listener_state); + descent.show(state, listener_state); + landed.show(state, listener_state); + JComponent tab = which_tab(state); - try { - pad.show(state, crc_errors); - ascent.show(state, crc_errors); - descent.show(state, crc_errors); - landed.show(state, crc_errors); if (tab != cur_tab) { if (cur_tab == pane.getSelectedComponent()) { pane.setSelectedComponent(tab); } cur_tab = tab; } - flightStatus.show(state, crc_errors); - flightInfo.show(state, crc_errors); + flightStatus.show(state, listener_state); + flightInfo.show(state, listener_state); if (state.data.companion != null) { if (!has_companion) { pane.add("Companion", companion); has_companion= true; } - companion.show(state, crc_errors); + companion.show(state, listener_state); } else { if (has_companion) { pane.remove(companion); @@ -131,17 +147,13 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay, A pane.add("Site Map", sitemap); has_map = true; } - sitemap.show(state, crc_errors); + sitemap.show(state, listener_state); } else { if (has_map) { pane.remove(sitemap); has_map = false; } } - } catch (Exception e) { - System.out.print("Show exception " + e + "\n"); - e.printStackTrace(); - } } public void set_exit_on_close() { @@ -260,22 +272,18 @@ public class AltosFlightUI extends AltosUIFrame implements AltosFlightDisplay, A pane = new JTabbedPane(); pad = new AltosPad(); - pane.add("Launch Pad", pad); + pane.add("Status", pad); ascent = new AltosAscent(); - pane.add("Ascent", ascent); - descent = new AltosDescent(); - pane.add("Descent", descent); - landed = new AltosLanded(reader); - pane.add("Landed", landed); flightInfo = new AltosInfoTable(); pane.add("Table", new JScrollPane(flightInfo)); companion = new AltosCompanionInfo(); has_companion = false; + has_state = false; sitemap = new AltosSiteMap(); has_map = false; diff --git a/altosui/AltosGraphUI.java b/altosui/AltosGraphUI.java index 2f3575a4..d8b8f6dd 100644 --- a/altosui/AltosGraphUI.java +++ b/altosui/AltosGraphUI.java @@ -26,17 +26,19 @@ public class AltosGraphUI extends AltosUIFrame AltosGraphDataSet graphDataSet; AltosFlightStats stats; AltosFlightStatsTable statsTable; + boolean has_gps; - boolean fill_map(AltosRecordIterable records) { + void fill_map(AltosRecordIterable records) { boolean any_gps = false; for (AltosRecord record : records) { state = new AltosState(record, state); - if (state.gps.locked && state.gps.nsat >= 4) { - map.show(state, 0); - any_gps = true; + if (state.gps != null && state.gps.locked && state.gps.nsat >= 4) { + if (map == null) + map = new AltosSiteMap(); + map.show(state, null); + has_gps = true; } } - return any_gps; } AltosGraphUI(AltosRecordIterable records, File file) throws InterruptedException, IOException { @@ -54,13 +56,13 @@ public class AltosGraphUI extends AltosUIFrame statsTable = new AltosFlightStatsTable(stats); - map = new AltosSiteMap(); - pane.add("Flight Graph", graph.panel); pane.add("Configure Graph", enable); pane.add("Flight Statistics", statsTable); - if (fill_map(records)) + has_gps = false; + fill_map(records); + if (has_gps) pane.add("Map", map); setContentPane (pane); @@ -69,7 +71,7 @@ public class AltosGraphUI extends AltosUIFrame setDefaultCloseOperation(DISPOSE_ON_CLOSE); setVisible(true); - if (state != null) + if (state != null && has_gps) map.centre(state); } } diff --git a/altosui/AltosIdleMonitorUI.java b/altosui/AltosIdleMonitorUI.java index 8c883eeb..bbab017f 100644 --- a/altosui/AltosIdleMonitorUI.java +++ b/altosui/AltosIdleMonitorUI.java @@ -63,21 +63,21 @@ public class AltosIdleMonitorUI extends AltosUIFrame implements AltosFlightDispl AltosFlightStatusUpdate status_update; - public void show(AltosState state, int crc_errors) { + public void show(AltosState state, AltosListenerState listener_state) { status_update.saved_state = state; try { - pad.show(state, crc_errors); - flightStatus.show(state, crc_errors); - flightInfo.show(state, crc_errors); + pad.show(state, listener_state); + flightStatus.show(state, listener_state); + flightInfo.show(state, listener_state); } catch (Exception e) { System.out.print("Show exception" + e); } } - public void update(final AltosState state) { + public void update(final AltosState state, final AltosListenerState listener_state) { Runnable r = new Runnable() { public void run() { - show(state, 0); + show(state, listener_state); } }; SwingUtilities.invokeLater(r); diff --git a/altosui/AltosInfoTable.java b/altosui/AltosInfoTable.java index 2facf38a..3d16faf2 100644 --- a/altosui/AltosInfoTable.java +++ b/altosui/AltosInfoTable.java @@ -104,101 +104,118 @@ public class AltosInfoTable extends JTable { model.clear(); } - public void show(AltosState state, int crc_errors) { - if (state == null) - return; + public void show(AltosState state, AltosListenerState listener_state) { info_reset(); - info_add_row(0, "Altitude", "%6.0f m", state.altitude); - info_add_row(0, "Pad altitude", "%6.0f m", state.ground_altitude); - info_add_row(0, "Height", "%6.0f m", state.height); - info_add_row(0, "Max height", "%6.0f m", state.max_height); - info_add_row(0, "Acceleration", "%8.1f m/s²", state.acceleration); - info_add_row(0, "Max acceleration", "%8.1f m/s²", state.max_acceleration); - info_add_row(0, "Speed", "%8.1f m/s", state.speed()); - info_add_row(0, "Max Speed", "%8.1f m/s", state.max_accel_speed); - info_add_row(0, "Temperature", "%9.2f °C", state.temperature); - info_add_row(0, "Battery", "%9.2f V", state.battery); - if (state.drogue_sense != AltosRecord.MISSING) - info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense); - if (state.main_sense != AltosRecord.MISSING) - info_add_row(0, "Main", "%9.2f V", state.main_sense); - info_add_row(0, "CRC Errors", "%6d", crc_errors); - - if (state.gps == null || !state.gps.connected) { - info_add_row(1, "GPS", "not available"); - } else { - if (state.gps_ready) - info_add_row(1, "GPS state", "%s", "ready"); - else - info_add_row(1, "GPS state", "wait (%d)", - state.gps_waiting); - if (state.data.gps.locked) - info_add_row(1, "GPS", " locked"); - else if (state.data.gps.connected) - info_add_row(1, "GPS", " unlocked"); - else - info_add_row(1, "GPS", " missing"); - info_add_row(1, "Satellites", "%6d", state.data.gps.nsat); - info_add_deg(1, "Latitude", state.gps.lat, 'N', 'S'); - info_add_deg(1, "Longitude", state.gps.lon, 'E', 'W'); - info_add_row(1, "GPS altitude", "%6d", state.gps.alt); - info_add_row(1, "GPS height", "%6.0f", state.gps_height); - - /* The SkyTraq GPS doesn't report these values */ - /* - if (false) { - info_add_row(1, "GPS ground speed", "%8.1f m/s %3d°", - state.gps.ground_speed, - state.gps.course); - info_add_row(1, "GPS climb rate", "%8.1f m/s", - state.gps.climb_rate); - info_add_row(1, "GPS error", "%6d m(h)%3d m(v)", - state.gps.h_error, state.gps.v_error); - } - */ - - info_add_row(1, "GPS hdop", "%8.1f", state.gps.hdop); - - if (state.npad > 0) { - if (state.from_pad != null) { - info_add_row(1, "Distance from pad", "%6d m", - (int) (state.from_pad.distance + 0.5)); - info_add_row(1, "Direction from pad", "%6d°", - (int) (state.from_pad.bearing + 0.5)); - info_add_row(1, "Elevation from pad", "%6d°", - (int) (state.elevation + 0.5)); - info_add_row(1, "Range from pad", "%6d m", - (int) (state.range + 0.5)); - } else { - info_add_row(1, "Distance from pad", "unknown"); - info_add_row(1, "Direction from pad", "unknown"); - info_add_row(1, "Elevation from pad", "unknown"); - info_add_row(1, "Range from pad", "unknown"); + if (state != null) { + if (state.altitude != AltosRecord.MISSING) + info_add_row(0, "Altitude", "%6.0f m", state.altitude); + if (state.ground_altitude != AltosRecord.MISSING) + info_add_row(0, "Pad altitude", "%6.0f m", state.ground_altitude); + if (state.height != AltosRecord.MISSING) + info_add_row(0, "Height", "%6.0f m", state.height); + if (state.height != AltosRecord.MISSING) + info_add_row(0, "Max height", "%6.0f m", state.max_height); + if (state.acceleration != AltosRecord.MISSING) + info_add_row(0, "Acceleration", "%8.1f m/s²", state.acceleration); + if (state.acceleration != AltosRecord.MISSING) + info_add_row(0, "Max acceleration", "%8.1f m/s²", state.max_acceleration); + if (state.speed() != AltosRecord.MISSING) + info_add_row(0, "Speed", "%8.1f m/s", state.speed()); + if (state.speed() != AltosRecord.MISSING) + info_add_row(0, "Max Speed", "%8.1f m/s", state.max_accel_speed); + if (state.temperature != AltosRecord.MISSING) + info_add_row(0, "Temperature", "%9.2f °C", state.temperature); + if (state.battery != AltosRecord.MISSING) + info_add_row(0, "Battery", "%9.2f V", state.battery); + if (state.drogue_sense != AltosRecord.MISSING) + info_add_row(0, "Drogue", "%9.2f V", state.drogue_sense); + if (state.main_sense != AltosRecord.MISSING) + info_add_row(0, "Main", "%9.2f V", state.main_sense); + } + if (listener_state != null) { + info_add_row(0, "CRC Errors", "%6d", listener_state.crc_errors); + + if (listener_state.battery != AltosRecord.MISSING) + info_add_row(0, "Receiver Battery", "%9.2f", listener_state.battery); + } + + if (state != null) { + if (state.gps == null || !state.gps.connected) { + info_add_row(1, "GPS", "not available"); + } else { + if (state.gps_ready) + info_add_row(1, "GPS state", "%s", "ready"); + else + info_add_row(1, "GPS state", "wait (%d)", + state.gps_waiting); + if (state.data.gps.locked) + info_add_row(1, "GPS", " locked"); + else if (state.data.gps.connected) + info_add_row(1, "GPS", " unlocked"); + else + info_add_row(1, "GPS", " missing"); + info_add_row(1, "Satellites", "%6d", state.data.gps.nsat); + info_add_deg(1, "Latitude", state.gps.lat, 'N', 'S'); + info_add_deg(1, "Longitude", state.gps.lon, 'E', 'W'); + info_add_row(1, "GPS altitude", "%6d", state.gps.alt); + info_add_row(1, "GPS height", "%6.0f", state.gps_height); + + /* The SkyTraq GPS doesn't report these values */ + /* + if (false) { + info_add_row(1, "GPS ground speed", "%8.1f m/s %3d°", + state.gps.ground_speed, + state.gps.course); + info_add_row(1, "GPS climb rate", "%8.1f m/s", + state.gps.climb_rate); + info_add_row(1, "GPS error", "%6d m(h)%3d m(v)", + state.gps.h_error, state.gps.v_error); + } + */ + + info_add_row(1, "GPS hdop", "%8.1f", state.gps.hdop); + + if (state.npad > 0) { + if (state.from_pad != null) { + info_add_row(1, "Distance from pad", "%6d m", + (int) (state.from_pad.distance + 0.5)); + info_add_row(1, "Direction from pad", "%6d°", + (int) (state.from_pad.bearing + 0.5)); + info_add_row(1, "Elevation from pad", "%6d°", + (int) (state.elevation + 0.5)); + info_add_row(1, "Range from pad", "%6d m", + (int) (state.range + 0.5)); + } else { + info_add_row(1, "Distance from pad", "unknown"); + info_add_row(1, "Direction from pad", "unknown"); + info_add_row(1, "Elevation from pad", "unknown"); + info_add_row(1, "Range from pad", "unknown"); + } + info_add_deg(1, "Pad latitude", state.pad_lat, 'N', 'S'); + info_add_deg(1, "Pad longitude", state.pad_lon, 'E', 'W'); + info_add_row(1, "Pad GPS alt", "%6.0f m", state.pad_alt); } - info_add_deg(1, "Pad latitude", state.pad_lat, 'N', 'S'); - info_add_deg(1, "Pad longitude", state.pad_lon, 'E', 'W'); - info_add_row(1, "Pad GPS alt", "%6.0f m", state.pad_alt); - } - info_add_row(1, "GPS date", "%04d-%02d-%02d", - state.gps.year, - state.gps.month, - state.gps.day); - info_add_row(1, "GPS time", " %02d:%02d:%02d", - state.gps.hour, - state.gps.minute, - state.gps.second); - //int nsat_vis = 0; - int c; - - if (state.gps.cc_gps_sat == null) - info_add_row(2, "Satellites Visible", "%4d", 0); - else { - info_add_row(2, "Satellites Visible", "%4d", state.gps.cc_gps_sat.length); - for (c = 0; c < state.gps.cc_gps_sat.length; c++) { - info_add_row(2, "Satellite id,C/N0", - "%4d, %4d", - state.gps.cc_gps_sat[c].svid, - state.gps.cc_gps_sat[c].c_n0); + info_add_row(1, "GPS date", "%04d-%02d-%02d", + state.gps.year, + state.gps.month, + state.gps.day); + info_add_row(1, "GPS time", " %02d:%02d:%02d", + state.gps.hour, + state.gps.minute, + state.gps.second); + //int nsat_vis = 0; + int c; + + if (state.gps.cc_gps_sat == null) + info_add_row(2, "Satellites Visible", "%4d", 0); + else { + info_add_row(2, "Satellites Visible", "%4d", state.gps.cc_gps_sat.length); + for (c = 0; c < state.gps.cc_gps_sat.length; c++) { + info_add_row(2, "Satellite id,C/N0", + "%4d, %4d", + state.gps.cc_gps_sat[c].svid, + state.gps.cc_gps_sat[c].c_n0); + } } } } diff --git a/altosui/AltosLanded.java b/altosui/AltosLanded.java index e13229a8..1d209bda 100644 --- a/altosui/AltosLanded.java +++ b/altosui/AltosLanded.java @@ -29,7 +29,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio public class LandedValue { JLabel label; JTextField value; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void reset() { value.setText(""); @@ -102,7 +102,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio } class Lat extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { if (state.gps != null && state.gps.connected) show(pos(state.gps.lat,"N", "S")); else @@ -116,7 +116,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Lat lat; class Lon extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(); if (state.gps != null && state.gps.connected) show(pos(state.gps.lon,"E", "W")); @@ -131,7 +131,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Lon lon; class Bearing extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(); if (state.from_pad != null) show("%3.0f°", state.from_pad.bearing); @@ -146,7 +146,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Bearing bearing; class Distance extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(); if (state.from_pad != null) show(AltosConvert.distance, state.from_pad.distance); @@ -161,7 +161,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Distance distance; class Height extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.height, state.max_height); } public Height (GridBagLayout layout, int y) { @@ -172,7 +172,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Height height; class Speed extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.speed, state.max_speed()); } public Speed (GridBagLayout layout, int y) { @@ -183,7 +183,7 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio Speed speed; class Accel extends LandedValue { - void show (AltosState state, int crc_errors) { + void show (AltosState state, AltosListenerState listener_state) { show(AltosConvert.accel, state.max_acceleration); } public Accel (GridBagLayout layout, int y) { @@ -213,21 +213,21 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio accel.set_font(); } - public void show(AltosState state, int crc_errors) { + public void show(AltosState state, AltosListenerState listener_state) { if (state.gps != null && state.gps.connected) { - bearing.show(state, crc_errors); - distance.show(state, crc_errors); - lat.show(state, crc_errors); - lon.show(state, crc_errors); + bearing.show(state, listener_state); + distance.show(state, listener_state); + lat.show(state, listener_state); + lon.show(state, listener_state); } else { bearing.hide(); distance.hide(); lat.hide(); lon.hide(); } - height.show(state, crc_errors); - speed.show(state, crc_errors); - accel.show(state, crc_errors); + height.show(state, listener_state); + speed.show(state, listener_state); + accel.show(state, listener_state); if (reader.backing_file() != null) graph.setEnabled(true); } @@ -271,6 +271,10 @@ public class AltosLanded extends JComponent implements AltosFlightDisplay, Actio } } + public String getName() { + return "Landed"; + } + public AltosLanded(AltosFlightReader in_reader) { layout = new GridBagLayout(); diff --git a/altosui/AltosPad.java b/altosui/AltosPad.java index d13f6945..e2316a13 100644 --- a/altosui/AltosPad.java +++ b/altosui/AltosPad.java @@ -29,7 +29,7 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { JTextField value; AltosLights lights; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void reset() { value.setText(""); @@ -66,6 +66,10 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { value.setFont(Altos.value_font); } + public void set_label(String text) { + label.setText(text); + } + public LaunchStatus (GridBagLayout layout, int y, String text) { GridBagConstraints c = new GridBagConstraints(); c.weighty = 1; @@ -105,7 +109,7 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { public class LaunchValue { JLabel label; JTextField value; - void show(AltosState state, int crc_errors) {} + void show(AltosState state, AltosListenerState listener_state) {} void show() { label.setVisible(true); @@ -135,6 +139,10 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { show(String.format(format, v)); } + public void set_label(String text) { + label.setText(text); + } + void reset() { value.setText(""); } @@ -167,9 +175,13 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { } class Battery extends LaunchStatus { - void show (AltosState state, int crc_errors) { - show("%4.2f V", state.battery); - lights.set(state.battery > 3.7); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.battery == AltosRecord.MISSING) + hide(); + else { + show("%4.2f V", state.battery); + lights.set(state.battery > 3.7); + } } public Battery (GridBagLayout layout, int y) { super(layout, y, "Battery Voltage"); @@ -179,9 +191,13 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { Battery battery; class Apogee extends LaunchStatus { - void show (AltosState state, int crc_errors) { - show("%4.2f V", state.drogue_sense); - lights.set(state.drogue_sense > 3.2); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.drogue_sense == AltosRecord.MISSING) + hide(); + else { + show("%4.2f V", state.drogue_sense); + lights.set(state.drogue_sense > 3.2); + } } public Apogee (GridBagLayout layout, int y) { super(layout, y, "Apogee Igniter Voltage"); @@ -191,9 +207,13 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { Apogee apogee; class Main extends LaunchStatus { - void show (AltosState state, int crc_errors) { - show("%4.2f V", state.main_sense); - lights.set(state.main_sense > 3.2); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.main_sense == AltosRecord.MISSING) + hide(); + else { + show("%4.2f V", state.main_sense); + lights.set(state.main_sense > 3.2); + } } public Main (GridBagLayout layout, int y) { super(layout, y, "Main Igniter Voltage"); @@ -203,18 +223,21 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { Main main; class LoggingReady extends LaunchStatus { - void show (AltosState state, int crc_errors) { - if (state.data.flight != 0) { - if (state.data.state <= Altos.ao_flight_pad) - show("Ready to record"); - else if (state.data.state < Altos.ao_flight_landed) - show("Recording data"); - else - show("Recorded data"); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.data.flight == AltosRecord.MISSING) { + hide(); + } else { + if (state.data.flight != 0) { + if (state.data.state <= Altos.ao_flight_pad) + show("Ready to record"); + else if (state.data.state < Altos.ao_flight_landed) + show("Recording data"); + else + show("Recorded data"); + } else + show("Storage full"); + lights.set(state.data.flight != 0); } - else - show("Storage full"); - lights.set(state.data.flight != 0); } public LoggingReady (GridBagLayout layout, int y) { super(layout, y, "On-board Data Logging"); @@ -224,9 +247,13 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { LoggingReady logging_ready; class GPSLocked extends LaunchStatus { - void show (AltosState state, int crc_errors) { - show("%4d sats", state.gps.nsat); - lights.set(state.gps.locked && state.gps.nsat >= 4); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) + hide(); + else { + show("%4d sats", state.gps.nsat); + lights.set(state.gps.locked && state.gps.nsat >= 4); + } } public GPSLocked (GridBagLayout layout, int y) { super (layout, y, "GPS Locked"); @@ -236,12 +263,16 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { GPSLocked gps_locked; class GPSReady extends LaunchStatus { - void show (AltosState state, int crc_errors) { - if (state.gps_ready) - show("Ready"); - else - show("Waiting %d", state.gps_waiting); - lights.set(state.gps_ready); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) + hide(); + else { + if (state.gps_ready) + show("Ready"); + else + show("Waiting %d", state.gps_waiting); + lights.set(state.gps_ready); + } } public GPSReady (GridBagLayout layout, int y) { super (layout, y, "GPS Ready"); @@ -250,6 +281,22 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { GPSReady gps_ready; + class ReceiverBattery extends LaunchStatus { + void show (AltosState state, AltosListenerState listener_state) { + if (listener_state == null || listener_state.battery == AltosRecord.MISSING) + hide(); + else { + show("%4.2f V", listener_state.battery); + lights.set(listener_state.battery > 3.7); + } + } + public ReceiverBattery (GridBagLayout layout, int y) { + super(layout, y, "Receiver Battery"); + } + } + + ReceiverBattery receiver_battery; + String pos(double p, String pos, String neg) { String h = pos; if (p < 0) { @@ -262,8 +309,18 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { } class PadLat extends LaunchValue { - void show (AltosState state, int crc_errors) { - show(pos(state.pad_lat,"N", "S")); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) { + hide(); + } else { + if (state.state < AltosLib.ao_flight_pad) { + show(pos(state.gps.lat,"N", "S")); + set_label("Latitude"); + } else { + show(pos(state.pad_lat,"N", "S")); + set_label("Pad Latitude"); + } + } } public PadLat (GridBagLayout layout, int y) { super (layout, y, "Pad Latitude"); @@ -273,8 +330,18 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { PadLat pad_lat; class PadLon extends LaunchValue { - void show (AltosState state, int crc_errors) { - show(pos(state.pad_lon,"E", "W")); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null || state.gps == null) { + hide(); + } else { + if (state.state < AltosLib.ao_flight_pad) { + show(pos(state.gps.lon,"E", "W")); + set_label("Longitude"); + } else { + show(pos(state.pad_lon,"E", "W")); + set_label("Pad Longitude"); + } + } } public PadLon (GridBagLayout layout, int y) { super (layout, y, "Pad Longitude"); @@ -284,8 +351,22 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { PadLon pad_lon; class PadAlt extends LaunchValue { - void show (AltosState state, int crc_errors) { - show("%4.0f m", state.pad_alt); + void show (AltosState state, AltosListenerState listener_state) { + if (state == null) + hide(); + else { + if (state.state < AltosLib.ao_flight_pad && state.gps != null) { + show("%4.0f m", state.gps.alt); + set_label("Altitude"); + } else { + if (state.pad_alt == AltosRecord.MISSING) + hide(); + else { + show("%4.0f m", state.pad_alt); + set_label("Pad Altitude"); + } + } + } } public PadAlt (GridBagLayout layout, int y) { super (layout, y, "Pad Altitude"); @@ -301,6 +382,7 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { logging_ready.reset(); gps_locked.reset(); gps_ready.reset(); + receiver_battery.reset(); pad_lat.reset(); pad_lon.reset(); pad_alt.reset(); @@ -313,34 +395,23 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { logging_ready.set_font(); gps_locked.set_font(); gps_ready.set_font(); + receiver_battery.set_font(); pad_lat.set_font(); pad_lon.set_font(); pad_alt.set_font(); } - public void show(AltosState state, int crc_errors) { - battery.show(state, crc_errors); - if (state.drogue_sense == AltosRecord.MISSING) - apogee.hide(); - else - apogee.show(state, crc_errors); - if (state.main_sense == AltosRecord.MISSING) - main.hide(); - else - main.show(state, crc_errors); - logging_ready.show(state, crc_errors); - pad_alt.show(state, crc_errors); - if (state.gps != null && state.gps.connected) { - gps_locked.show(state, crc_errors); - gps_ready.show(state, crc_errors); - pad_lat.show(state, crc_errors); - pad_lon.show(state, crc_errors); - } else { - gps_locked.hide(); - gps_ready.hide(); - pad_lat.hide(); - pad_lon.hide(); - } + public void show(AltosState state, AltosListenerState listener_state) { + battery.show(state, listener_state); + apogee.show(state, listener_state); + main.show(state, listener_state); + logging_ready.show(state, listener_state); + pad_alt.show(state, listener_state); + receiver_battery.show(state, listener_state); + gps_locked.show(state, listener_state); + gps_ready.show(state, listener_state); + pad_lat.show(state, listener_state); + pad_lon.show(state, listener_state); } public AltosPad() { @@ -364,8 +435,10 @@ public class AltosPad extends JComponent implements AltosFlightDisplay { logging_ready = new LoggingReady(layout, 3); gps_locked = new GPSLocked(layout, 4); gps_ready = new GPSReady(layout, 5); - pad_lat = new PadLat(layout, 6); - pad_lon = new PadLon(layout, 7); - pad_alt = new PadAlt(layout, 8); + receiver_battery = new ReceiverBattery(layout, 6); + pad_lat = new PadLat(layout, 7); + pad_lon = new PadLon(layout, 8); + pad_alt = new PadAlt(layout, 9); + show(null, null); } } diff --git a/altosui/AltosSiteMap.java b/altosui/AltosSiteMap.java index f614eae6..23085f3e 100644 --- a/altosui/AltosSiteMap.java +++ b/altosui/AltosSiteMap.java @@ -220,6 +220,16 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { return pngfile.toString(); } + public void initAndFinishMapAsync (final AltosSiteMapTile tile, final Point offset) { + Thread thread = new Thread() { + public void run() { + initMap(offset); + finishTileLater(tile, offset); + } + }; + thread.start(); + } + public void setBaseLocation(double lat, double lng) { for (Point k : mapTiles.keySet()) { AltosSiteMapTile tile = mapTiles.get(k); @@ -264,7 +274,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { initMaps(lat, lon); scrollRocketToVisible(pt(lat, lon)); } - public void show(final AltosState state, final int crc_errors) { + public void show(final AltosState state, final AltosListenerState listener_state) { // if insufficient gps data, nothing to update if (!state.gps.locked && state.gps.nsat < 4) return; @@ -294,7 +304,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { Point2D.Double ref, lref; ref = translatePoint(pt, tileCoordOffset(offset)); lref = translatePoint(last_pt, tileCoordOffset(offset)); - tile.show(state, crc_errors, lref, ref); + tile.show(state, listener_state, lref, ref); if (0 <= ref.x && ref.x < px_size) if (0 <= ref.y && ref.y < px_size) in_any = true; @@ -307,9 +317,8 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { lref = translatePoint(last_pt, tileCoordOffset(offset)); AltosSiteMapTile tile = createTile(offset); - tile.show(state, crc_errors, lref, ref); - initMap(offset); - finishTileLater(tile, offset); + tile.show(state, listener_state, lref, ref); + initAndFinishMapAsync(tile, offset); } scrollRocketToVisible(pt); @@ -370,8 +379,7 @@ public class AltosSiteMap extends JScrollPane implements AltosFlightDisplay { if (mapTiles.containsKey(offset)) continue; AltosSiteMapTile tile = createTile(offset); - initMap(offset); - finishTileLater(tile, offset); + initAndFinishMapAsync(tile, offset); } } } diff --git a/altosui/AltosSiteMapCache.java b/altosui/AltosSiteMapCache.java index 617ed4a9..40c8ff6b 100644 --- a/altosui/AltosSiteMapCache.java +++ b/altosui/AltosSiteMapCache.java @@ -19,6 +19,7 @@ package altosui; import javax.swing.*; import javax.imageio.ImageIO; +import java.awt.image.*; import java.io.*; import java.net.URL; import java.net.URLConnection; @@ -87,7 +88,14 @@ public class AltosSiteMapCache extends JLabel { } try { - return new ImageIcon(ImageIO.read(pngfile)); + BufferedImage img; + + img = ImageIO.read(pngfile); + if (img == null) { + System.out.printf("# Can't read pngfile %s\n", pngfile); + return null; + } + return new ImageIcon(img); } catch (IOException e) { System.out.printf("# IO error trying to load %s\n", pngfile); return null; diff --git a/altosui/AltosSiteMapTile.java b/altosui/AltosSiteMapTile.java index 10e65bcd..365e4b6c 100644 --- a/altosui/AltosSiteMapTile.java +++ b/altosui/AltosSiteMapTile.java @@ -56,7 +56,7 @@ public class AltosSiteMapTile extends JLayeredPane { private boolean drawn_landed_circle = false; private boolean drawn_boost_circle = false; - public synchronized void show(AltosState state, int crc_errors, + public synchronized void show(AltosState state, AltosListenerState listener_state, Point2D.Double last_pt, Point2D.Double pt) { if (0 <= state.state && state.state < stateColors.length) { diff --git a/altosui/Makefile.am b/altosui/Makefile.am index a8292c64..d59e3082 100644 --- a/altosui/Makefile.am +++ b/altosui/Makefile.am @@ -153,7 +153,7 @@ FIRMWARE=$(FIRMWARE_TM) $(FIRMWARE_TELEMINI) $(FIRMWARE_TD) ALTUSMETRUM_DOC=$(top_srcdir)/doc/altusmetrum.pdf ALTOS_DOC=$(top_srcdir)/doc/altos.pdf TELEMETRY_DOC=$(top_srcdir)/doc/telemetry.pdf -TEMPLATE_DOC=$(top_srcdir)/doc/telemetrum-outline.pdf $(top_srcdir)/doc/megametrum-outline.pdf +TEMPLATE_DOC=$(top_srcdir)/doc/telemetrum-outline.pdf $(top_srcdir)/doc/telemega-outline.pdf DOC=$(ALTUSMETRUM_DOC) $(ALTOS_DOC) $(TELEMETRY_DOC) $(TEMPLATE_DOC) diff --git a/altosui/altos-windows.nsi.in b/altosui/altos-windows.nsi.in index cde54b41..9886e4a2 100644 --- a/altosui/altos-windows.nsi.in +++ b/altosui/altos-windows.nsi.in @@ -131,7 +131,7 @@ Section "Documentation" File "../doc/altos.pdf" File "../doc/telemetry.pdf" File "../doc/telemetrum-outline.pdf" - File "../doc/megametrum-outline.pdf" + File "../doc/telemega-outline.pdf" SectionEnd Section "Uninstaller" diff --git a/altosuilib/AltosUSBDevice.java b/altosuilib/AltosUSBDevice.java index a5496597..0f6cbd10 100644 --- a/altosuilib/AltosUSBDevice.java +++ b/altosuilib/AltosUSBDevice.java @@ -72,11 +72,12 @@ public class AltosUSBDevice extends altos_device implements AltosDevice { return matchProduct(AltosUILib.product_teledongle) || matchProduct(AltosUILib.product_teleterra) || matchProduct(AltosUILib.product_telebt) || - matchProduct(AltosUILib.product_megadongle); + matchProduct(AltosUILib.product_telemega); if (want_product == AltosUILib.product_altimeter) return matchProduct(AltosUILib.product_telemetrum) || - matchProduct(AltosUILib.product_megametrum); + matchProduct(AltosUILib.product_telemega) || + matchProduct(AltosUILib.product_telegps); int have_product = getProduct(); @@ -109,4 +110,4 @@ public class AltosUSBDevice extends altos_device implements AltosDevice { return device_list; } -}
\ No newline at end of file +} diff --git a/ao-bringup/megametrum.cfg b/ao-bringup/telemega.cfg index e95c6f2b..f6b96c13 100644 --- a/ao-bringup/megametrum.cfg +++ b/ao-bringup/telemega.cfg @@ -1,4 +1,4 @@ -# openocd config for MegaMetrum using the Olimex ARM-USB-OCD dongle +# openocd config for TeleMega using the Olimex ARM-USB-OCD dongle source /opt/stm32/share/openocd/scripts/interface/olimex-arm-usb-ocd.cfg source /opt/stm32/share/openocd/scripts/target/stm32l.cfg diff --git a/ao-bringup/megametrum.gdb b/ao-bringup/telemega.gdb index 964ae1f3..964ae1f3 100644 --- a/ao-bringup/megametrum.gdb +++ b/ao-bringup/telemega.gdb diff --git a/ao-bringup/turnon_telebt b/ao-bringup/turnon_telebt index 3b8bd3e4..6c52721c 100755 --- a/ao-bringup/turnon_telebt +++ b/ao-bringup/turnon_telebt @@ -45,4 +45,4 @@ echo "Programming flash with cal value " $CAL_VALUE $AOLOAD -D 100 --cal $CAL_VALUE /usr/share/altos/telebt-v0.1*.ihx $SERIAL echo "Serial number "$SERIAL" programmed with RF cal value "$CAL_VALUE -echo "Unplug and replug USB, cu to the board, confirm freq and record power" +echo "Unplug debug cable, power cycle, cu to the board, confirm freq and record power" diff --git a/ao-tools/Makefile.am b/ao-tools/Makefile.am index e4df2e63..4600f1d6 100644 --- a/ao-tools/Makefile.am +++ b/ao-tools/Makefile.am @@ -1,3 +1,3 @@ SUBDIRS=lib ao-rawload ao-dbg ao-bitbang ao-eeprom ao-list \ ao-load ao-telem ao-stmload ao-send-telem ao-sky-flash \ - ao-dumpflash ao-edit-telem + ao-dumpflash ao-edit-telem ao-dump-up diff --git a/ao-tools/ao-dump-up/Makefile.am b/ao-tools/ao-dump-up/Makefile.am new file mode 100644 index 00000000..94bb94a9 --- /dev/null +++ b/ao-tools/ao-dump-up/Makefile.am @@ -0,0 +1,12 @@ +bin_PROGRAMS=ao-dump-up + +AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS) $(GNOME_CFLAGS) +AO_DUMP_LOG_LIBS=$(top_builddir)/ao-tools/lib/libao-tools.a + +ao_dump_up_DEPENDENCIES = $(AO_DUMP_LOG_LIBS) + +ao_dump_up_LDADD=$(AO_DUMP_LOG_LIBS) $(LIBUSB_LIBS) $(GNOME_LIBS) + +ao_dump_up_SOURCES = ao-dump-up.c + +man_MANS = ao-dump-up.1 diff --git a/ao-tools/ao-dump-up/ao-dump-up.1 b/ao-tools/ao-dump-up/ao-dump-up.1 new file mode 100644 index 00000000..cfa81b46 --- /dev/null +++ b/ao-tools/ao-dump-up/ao-dump-up.1 @@ -0,0 +1,45 @@ +.\" +.\" Copyright © 2009 Keith Packard <keithp@keithp.com> +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, but +.\" WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +.\" General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License along +.\" with this program; if not, write to the Free Software Foundation, Inc., +.\" 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +.\" +.\" +.TH AO-DUMPLOG 1 "ao-dump-up" "" +.SH NAME +ao-dump-up \- Dump flight log from MicroPeak flight computer +.SH SYNOPSIS +.B "ao-dump-up" +[\-T \fItty-device\fP] +[\--tty \fItty-device\fP] +[\-D \fIaltos-device\fP] +[\--device \fIaltos-device\fP] +.SH OPTIONS +.TP +\-T tty-device | --tty tty-device +This selects which tty device ao-dump-up uses to communicate with +the target device. +.TP +\-D AltOS-device | --device AltOS-device +Search for a connected device. This forces the program to look +for a specific USB device name. +.SH DESCRIPTION +.I ao-dump-up +downloads a MicroPeak flight log from a connected MicroPeak USB adapter. +.SH USAGE +.I ao-dump-up +connects to the specified target device and dumps the stored flight +log. +.SH AUTHOR +Keith Packard diff --git a/ao-tools/ao-dump-up/ao-dump-up.c b/ao-tools/ao-dump-up/ao-dump-up.c new file mode 100644 index 00000000..6268dc8b --- /dev/null +++ b/ao-tools/ao-dump-up/ao-dump-up.c @@ -0,0 +1,207 @@ +/* + * Copyright © 2009 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include "cc-usb.h" +#include "cc.h" + +#define NUM_BLOCK 512 + +static const struct option options[] = { + { .name = "tty", .has_arg = 1, .val = 'T' }, + { .name = "device", .has_arg = 1, .val = 'D' }, + { 0, 0, 0, 0}, +}; + +static void usage(char *program) +{ + fprintf(stderr, "usage: %s [--tty <tty-name>] [--device <device-name>]\n", program); + exit(1); +} + +static uint8_t +log_checksum(int d[8]) +{ + uint8_t sum = 0x5a; + int i; + + for (i = 0; i < 8; i++) + sum += (uint8_t) d[i]; + return -sum; +} + +static int get_nonwhite(struct cc_usb *cc, int timeout) +{ + int c; + + for (;;) { + c = cc_usb_getchar_timeout(cc, timeout); + putchar(c); + if (!isspace(c)) + return c; + } +} + +static uint8_t +get_hexc(struct cc_usb *cc) +{ + int c = get_nonwhite(cc, 1000); + + if ('0' <= c && c <= '9') + return c - '0'; + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + if ('A' <= c && c <= 'F') + return c - 'A' + 10; + fprintf(stderr, "Non-hex char '%c'\n", c); + exit(1); +} + +static int file_crc; + +static const int POLY = 0x8408; + +static int +log_crc(int crc, int b) +{ + int i; + + for (i = 0; i < 8; i++) { + if (((crc & 0x0001) ^ (b & 0x0001)) != 0) + crc = (crc >> 1) ^ POLY; + else + crc = crc >> 1; + b >>= 1; + } + return crc & 0xffff; +} + +static uint8_t +get_hex(struct cc_usb *cc) +{ + int a = get_hexc(cc); + int b = get_hexc(cc); + int h = (a << 4) + b; + + file_crc = log_crc(file_crc, h); + return h; +} + +static int get_32(struct cc_usb *cc) +{ + int v = 0; + int i; + for (i = 0; i < 4; i++) { + v += get_hex(cc) << (i * 8); + } + return v; +} + +static int get_16(struct cc_usb *cc) +{ + int v = 0; + int i; + for (i = 0; i < 2; i++) { + v += get_hex(cc) << (i * 8); + } + return v; +} + +static int swap16(int i) +{ + return ((i << 8) & 0xff00) | ((i >> 8) & 0xff); +} + +static int find_header(struct cc_usb *cc) +{ + for (;;) { + if (get_nonwhite(cc, 0) == 'M' && get_nonwhite(cc, 1000) == 'P') + return 1; + } +} + +static const char *state_names[] = { + "startup", + "idle", + "pad", + "boost", + "fast", + "coast", + "drogue", + "main", + "landed", + "invalid" +}; + + +int +main (int argc, char **argv) +{ + struct cc_usb *cc; + char *tty = NULL; + char *device = NULL; + int c; + char line[8192]; + int nsamples; + int i; + int crc; + int current_crc; + + while ((c = getopt_long(argc, argv, "T:D:", options, NULL)) != -1) { + switch (c) { + case 'T': + tty = optarg; + break; + case 'D': + device = optarg; + break; + default: + usage(argv[0]); + break; + } + } + if (!tty) + tty = cc_usbdevs_find_by_arg(device, "FT230X Basic UART"); + if (!tty) + tty = getenv("ALTOS_TTY"); + if (!tty) + tty="/dev/ttyUSB0"; + cc = cc_usb_open(tty); + if (!cc) + exit(1); + find_header(cc); + file_crc = 0xffff; + get_32(cc); /* ground pressure */ + get_32(cc); /* min pressure */ + nsamples = get_16(cc); /* nsamples */ + for (i = 0; i < nsamples; i++) + get_16(cc); /* sample i */ + current_crc = swap16(~file_crc & 0xffff); + crc = get_16(cc); /* crc */ + putchar ('\n'); + if (crc == current_crc) + printf("CRC valid\n"); + else + printf("CRC invalid\n"); + cc_usb_close(cc); + exit (0); +} diff --git a/ao-tools/ao-telem/ao-telem.c b/ao-tools/ao-telem/ao-telem.c index e7fc8e26..d2dae5a7 100644 --- a/ao-tools/ao-telem/ao-telem.c +++ b/ao-tools/ao-telem/ao-telem.c @@ -24,6 +24,7 @@ #include "cc.h" static const struct option options[] = { + { .name = "crc", .has_arg = 0, .val = 'c' }, { 0, 0, 0, 0}, }; @@ -44,8 +45,12 @@ main (int argc, char **argv) char *s; FILE *file; int serial; - while ((c = getopt_long(argc, argv, "", options, NULL)) != -1) { + int ignore_crc = 0; + while ((c = getopt_long(argc, argv, "c", options, NULL)) != -1) { switch (c) { + case 'c': + ignore_crc = 1; + break; default: usage(argv[0]); break; @@ -74,7 +79,7 @@ main (int argc, char **argv) printf ("serial %5d rssi %d status %02x tick %5d type %3d ", telem.generic.serial, rssi, telem.generic.status, telem.generic.tick, telem.generic.type); - if ((telem.generic.status & (1 << 7)) == 0) { + if (!ignore_crc && (telem.generic.status & (1 << 7)) == 0) { printf ("CRC error\n"); continue; } diff --git a/ao-tools/lib/cc-usb.c b/ao-tools/lib/cc-usb.c index 621a15c1..485583f9 100644 --- a/ao-tools/lib/cc-usb.c +++ b/ao-tools/lib/cc-usb.c @@ -258,10 +258,10 @@ cc_usb_printf(struct cc_usb *cc, char *format, ...) } int -cc_usb_getchar(struct cc_usb *cc) +cc_usb_getchar_timeout(struct cc_usb *cc, int timeout) { while (cc->in_pos == cc->in_count) { - if (_cc_usb_sync(cc, 5000) < 0) { + if (_cc_usb_sync(cc, timeout) < 0) { fprintf(stderr, "USB link timeout\n"); exit(1); } @@ -269,6 +269,12 @@ cc_usb_getchar(struct cc_usb *cc) return cc->in_buf[cc->in_pos++]; } +int +cc_usb_getchar(struct cc_usb *cc) +{ + return cc_usb_getchar_timeout(cc, 5000); +} + void cc_usb_getline(struct cc_usb *cc, char *line, int max) { @@ -424,6 +430,8 @@ cc_usb_open(char *tty) tcgetattr(cc->fd, &termios); save_termios = termios; cfmakeraw(&termios); + cfsetospeed(&termios, B9600); + cfsetispeed(&termios, B9600); tcsetattr(cc->fd, TCSAFLUSH, &termios); cc_usb_printf(cc, "\nE 0\nm 0\n"); do { diff --git a/ao-tools/lib/cc-usb.h b/ao-tools/lib/cc-usb.h index e90e1195..f1193456 100644 --- a/ao-tools/lib/cc-usb.h +++ b/ao-tools/lib/cc-usb.h @@ -54,6 +54,9 @@ void cc_queue_read(struct cc_usb *cc, uint8_t *buf, int len); int +cc_usb_getchar_timeout(struct cc_usb *cc, int timeout); + +int cc_usb_getchar(struct cc_usb *cc); void diff --git a/ao-tools/lib/cc-usbdev.c b/ao-tools/lib/cc-usbdev.c index a19e231c..95bfa244 100644 --- a/ao-tools/lib/cc-usbdev.c +++ b/ao-tools/lib/cc-usbdev.c @@ -132,13 +132,25 @@ usb_tty(char *sys) /* Check for tty/ttyACMx style names */ tty_dir = cc_fullname(endpoint_full, "tty"); - free(endpoint_full); ntty = scandir(tty_dir, &namelist, dir_filter_tty, alphasort); free (tty_dir); if (ntty > 0) { tty = cc_fullname("/dev", namelist[0]->d_name); + free(endpoint_full); + free(namelist); + return tty; + } + + /* Check for ttyACMx style names + */ + ntty = scandir(endpoint_full, &namelist, + dir_filter_tty, + alphasort); + free(endpoint_full); + if (ntty > 0) { + tty = cc_fullname("/dev", namelist[0]->d_name); free(namelist); return tty; } @@ -197,6 +209,15 @@ dir_filter_dev(const struct dirent *d) return 1; } +static int +is_am(int idVendor, int idProduct) { + if (idVendor == 0xfffe) + return 1; + if (idVendor == 0x0403 && idProduct == 0x6015) + return 1; + return 0; +} + struct cc_usbdevs * cc_usbdevs_scan(void) { @@ -220,7 +241,7 @@ cc_usbdevs_scan(void) dir = cc_fullname(USB_DEVICES, ents[e]->d_name); dev = usb_scan_device(dir); free(dir); - if (dev->idVendor == 0xfffe && dev->tty) { + if (is_am(dev->idVendor, dev->idProduct) && dev->tty) { if (devs->dev) devs->dev = realloc(devs->dev, (devs->ndev + 1) * sizeof (struct usbdev *)); diff --git a/configure.ac b/configure.ac index 977678f3..f54a8b4c 100644 --- a/configure.ac +++ b/configure.ac @@ -223,6 +223,7 @@ ao-tools/ao-send-telem/Makefile ao-tools/ao-sky-flash/Makefile ao-tools/ao-dumpflash/Makefile ao-tools/ao-edit-telem/Makefile +ao-tools/ao-dump-up/Makefile ao-utils/Makefile src/Version ]) diff --git a/debian/docs b/debian/docs index 3ac75ad4..dcdb7763 100644 --- a/debian/docs +++ b/debian/docs @@ -7,4 +7,4 @@ doc/telemetry.pdf doc/altos.html doc/altos.pdf doc/telemetrum-outline.pdf -doc/megametrum-outline.pdf +doc/telemega-outline.pdf diff --git a/doc/altos.xsl b/doc/altos.xsl index c301adde..5af94725 100644 --- a/doc/altos.xsl +++ b/doc/altos.xsl @@ -50,7 +50,7 @@ STM32L series from ST Microelectronics. This ARM Cortex-M3 based microcontroller offers low power consumption and a wide variety of built-in peripherals. Altus Metrum uses - this in the MegaMetrum, MegaDongle and TeleLCO projects. + this in the TeleMega, MegaDongle and TeleLCO projects. </para> </listitem> <listitem> diff --git a/doc/altusmetrum.xsl b/doc/altusmetrum.xsl index c5c08d4e..294f30ac 100644 --- a/doc/altusmetrum.xsl +++ b/doc/altusmetrum.xsl @@ -3,7 +3,7 @@ "/usr/share/xml/docbook/schema/dtd/4.5/docbookx.dtd"> <book> <title>The Altus Metrum System</title> - <subtitle>An Owner's Manual for TeleMetrum, TeleMini and TeleDongle Devices</subtitle> + <subtitle>An Owner's Manual for TeleMetrum, TeleMini, TeleDongle and TeleBT Devices</subtitle> <bookinfo> <author> <firstname>Bdale</firstname> @@ -36,6 +36,15 @@ </legalnotice> <revhistory> <revision> + <revnumber>1.2</revnumber> + <date>14 April 2013</date> + <revremark> + Updated for software version 1.2. Version 1.2 adds support + for TeleBT and AltosDroid. It also adds a few minor features + and fixes a few minor bugs in AltosUI and the AltOS firmware. + </revremark> + </revision> + <revision> <revnumber>1.1.1</revnumber> <date>16 September 2012</date> <revremark> @@ -128,15 +137,22 @@ NAR #88757, TRA #12200 air-frame. </para> <para> - Complementing TeleMetrum and TeleMini is TeleDongle, a USB to RF - interface for communicating with the altimeters. Combined with your - choice of antenna and - notebook computer, TeleDongle and our associated user interface software - form a complete ground station capable of logging and displaying in-flight - telemetry, aiding rocket recovery, then processing and archiving flight + TeleDongle was our first ground station, providing a USB to RF + interfaces for communicating with the altimeters. Combined with + your choice of antenna and notebook computer, TeleDongle and our + associated user interface software form a complete ground + station capable of logging and displaying in-flight telemetry, + aiding rocket recovery, then processing and archiving flight data for analysis and review. </para> <para> + For a slightly more portable ground station experience that also + provides direct rocket recovery support, TeleBT offers flight + monitoring and data logging using a Bluetooth connection between + the receiver and an Android device that has the Altos Droid + application installed from the Google Play store. + </para> + <para> More products will be added to the Altus Metrum family over time, and we currently envision that this will be a single, comprehensive manual for the entire product family. @@ -197,6 +213,16 @@ NAR #88757, TRA #12200 The latest version may always be downloaded from <ulink url="http://altusmetrum.org/AltOS"/>. </para> + <para> + If you're using a TeleBT instead of the TeleDongle, you'll want + to go install the Altos Droid application from the Google Play + store. You don't need a data plan to use Altos Droid, but + without network access, the Map view will be less useful as it + won't contain any map data. You can also use TeleBT connected + over USB with your laptop computer; it acts exactly like a + TeleDongle. Anywhere this manual talks about TeleDongle, you can + also read that as 'and TeleBT when connected via USB'. + </para> </chapter> <chapter> <title>Handling Precautions</title> @@ -364,15 +390,15 @@ NAR #88757, TRA #12200 flights, do what makes sense. </para> <para> - If idle mode is entered, you will hear an audible "di-dit" or see - two short flashes ("I" for idle), and the flight state machine is - disengaged, thus no ejection charges will fire. The altimeters also - listen for the radio link when in idle mode for requests sent via - TeleDongle. Commands can be issued to a TeleMetrum in idle mode - over either - USB or the radio link equivalently. TeleMini only has the radio link. - Idle mode is useful for configuring the altimeter, for extracting data - from the on-board storage chip after flight, and for ground testing + If idle mode is entered, you will hear an audible "di-dit" or + see two short flashes ("I" for idle), and the flight state + machine is disengaged, thus no ejection charges will fire. + The altimeters also listen for the radio link when in idle + mode for requests sent via TeleDongle. Commands can be issued + to a TeleMetrum in idle mode over either USB or the radio link + equivalently. TeleMini only has the radio link. Idle mode is + useful for configuring the altimeter, for extracting data from + the on-board storage chip after flight, and for ground testing pyro charges. </para> <para> @@ -443,11 +469,12 @@ NAR #88757, TRA #12200 <section> <title>Controlling An Altimeter Over The Radio Link</title> <para> - One of the unique features of the Altus Metrum system is - the ability to create a two way command link between TeleDongle - and an altimeter using the digital radio transceivers built into - each device. This allows you to interact with the altimeter from - afar, as if it were directly connected to the computer. + One of the unique features of the Altus Metrum system is the + ability to create a two way command link between TeleDongle + and an altimeter using the digital radio transceivers + built into each device. This allows you to interact with the + altimeter from afar, as if it were directly connected to the + computer. </para> <para> Any operation which can be performed with TeleMetrum can @@ -1582,6 +1609,151 @@ NAR #88757, TRA #12200 </section> </chapter> <chapter> + <title>AltosDroid</title> + <para> + AltosDroid provides the same flight monitoring capabilities as + AltosUI, but runs on Android devices and is designed to connect + to a TeleBT receiver over Bluetooth™. Altos Droid monitors + telemetry data, logging it to internal storage in the Android + device, and presents that data in a UI the same way the 'Monitor + Flight' window does in AltosUI. + </para> + <para> + This manual will explain how to configure AltosDroid, connect + to TeleBT, operate the flight monitoring interface and describe + what the displayed data means. + </para> + <section> + <title>Installing AltosDroid</title> + <para> + AltosDroid is included in the Google Play store. To install + it on your Android device, open open the Google Play Store + application and search for "altosdroid". Make sure you don't + have a space between "altos" and "droid" or you probably won't + find what you want. That should bring you to the right page + from which you can download and install the application. + </para> + </section> + <section> + <title>Connecting to TeleBT</title> + <para> + Press the Android 'Menu' button or soft-key to see the + configuration options available. Select the 'Connect a device' + option and then the 'Scan for devices' entry at the bottom to + look for your TeleBT device. Select your device, and when it + asks for the code, enter '1234'. + </para> + <para> + Subsequent connections will not require you to enter that + code, and your 'paired' device will appear in the list without + scanning. + </para> + </section> + <section> + <title>Configuring AltosDroid</title> + <para> + The only configuration option available for AltosDroid is + which frequency to listen on. Press the Android 'Menu' button + or soft-key and pick the 'Select radio frequency' entry. That + brings up a menu of pre-set radio frequencies; pick the one + which matches your altimeter. + </para> + </section> + <section> + <title>Altos Droid Flight Monitoring</title> + <para> + Altos Droid is designed to mimic the AltosUI flight monitoring + display, providing separate tabs for each stage of your rocket + flight along with a tab containing a map of the local area + with icons marking the current location of the altimeter and + the Android device. + </para> + <section> + <title>Pad</title> + <para> + The 'Launch Pad' tab shows information used to decide when the + rocket is ready for flight. The first elements include red/green + indicators, if any of these is red, you'll want to evaluate + whether the rocket is ready to launch: + <itemizedlist> + <listitem> + <para> + Battery Voltage. This indicates whether the Li-Po battery + powering the TeleMetrum has sufficient charge to last for + the duration of the flight. A value of more than + 3.7V is required for a 'GO' status. + </para> + </listitem> + <listitem> + <para> + Apogee Igniter Voltage. This indicates whether the apogee + igniter has continuity. If the igniter has a low + resistance, then the voltage measured here will be close + to the Li-Po battery voltage. A value greater than 3.2V is + required for a 'GO' status. + </para> + </listitem> + <listitem> + <para> + Main Igniter Voltage. This indicates whether the main + igniter has continuity. If the igniter has a low + resistance, then the voltage measured here will be close + to the Li-Po battery voltage. A value greater than 3.2V is + required for a 'GO' status. + </para> + </listitem> + <listitem> + <para> + On-board Data Logging. This indicates whether there is + space remaining on-board to store flight data for the + upcoming flight. If you've downloaded data, but failed + to erase flights, there may not be any space + left. TeleMetrum can store multiple flights, depending + on the configured maximum flight log size. TeleMini + stores only a single flight, so it will need to be + downloaded and erased after each flight to capture + data. This only affects on-board flight logging; the + altimeter will still transmit telemetry and fire + ejection charges at the proper times. + </para> + </listitem> + <listitem> + <para> + GPS Locked. For a TeleMetrum device, this indicates whether the GPS receiver is + currently able to compute position information. GPS requires + at least 4 satellites to compute an accurate position. + </para> + </listitem> + <listitem> + <para> + GPS Ready. For a TeleMetrum device, this indicates whether GPS has reported at least + 10 consecutive positions without losing lock. This ensures + that the GPS receiver has reliable reception from the + satellites. + </para> + </listitem> + </itemizedlist> + <para> + The Launchpad tab also shows the computed launch pad position + and altitude, averaging many reported positions to improve the + accuracy of the fix. + </para> + </para> + </section> + </section> + <section> + <title>Downloading Flight Logs</title> + <para> + Altos Droid always saves every bit of telemetry data it + receives. To download that to a computer for use with AltosUI, + simply remove the SD card from your Android device, or connect + your device to your computer's USB port and browse the files + on that device. You will find '.telem' files in the TeleMetrum + directory that will work with AltosUI directly. + </para> + </section> + </chapter> + <chapter> <title>Using Altus Metrum Products</title> <section> <title>Being Legal</title> diff --git a/doc/micropeak.xsl b/doc/micropeak.xsl index 2120acb2..96179d01 100644 --- a/doc/micropeak.xsl +++ b/doc/micropeak.xsl @@ -224,12 +224,17 @@ NAR #88757, TRA #12200 </listitem> <listitem> <para> - The MicroPeak USB adapter has a small phototransistor on the - end of the board furthest from the USB connector. Locate - this and place the LED on the MicroPeak right over - it. Turn on the MicroPeak board and adjust the position - until the blue LED on the MicroPeak USB adapter blinks in - time with the orange LED on the MicroPeak board. + The MicroPeak USB adapter has a small phototransistor on + the end of the board furthest from the USB + connector. Locate this and place the LED on the MicroPeak + directly in contact with it. The MicroPeak LED and the + MicroPeak USB adapter photo need to be touching—even a + millimeters of space between them will reduce the light + intensity from the LED enough that the phototransistor + will not sense it. Turn on the MicroPeak board and adjust + the position until the blue LED on the MicroPeak USB + adapter blinks in time with the orange LED on the + MicroPeak board. </para> </listitem> <listitem> diff --git a/doc/release-notes-1.2.xsl b/doc/release-notes-1.2.xsl index 610fa1a2..b254c7b5 100644 --- a/doc/release-notes-1.2.xsl +++ b/doc/release-notes-1.2.xsl @@ -41,7 +41,7 @@ raised, breaking the Descent tab contents. </listitem> <listitem> - Add preliminary MegaMetrum support, including configuration, + Add preliminary TeleMega support, including configuration, data download and analysis. </listitem> <listitem> diff --git a/doc/megametrum-outline.pdf b/doc/telemega-outline.pdf Binary files differindex f8fc26e2..f8fc26e2 100644 --- a/doc/megametrum-outline.pdf +++ b/doc/telemega-outline.pdf diff --git a/doc/megametrum-outline.svg b/doc/telemega-outline.svg index e8d74d38..e8d74d38 100644 --- a/doc/megametrum-outline.svg +++ b/doc/telemega-outline.svg diff --git a/libaltos/libaltos.c b/libaltos/libaltos.c index 2a41ef80..fc949c70 100644 --- a/libaltos/libaltos.c +++ b/libaltos/libaltos.c @@ -736,30 +736,36 @@ struct altos_file * altos_bt_open(struct altos_bt_device *device) { struct sockaddr_rc addr = { 0 }; - int s, status; + int status, i; struct altos_file *file; file = calloc(1, sizeof (struct altos_file)); if (!file) goto no_file; - file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); - if (file->fd < 0) { - altos_set_last_posix_error(); - goto no_sock; - } - addr.rc_family = AF_BLUETOOTH; addr.rc_channel = 1; str2ba(device->addr, &addr.rc_bdaddr); - status = connect(file->fd, - (struct sockaddr *)&addr, - sizeof(addr)); + for (i = 0; i < 5; i++) { + file->fd = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (file->fd < 0) { + altos_set_last_posix_error(); + goto no_sock; + } + + status = connect(file->fd, + (struct sockaddr *)&addr, + sizeof(addr)); + if (status >= 0 || errno != EBUSY) + break; + close(file->fd); + usleep(100 * 1000); + } if (status < 0) { altos_set_last_posix_error(); goto no_link; } - sleep(1); + usleep(100 * 1000); #ifdef USE_POLL pipe(file->pipe); @@ -768,7 +774,7 @@ altos_bt_open(struct altos_bt_device *device) #endif return file; no_link: - close(s); + close(file->fd); no_sock: free(file); no_file: @@ -1319,6 +1325,7 @@ altos_open(struct altos_device *device) struct altos_file *file = calloc (1, sizeof (struct altos_file)); char full_name[64]; COMMTIMEOUTS timeouts; + int i; if (!file) return NULL; @@ -1326,7 +1333,15 @@ altos_open(struct altos_device *device) strcpy(full_name, "\\\\.\\"); strcat(full_name, device->path); - file->handle = open_serial(full_name); + file->handle = INVALID_HANDLE_VALUE; + + for (i = 0; i < 5; i++) { + file->handle = open_serial(full_name); + if (file->handle != INVALID_HANDLE_VALUE) + break; + Sleep(100); + } + if (file->handle == INVALID_HANDLE_VALUE) { free(file); return NULL; diff --git a/micropeak/MicroDownload.java b/micropeak/MicroDownload.java index 6f0ca4f6..bd6795f8 100644 --- a/micropeak/MicroDownload.java +++ b/micropeak/MicroDownload.java @@ -92,11 +92,14 @@ public class MicroDownload extends AltosUIDialog implements Runnable, ActionList serial.close(); serial_thread.interrupt(); } + setVisible(false); } public MicroDownload(MicroPeak owner, AltosDevice device) { super (owner, "Download MicroPeak Data", false); + int y = 0; + GridBagConstraints c; Insets il = new Insets(4,4,4,4); Insets ir = new Insets(4,4,4,4); @@ -108,7 +111,7 @@ public class MicroDownload extends AltosUIDialog implements Runnable, ActionList pane.setLayout(new GridBagLayout()); c = new GridBagConstraints(); - c.gridx = 0; c.gridy = 0; + c.gridx = 0; c.gridy = y; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LINE_START; c.insets = il; @@ -116,29 +119,66 @@ public class MicroDownload extends AltosUIDialog implements Runnable, ActionList pane.add(device_label, c); c = new GridBagConstraints(); - c.gridx = 1; c.gridy = 0; + c.gridx = 1; c.gridy = y; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1; c.anchor = GridBagConstraints.LINE_START; c.insets = ir; JLabel device_value = new JLabel(device.toString()); pane.add(device_value, c); + y++; + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = y; + c.gridwidth = GridBagConstraints.REMAINDER; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + JTextArea help_text = new JTextArea( + + "Locate the photo transistor on the MicroPeak USB adapter\n" + + "and place the LED on the MicroPeak directly in contact\n" + + "with it.\n" + + "\n" + + "The MicroPeak LED and the MicroPeak USB adapter\n" + + "photo need to be touching—even a millimeters of space\n" + + "between them will reduce the light intensity from the LED\n" + + "enough that the phototransistor will not sense it.\n" + + "\n" + + "Turn on the MicroPeak board and adjust the position until\n" + + "the blue LED on the MicroPeak USB adapter blinks in time\n" + + "with the orange LED on the MicroPeak board."); + + pane.add(help_text, c); + y++; + + c = new GridBagConstraints(); + c.gridx = 0; c.gridy = y; + c.gridwidth = 1; + c.fill = GridBagConstraints.HORIZONTAL; + c.weightx = 1; + c.anchor = GridBagConstraints.LINE_START; + c.insets = ir; + JLabel waiting_value = new JLabel("Waiting for MicroPeak data..."); + pane.add(waiting_value, c); cancel = new JButton("Cancel"); c = new GridBagConstraints(); c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.CENTER; - c.gridx = 0; c.gridy = 1; + c.gridx = 1; c.gridy = y; c.gridwidth = GridBagConstraints.REMAINDER; Insets ic = new Insets(4,4,4,4); c.insets = ic; pane.add(cancel, c); + y++; cancel.addActionListener(this); pack(); setLocationRelativeTo(owner); setVisible(true); - this.start(); + start(); } } diff --git a/src/Makefile b/src/Makefile index 9e31e3ea..e271ddf3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,7 +18,7 @@ SDCCDIRS=\ telemetrum-v1.2 telemetrum-v1.1 telemetrum-v1.0 \ teledongle-v0.2 teledongle-v0.1 \ telemini-v1.0 telenano-v0.1 \ - telebt-v0.0 telebt-v0.1 \ + telebt-v1.0 \ telemetrum-v0.1-sky telemetrum-v0.1-sirf \ telelaunch-v0.1 tidongle test \ teleterra-v0.2 teleshield-v0.1 \ @@ -29,7 +29,7 @@ AVRDIRS=\ telescience-v0.1 telescience-pwm telepyro-v0.1 micropeak ARMDIRS=\ - megametrum-v0.1 megadongle-v0.1 stm-bringup stm-demo telelco-v0.1 \ + telemega-v0.1 megadongle-v0.1 stm-bringup stm-demo telelco-v0.1 \ telescience-v0.2 ifneq ($(shell which sdcc),) diff --git a/src/cc1111/ao_adc.c b/src/cc1111/ao_adc.c index bfdc418a..ed76179b 100644 --- a/src/cc1111/ao_adc.c +++ b/src/cc1111/ao_adc.c @@ -153,6 +153,15 @@ ao_adc_isr(void) __interrupt 1 #define GOT_ADC #endif /* TELEFIRE_V_0_1 */ +#ifdef TELEBT_V_1_0 + a = (uint8_t __xdata *) (&ao_data_ring[ao_data_head].adc.batt); + a[0] = ADCL; + a[1] = ADCH; + if (0) + ; +#define GOT_ADC +#endif + #ifndef GOT_ADC #error No known ADC configuration set #endif diff --git a/src/core/ao.h b/src/core/ao.h index 7ff49dd5..548e8738 100644 --- a/src/core/ao.h +++ b/src/core/ao.h @@ -65,6 +65,7 @@ #define AO_PANIC_STACK 12 /* Stack overflow */ #define AO_PANIC_SPI 13 /* SPI communication failure */ #define AO_PANIC_CRASH 14 /* Processor crashed */ +#define AO_PANIC_BUFIO 15 /* Mis-using bufio API */ #define AO_PANIC_SELF_TEST_CC1120 0x40 | 1 /* Self test failure */ #define AO_PANIC_SELF_TEST_HMC5883 0x40 | 2 /* Self test failure */ #define AO_PANIC_SELF_TEST_MPU6000 0x40 | 3 /* Self test failure */ @@ -93,7 +94,7 @@ extern volatile __data AO_TICK_TYPE ao_tick_count; #define AO_SEC_TO_TICKS(s) ((s) * AO_HERTZ) /* Returns the current time in ticks */ -uint16_t +AO_TICK_TYPE ao_time(void); /* Suspend the current task until ticks time has passed */ @@ -517,17 +518,28 @@ extern __xdata uint8_t ao_radio_dma; #define AO_RADIO_STATUS_CRC_OK AO_FEC_DECODE_CRC_OK #endif +#ifndef HAS_RADIO_RECV +#define HAS_RADIO_RECV HAS_RADIO +#endif +#ifndef HAS_RADIO_XMIT +#define HAS_RADIO_XMIT HAS_RADIO +#endif + void ao_radio_general_isr(void) ao_arch_interrupt(16); +#if HAS_RADIO_XMIT void ao_radio_send(const __xdata void *d, uint8_t size) __reentrant; +#endif +#if HAS_RADIO_RECV uint8_t ao_radio_recv(__xdata void *d, uint8_t size) __reentrant; void ao_radio_recv_abort(void); +#endif void ao_radio_test(uint8_t on); @@ -535,7 +547,26 @@ ao_radio_test(uint8_t on); typedef int16_t (*ao_radio_fill_func)(uint8_t *buffer, int16_t len); void -ao_radio_send_lots(ao_radio_fill_func fill); +ao_radio_send_aprs(ao_radio_fill_func fill); + +/* + * ao_radio_pa + */ + +#if HAS_RADIO_AMP +void +ao_radio_pa_on(void); + +void +ao_radio_pa_off(void); + +void +ao_radio_pa_init(void); +#else +#define ao_radio_pa_on() +#define ao_radio_pa_off() +#define ao_radio_pa_init() +#endif /* * Compute the packet length as follows: @@ -687,7 +718,7 @@ extern __xdata uint8_t ao_force_freq; #endif #define AO_CONFIG_MAJOR 1 -#define AO_CONFIG_MINOR 13 +#define AO_CONFIG_MINOR 14 #define AO_AES_LEN 16 @@ -715,6 +746,12 @@ struct ao_config { struct ao_pyro pyro[AO_PYRO_NUM]; /* minor version 12 */ #endif uint16_t aprs_interval; /* minor version 13 */ +#if HAS_RADIO_POWER + uint8_t radio_power; /* minor version 14 */ +#endif +#if HAS_RADIO_AMP + uint8_t radio_amp; /* minor version 14 */ +#endif }; #define AO_IGNITE_MODE_DUAL 0 diff --git a/src/core/ao_config.c b/src/core/ao_config.c index 0aac16a6..73608a55 100644 --- a/src/core/ao_config.c +++ b/src/core/ao_config.c @@ -47,6 +47,8 @@ __xdata uint8_t ao_config_mutex; #define AO_CONFIG_DEFAULT_FLIGHT_LOG_MAX ((uint32_t) 192 * (uint32_t) 1024) #endif #endif +#define AO_CONFIG_DEFAULT_RADIO_POWER 0x60 +#define AO_CONFIG_DEFAULT_RADIO_AMP 0 #if HAS_EEPROM static void @@ -141,6 +143,14 @@ _ao_config_get(void) #endif if (minor < 13) ao_config.aprs_interval = 0; +#if HAS_RADIO_POWER + if (minor < 14) + ao_config.radio_power = AO_CONFIG_DEFAULT_RADIO_POWER; + #endif +#if HAS_RADIO_AMP + if (minor < 14) + ao_config.radio_amp = AO_CONFIG_DEFAULT_RADIO_AMP; +#endif ao_config.minor = AO_CONFIG_MINOR; ao_config_dirty = 1; } @@ -210,6 +220,7 @@ ao_config_callsign_set(void) __reentrant } #if HAS_RADIO + void ao_config_frequency_show(void) __reentrant { @@ -227,7 +238,9 @@ ao_config_frequency_set(void) __reentrant ao_config.frequency = ao_cmd_lex_u32; ao_config_set_radio(); _ao_config_edit_finish(); +#if HAS_RADIO_RECV ao_radio_recv_abort(); +#endif } #endif @@ -521,6 +534,48 @@ ao_config_aprs_set(void) #endif /* HAS_APRS */ +#if HAS_RADIO_AMP + +void +ao_config_radio_amp_show(void) +{ + printf ("Radio amp setting: %d\n", ao_config.radio_amp); +} + +void +ao_config_radio_amp_set(void) +{ + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + _ao_config_edit_start(); + ao_config.radio_amp = ao_cmd_lex_i; + _ao_config_edit_finish(); +} + +#endif + +#if HAS_RADIO_POWER + +void +ao_config_radio_power_show(void) +{ + printf ("Radio power setting: %d\n", ao_config.radio_power); +} + +void +ao_config_radio_power_set(void) +{ + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + _ao_config_edit_start(); + ao_config.radio_power = ao_cmd_lex_i; + _ao_config_edit_finish(); +} + +#endif + struct ao_config_var { __code char *str; void (*set)(void) __reentrant; @@ -554,6 +609,14 @@ __code struct ao_config_var ao_config_vars[] = { ao_config_radio_enable_set, ao_config_radio_enable_show }, { "f <cal>\0Radio calib (cal = rf/(xtal/2^16))", ao_config_radio_cal_set, ao_config_radio_cal_show }, +#if HAS_RADIO_POWER + { "p <setting>\0Radio power setting (0-255)", + ao_config_radio_power_set, ao_config_radio_power_show }, +#endif +#if HAS_RADIO_AMP + { "d <setting>\0Radio amplifier setting (0-3)", + ao_config_radio_amp_set, ao_config_radio_amp_show }, +#endif #endif /* HAS_RADIO */ #if HAS_ACCEL { "a <+g> <-g>\0Accel calib (0 for auto)", diff --git a/src/core/ao_fec_rx.c b/src/core/ao_fec_rx.c index 072a9e90..c4f5559a 100644 --- a/src/core/ao_fec_rx.c +++ b/src/core/ao_fec_rx.c @@ -18,7 +18,7 @@ #include <ao_fec.h> #include <stdio.h> -#ifdef MEGAMETRUM +#ifdef TELEMEGA #include <ao.h> #endif diff --git a/src/core/ao_gps_report_mega.c b/src/core/ao_gps_report_mega.c index 47891cab..e3af4307 100644 --- a/src/core/ao_gps_report_mega.c +++ b/src/core/ao_gps_report_mega.c @@ -16,6 +16,7 @@ */ #include "ao.h" +#include "ao_log.h" void ao_gps_report_mega(void) diff --git a/src/core/ao_log.h b/src/core/ao_log.h index 036d6f2d..a68a40dd 100644 --- a/src/core/ao_log.h +++ b/src/core/ao_log.h @@ -43,7 +43,7 @@ extern __pdata enum ao_flight_state ao_log_state; #define AO_LOG_FORMAT_TINY 2 /* two byte state/baro records */ #define AO_LOG_FORMAT_TELEMETRY 3 /* 32 byte ao_telemetry records */ #define AO_LOG_FORMAT_TELESCIENCE 4 /* 32 byte typed telescience records */ -#define AO_LOG_FORMAT_MEGAMETRUM 5 /* 32 byte typed megametrum records */ +#define AO_LOG_FORMAT_TELEMEGA 5 /* 32 byte typed telemega records */ #define AO_LOG_FORMAT_NONE 127 /* No log at all */ extern __code uint8_t ao_log_format; @@ -268,4 +268,7 @@ ao_log_data(__xdata struct ao_log_record *log) __reentrant; uint8_t ao_log_mega(__xdata struct ao_log_mega *log) __reentrant; +void +ao_log_flush(void); + #endif /* _AO_LOG_H_ */ diff --git a/src/core/ao_log_mega.c b/src/core/ao_log_mega.c index e03687ad..abf953a6 100644 --- a/src/core/ao_log_mega.c +++ b/src/core/ao_log_mega.c @@ -23,7 +23,7 @@ static __xdata uint8_t ao_log_mutex; static __xdata struct ao_log_mega log; -__code uint8_t ao_log_format = AO_LOG_FORMAT_MEGAMETRUM; +__code uint8_t ao_log_format = AO_LOG_FORMAT_TELEMEGA; static uint8_t ao_log_csum(__xdata uint8_t *b) __reentrant @@ -171,6 +171,8 @@ ao_log(void) } #endif + ao_log_flush(); + /* Wait for a while */ ao_delay(AO_MS_TO_TICKS(100)); diff --git a/src/core/ao_telemetry.c b/src/core/ao_telemetry.c index 8d440e15..03aa48d8 100644 --- a/src/core/ao_telemetry.c +++ b/src/core/ao_telemetry.c @@ -16,6 +16,7 @@ */ #include "ao.h" +#include "ao_log.h" #include "ao_product.h" static __pdata uint16_t ao_telemetry_interval; @@ -28,7 +29,7 @@ static __pdata uint16_t ao_aprs_time; #include <ao_aprs.h> #endif -#if defined(MEGAMETRUM) +#if defined(TELEMEGA) #define AO_SEND_MEGA 1 #endif @@ -306,12 +307,14 @@ ao_telemetry(void) #ifdef AO_SEND_ALL_BARO ao_send_baro(); #endif +#if HAS_FLIGHT #ifdef AO_SEND_MEGA ao_send_mega_sensor(); ao_send_mega_data(); #else ao_send_sensor(); #endif +#endif #if HAS_COMPANION if (ao_companion_running) diff --git a/src/drivers/ao_aprs.c b/src/drivers/ao_aprs.c index 03bcab05..6ab61e6a 100644 --- a/src/drivers/ao_aprs.c +++ b/src/drivers/ao_aprs.c @@ -593,7 +593,7 @@ void ao_aprs_send(void) tncIndex = 0; tncMode = TNC_TX_SYNC; - ao_radio_send_lots(tncFill); + ao_radio_send_aprs(tncFill); } /** @} */ diff --git a/src/drivers/ao_btm.c b/src/drivers/ao_btm.c index de1f31a3..3b6028a0 100644 --- a/src/drivers/ao_btm.c +++ b/src/drivers/ao_btm.c @@ -302,7 +302,7 @@ ao_btm(void) while (!ao_btm_connected) ao_sleep(&ao_btm_connected); while (ao_btm_connected) { - ao_led_for(AO_LED_GREEN, AO_MS_TO_TICKS(20)); + ao_led_for(AO_BT_LED, AO_MS_TO_TICKS(20)); ao_delay(AO_SEC_TO_TICKS(3)); } } diff --git a/src/drivers/ao_bufio.c b/src/drivers/ao_bufio.c new file mode 100644 index 00000000..c0fe604a --- /dev/null +++ b/src/drivers/ao_bufio.c @@ -0,0 +1,321 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef AO_FAT_TEST +#include "ao.h" +#endif + +/* Include bufio commands */ +#ifndef AO_FAT_TEST +#define BUFIO_COMMANDS 0 +#endif + +#include "ao_sdcard.h" +#include "ao_bufio.h" + +#define AO_NUM_BUF 16 +#define AO_BUFSIZ 512 + +struct ao_bufio { + uint32_t block; + int16_t seqno; + uint8_t busy; + uint8_t dirty; +}; + +static struct ao_bufio ao_bufio[AO_NUM_BUF]; +static uint8_t ao_buffer[AO_NUM_BUF][AO_BUFSIZ]; +static int16_t ao_seqno; +static uint8_t ao_bufio_mutex; + +#if 0 +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +static inline void +ao_bufio_lock(void) +{ + ao_mutex_get(&ao_bufio_mutex); +} + +static inline void +ao_bufio_unlock(void) +{ + ao_mutex_put(&ao_bufio_mutex); +} + +static inline int16_t +ao_seqno_age(int16_t s) +{ + return ao_seqno - s; +} + +static inline int16_t +ao_seqno_next(void) +{ + return ++ao_seqno; +} + +static inline int +ao_seqno_older(int16_t a, int16_t b) +{ + return ao_seqno_age(a) > ao_seqno_age(b); +} + +static inline void +ao_validate_bufno(int b) +{ + if (b < 0 || AO_NUM_BUF <= b) + ao_panic(AO_PANIC_BUFIO); +} + +static inline int +ao_buf_to_num(uint8_t *buf) +{ + int b = (buf - &ao_buffer[0][0]) / AO_BUFSIZ; + + ao_validate_bufno(b); + return b; +} + +static inline int +ao_bufio_to_num(struct ao_bufio *bufio) +{ + int b = (bufio - ao_bufio); + + ao_validate_bufno(b); + return b; +} + +static inline struct ao_bufio * +ao_buf_to_bufio(uint8_t *buf) +{ + int b = ao_buf_to_num(buf); + struct ao_bufio *bufio; + + bufio = &ao_bufio[b]; + DBG ("buf %08x is %d bufio %08x\n", buf, b, bufio); + return bufio; +} + +static inline uint8_t * +ao_bufio_to_buf(struct ao_bufio *bufio) +{ + int b = ao_bufio_to_num(bufio); + uint8_t *buf; + + buf = &ao_buffer[b][0]; + DBG ("bufio %08x is %d buf %08x\n", bufio, b, buf); + return buf; +} + +/* + * Write a buffer to storage if it is dirty + */ +static void +ao_bufio_write(struct ao_bufio *bufio) +{ + if (bufio->dirty) { + ao_sdcard_write_block(bufio->block, ao_bufio_to_buf(bufio)); + bufio->dirty = 0; + } +} + +/* + * Read a buffer from storage + */ +static uint8_t +ao_bufio_read(struct ao_bufio *bufio) +{ + uint8_t *buf = ao_bufio_to_buf(bufio); + + return ao_sdcard_read_block(bufio->block, buf); +} + +/* + * Find a buffer containing the specified block + */ +static struct ao_bufio * +ao_bufio_find_block(uint32_t block) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (bufio->block == block) { + DBG ("Found existing buffer %d (seqno %d)\n", + ao_bufio_to_num(bufio), bufio->seqno); + return bufio; + } + } + return NULL; +} + +/* + * Find the least recently used idle buffer + */ +static struct ao_bufio * +ao_bufio_find_idle(void) +{ + int b; + struct ao_bufio *oldest = NULL; + + for (b = 0; b < AO_NUM_BUF; b++) { + struct ao_bufio *bufio = &ao_bufio[b]; + if (!bufio->busy) + if (!oldest || ao_seqno_older(bufio->seqno, oldest->seqno)) + oldest = bufio; + } + if (oldest) + DBG ("Using idle buffer %d (seqno %d)\n", + ao_bufio_to_num(oldest), oldest->seqno); + return oldest; +} + +/* + * Return a pointer to a buffer containing + * the contents of the specified block + */ +uint8_t * +ao_bufio_get(uint32_t block) +{ + struct ao_bufio *bufio; + uint8_t *buf = NULL; + + ao_bufio_lock(); + bufio = ao_bufio_find_block(block); + if (!bufio) { + bufio = ao_bufio_find_idle(); + if (bufio) { + ao_bufio_write(bufio); + bufio->block = block; + DBG ("read buffer\n"); + if (!ao_bufio_read(bufio)) { + bufio->block = 0xffffffff; + bufio = NULL; + } + } else + ao_panic(AO_PANIC_BUFIO); + } + if (bufio) { + bufio->busy++; + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + buf = ao_bufio_to_buf(bufio); + } + ao_bufio_unlock(); + return buf; +} + +/* + * Release a buffer, marking it dirty + * if it has been written to + */ +void +ao_bufio_put(uint8_t *buf, uint8_t write) +{ + struct ao_bufio *bufio; + + ao_bufio_lock(); + bufio = ao_buf_to_bufio(buf); + + if (!bufio->busy) + ao_panic(AO_PANIC_BUFIO); + + DBG ("idle buffer %d write %d\n", ao_bufio_to_num(bufio), write); + bufio->dirty |= write; + if (!--bufio->busy) { + bufio->seqno = ao_seqno_next(); + DBG ("not busy, seqno %d\n", bufio->seqno); + } + ao_bufio_unlock(); +} + +/* + * Flush a single buffer immediately. Useful + * if write order is important + */ +void +ao_bufio_flush_one(uint8_t *buf) +{ + ao_bufio_lock(); + ao_bufio_write(ao_buf_to_bufio(buf)); + ao_bufio_unlock(); +} + +/* + * Flush all buffers to storage + */ +void +ao_bufio_flush(void) +{ + int b; + + ao_bufio_lock(); + for (b = 0; b < AO_NUM_BUF; b++) + ao_bufio_write(&ao_bufio[b]); + ao_bufio_unlock(); +} + +#if BUFIO_COMMANDS +static void +ao_bufio_test_read(void) +{ + uint8_t *buf; + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + if ((buf = ao_bufio_get(ao_cmd_lex_u32))) { + int i; + for (i = 0; i < 512; i++) { + printf (" %02x", buf[i]); + if ((i & 0xf) == 0xf) + printf("\n"); + } + ao_bufio_put(buf, 0); + } +} + +static const struct ao_cmds ao_bufio_cmds[] = { + { ao_bufio_test_read, "q\0Test bufio read" }, + { 0, NULL }, +}; +#endif + +void +ao_bufio_setup(void) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + ao_bufio[b].dirty = 0; + ao_bufio[b].busy = 0; + ao_bufio[b].block = 0xffffffff; + } +} + +void +ao_bufio_init(void) +{ + ao_bufio_setup(); + ao_sdcard_init(); +#if BUFIO_COMMANDS + ao_cmd_register(&ao_bufio_cmds[0]); +#endif +} diff --git a/src/drivers/ao_bufio.h b/src/drivers/ao_bufio.h new file mode 100644 index 00000000..6629f143 --- /dev/null +++ b/src/drivers/ao_bufio.h @@ -0,0 +1,39 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_BUFIO_H_ +#define _AO_BUFIO_H_ + +uint8_t * +ao_bufio_get(uint32_t block); + +void +ao_bufio_put(uint8_t *buf, uint8_t write); + +void +ao_bufio_flush_one(uint8_t *buf); + +void +ao_bufio_flush(void); + +void +ao_bufio_setup(void); + +void +ao_bufio_init(void); + +#endif /* _AO_BUFIO_H_ */ diff --git a/src/drivers/ao_cc1120.c b/src/drivers/ao_cc1120.c index 53bb5a62..a26eccbc 100644 --- a/src/drivers/ao_cc1120.c +++ b/src/drivers/ao_cc1120.c @@ -747,7 +747,7 @@ ao_radio_send(const void *d, uint8_t size) #define AO_RADIO_LOTS 64 void -ao_radio_send_lots(ao_radio_fill_func fill) +ao_radio_send_aprs(ao_radio_fill_func fill) { uint8_t buf[AO_RADIO_LOTS], *b; int cnt; diff --git a/src/drivers/ao_cc115l.c b/src/drivers/ao_cc115l.c new file mode 100644 index 00000000..30c56442 --- /dev/null +++ b/src/drivers/ao_cc115l.c @@ -0,0 +1,958 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <ao.h> +#include <ao_cc115l.h> +#include <ao_exti.h> +#include <ao_telemetry.h> +#include <ao_fec.h> + +#define AO_RADIO_MAX_SEND sizeof (struct ao_telemetry_generic) + +uint8_t ao_radio_mutex; + +static uint8_t ao_radio_fifo; /* fifo drained interrupt received */ +static uint8_t ao_radio_done; /* tx done interrupt received */ +static uint8_t ao_radio_wake; /* sleep address for radio interrupts */ +static uint8_t ao_radio_abort; /* radio operation should abort */ +static uint8_t ao_radio_mcu_wake; /* MARC status change */ +static uint8_t ao_radio_marcstate; /* Last read MARC state value */ + +/* Debugging commands */ +#define CC115L_DEBUG 0 + +/* Runtime tracing */ +#define CC115L_TRACE 0 + +#define FOSC 26000000 + +#define ao_radio_select() ao_spi_get_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS,AO_SPI_SPEED_1MHz) +#define ao_radio_deselect() ao_spi_put_mask(AO_CC115L_SPI_CS_PORT,(1 << AO_CC115L_SPI_CS_PIN),AO_CC115L_SPI_BUS) +#define ao_radio_spi_send(d,l) ao_spi_send((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_spi_recv(d,l) ao_spi_recv((d), (l), AO_CC115L_SPI_BUS) +#define ao_radio_duplex(o,i,l) ao_spi_duplex((o), (i), (l), AO_CC115L_SPI_BUS) + +struct ao_cc115l_reg { + uint16_t addr; + char *name; +}; + +#if CC115L_TRACE + +const static struct ao_cc115l_reg ao_cc115l_reg[]; +const static char *cc115l_state_name[]; + +enum ao_cc115l_trace_type { + trace_strobe, + trace_read, + trace_write, + trace_dma, + trace_line, +}; + +struct ao_cc115l_trace { + enum ao_cc115l_trace_type type; + int16_t addr; + int16_t value; + const char *comment; +}; + +#define NUM_TRACE 256 + +static struct ao_cc115l_trace trace[NUM_TRACE]; +static int trace_i; +static int trace_disable; + +static void trace_add(enum ao_cc115l_trace_type type, int16_t addr, int16_t value, const char *comment) +{ + if (trace_disable) + return; + switch (type) { + case trace_read: + case trace_write: + comment = ao_cc115l_reg[addr].name; + break; + case trace_strobe: + comment = cc115l_state_name[(value >> 4) & 0x7]; + break; + } + trace[trace_i].type = type; + trace[trace_i].addr = addr; + trace[trace_i].value = value; + trace[trace_i].comment = comment; + if (++trace_i == NUM_TRACE) + trace_i = 0; +} +#else +#define trace_add(t,a,v,c) +#endif + +static uint8_t +ao_radio_reg_read(uint8_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); + ao_radio_spi_recv(data, 1); + ao_radio_deselect(); + trace_add(trace_read, addr, data[0], NULL); + return data[0]; +} + +static void +ao_radio_reg_write(uint8_t addr, uint8_t value) +{ + uint8_t data[2]; + uint8_t d; + + trace_add(trace_write, addr, value, NULL); + data[0] = ((0 << CC115L_READ) | + (0 << CC115L_BURST) | + addr); + data[1] = value; + ao_radio_select(); + ao_radio_spi_send(data, 2); + ao_radio_deselect(); +} + +static void +ao_radio_burst_read_start (uint16_t addr) +{ + uint8_t data[1]; + uint8_t d; + + data[0] = ((1 << CC115L_READ) | + (1 << CC115L_BURST) | + addr); + ao_radio_select(); + ao_radio_spi_send(data, 1); +} + +static void +ao_radio_burst_read_stop (void) +{ + ao_radio_deselect(); +} + + +static uint8_t +ao_radio_strobe(uint8_t addr) +{ + uint8_t in; + + ao_radio_select(); + ao_radio_duplex(&addr, &in, 1); + ao_radio_deselect(); + trace_add(trace_strobe, addr, in, NULL); + return in; +} + +static uint8_t +ao_radio_fifo_write_start(void) +{ + uint8_t addr = ((0 << CC115L_READ) | + (1 << CC115L_BURST) | + CC115L_FIFO); + uint8_t status; + + ao_radio_select(); + ao_radio_duplex(&addr, &status, 1); + return status; +} + +static inline uint8_t ao_radio_fifo_write_stop(uint8_t status) { + ao_radio_deselect(); + return status; +} + +static uint8_t +ao_radio_fifo_write(uint8_t *data, uint8_t len) +{ + uint8_t status = ao_radio_fifo_write_start(); + trace_add(trace_dma, CC115L_FIFO, len, NULL); + ao_radio_spi_send(data, len); + return ao_radio_fifo_write_stop(status); +} + +static uint8_t +ao_radio_tx_fifo_space(void) +{ + return CC115L_FIFO_SIZE - (ao_radio_reg_read(CC115L_TXBYTES) & CC115L_TXBYTES_NUM_TX_BYTES_MASK); +} + +static uint8_t +ao_radio_status(void) +{ + return ao_radio_strobe (CC115L_SNOP); +} + +#define ao_radio_rdf_value 0x55 + +static uint8_t +ao_radio_get_marcstate(void) +{ + return ao_radio_reg_read(CC115L_MARCSTATE) & CC115L_MARCSTATE_MASK; +} + +static void +ao_radio_done_isr(void) +{ + ao_exti_disable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + trace_add(trace_line, __LINE__, 0, "done_isr"); + ao_radio_done = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_fifo_isr(void) +{ + ao_exti_disable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + trace_add(trace_line, __LINE__, 0, "fifo_isr"); + ao_radio_fifo = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_start_tx(void) +{ +} + +static void +ao_radio_idle(void) +{ + ao_radio_pa_off(); + for (;;) { + uint8_t state = ao_radio_strobe(CC115L_SIDLE); + if ((state >> CC115L_STATUS_STATE) == CC115L_STATUS_STATE_IDLE) + break; + } + /* Flush any pending TX bytes */ + ao_radio_strobe(CC115L_SFTX); +} + +/* + * Packet deviation is 20.5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 5) * (2 ** 3) = 20630Hz + */ + +#define PACKET_DEV_E 3 +#define PACKET_DEV_M 5 + +/* + * For our packet data, set the symbol rate to 38400 Baud + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 10) / (2**28) * 26e6 = 38383 + * + * DATARATE_M = 131 + * DATARATE_E = 10 + */ +#define PACKET_DRATE_E 10 +#define PACKET_DRATE_M 131 + +static const uint16_t packet_setup[] = { + CC115L_DEVIATN, ((PACKET_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (PACKET_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (PACKET_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (PACKET_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_16BITS << CC115L_MDMCFG2_SYNC_MODE)), +}; + + +/* + * RDF deviation is 5kHz + * + * fdev = fosc >> 17 * (8 + dev_m) << dev_e + * + * 26e6 / (2 ** 17) * (8 + 4) * (2 ** 1) = 4761Hz + */ + +#define RDF_DEV_E 1 +#define RDF_DEV_M 4 + +/* + * For our RDF beacon, set the symbol rate to 2kBaud (for a 1kHz tone) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 67) * (2 ** 6) / (2**28) * 26e6 = 2002 + * + * DATARATE_M = 67 + * DATARATE_E = 6 + */ +#define RDF_DRATE_E 6 +#define RDF_DRATE_M 67 + +static const uint16_t rdf_setup[] = { + CC115L_DEVIATN, ((RDF_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (RDF_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (RDF_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (RDF_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +/* + * APRS deviation is the same as RDF + */ + +#define APRS_DEV_E RDF_DEV_E +#define APRS_DEV_M RDF_DEV_M + +/* + * For our APRS beacon, set the symbol rate to 9.6kBaud (8x oversampling for 1200 baud data rate) + * + * (256 + DATARATE_M) * 2 ** DATARATE_E + * Rdata = -------------------------------------- * fosc + * 2 ** 28 + * + * (256 + 131) * (2 ** 8) / (2**28) * 26e6 = 9596 + * + * DATARATE_M = 131 + * DATARATE_E = 8 + * + */ +#define APRS_DRATE_E 8 +#define APRS_DRATE_M 131 + +static const uint16_t aprs_setup[] = { + CC115L_DEVIATN, ((APRS_DEV_E << CC115L_DEVIATN_DEVIATION_E) | + (APRS_DEV_M << CC115L_DEVIATN_DEVIATION_M)), + CC115L_MDMCFG4, ((0xf << 4) | + (APRS_DRATE_E << CC115L_MDMCFG4_DRATE_E)), + CC115L_MDMCFG3, (APRS_DRATE_M), + CC115L_MDMCFG2, (0x00 | + (CC115L_MDMCFG2_MOD_FORMAT_GFSK << CC115L_MDMCFG2_MOD_FORMAT) | + (0 << CC115L_MDMCFG2_MANCHESTER_EN) | + (CC115L_MDMCFG2_SYNC_MODE_NONE << CC115L_MDMCFG2_SYNC_MODE)), +}; + +#define AO_PKTCTRL0_INFINITE ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) +#define AO_PKTCTRL0_FIXED ((CC115L_PKTCTRL0_PKT_FORMAT_NORMAL << CC115L_PKTCTRL0_PKT_FORMAT) | \ + (0 << CC115L_PKTCTRL0_PKT_CRC_EN) | \ + (CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED << CC115L_PKTCTRL0_PKT_LENGTH_CONFIG)) + +static uint16_t ao_radio_mode; + + +/* + * These set the data rate and modulation parameters + */ +#define AO_RADIO_MODE_BITS_PACKET_TX 1 +#define AO_RADIO_MODE_BITS_RDF 2 +#define AO_RADIO_MODE_BITS_APRS 4 + +/* + * Flips between infinite packet mode and fixed packet mode; + * we use infinite mode until the sender gives us the + * last chunk of data + */ +#define AO_RADIO_MODE_BITS_INFINITE 40 +#define AO_RADIO_MODE_BITS_FIXED 80 + +#define AO_RADIO_MODE_NONE 0 + +#define AO_RADIO_MODE_RDF AO_RADIO_MODE_BITS_RDF +#define AO_RADIO_MODE_PACKET_TX AO_RADIO_MODE_BITS_PACKET_TX +#define AO_RADIO_MODE_APRS AO_RADIO_MODE_BITS_APRS + +static void +ao_radio_set_mode(uint16_t new_mode) +{ + uint16_t changes; + int i; + + if (new_mode == ao_radio_mode) + return; + + changes = new_mode & (~ao_radio_mode); + if (changes & AO_RADIO_MODE_BITS_PACKET_TX) + for (i = 0; i < sizeof (packet_setup) / sizeof (packet_setup[0]); i += 2) + ao_radio_reg_write(packet_setup[i], packet_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_RDF) + for (i = 0; i < sizeof (rdf_setup) / sizeof (rdf_setup[0]); i += 2) + ao_radio_reg_write(rdf_setup[i], rdf_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_APRS) + for (i = 0; i < sizeof (aprs_setup) / sizeof (aprs_setup[0]); i += 2) + ao_radio_reg_write(aprs_setup[i], aprs_setup[i+1]); + + if (changes & AO_RADIO_MODE_BITS_INFINITE) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_INFINITE); + + if (changes & AO_RADIO_MODE_BITS_FIXED) + ao_radio_reg_write(CC115L_PKTCTRL0, AO_PKTCTRL0_FIXED); + + ao_radio_mode = new_mode; +} + +/*************************************************************** + * SmartRF Studio(tm) Export + * + * Radio register settings specifed with address, value + * + * RF device: CC115L + * + ***************************************************************/ + +static const uint16_t radio_setup[] = { + + /* High when FIFO is above threshold, low when fifo is below threshold */ + AO_CC115L_FIFO_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_TXFIFO_THR, + + /* High when transmitter is running, low when off */ + AO_CC115L_DONE_INT_GPIO_IOCFG, CC115L_IOCFG_GPIO_CFG_PA_PD | (1 << CC115L_IOCFG_GPIO_INV), + + CC115L_FIFOTHR, 0x47, /* TX FIFO Thresholds */ + CC115L_MDMCFG1, (0x00 | + (CC115L_MDMCFG1_NUM_PREAMBLE_4 << CC115L_MDMCFG1_NUM_PREAMBLE) | + (1 << CC115L_MDMCFG1_CHANSPC_E)), + CC115L_MDMCFG0, 248, /* Channel spacing M value (100kHz channels) */ + CC115L_MCSM0, 0x38, /* Main Radio Control State Machine Configuration */ + CC115L_RESERVED_0X20, 0xfb, /* Use setting from SmartRF Studio */ + CC115L_FSCAL3, 0xe9, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL2, 0x2a, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL1, 0x00, /* Frequency Synthesizer Calibration */ + CC115L_FSCAL0, 0x1f, /* Frequency Synthesizer Calibration */ + CC115L_TEST2, 0x81, /* Various Test Settings */ + CC115L_TEST1, 0x35, /* Various Test Settings */ + CC115L_TEST0, 0x09, /* Various Test Settings */ +}; + +static uint8_t ao_radio_configured = 0; + +static void +ao_radio_setup(void) +{ + int i; + + ao_radio_strobe(CC115L_SRES); + ao_delay(AO_MS_TO_TICKS(10)); + + for (i = 0; i < sizeof (radio_setup) / sizeof (radio_setup[0]); i += 2) + ao_radio_reg_write(radio_setup[i], radio_setup[i+1]); + + ao_radio_mode = 0; + + ao_config_get(); + + ao_radio_configured = 1; +} + +static void +ao_radio_set_len(uint8_t len) +{ + static uint8_t last_len; + + if (len != last_len) { + ao_radio_reg_write(CC115L_PKTLEN, len); + last_len = len; + } +} + +static void +ao_radio_get(void) +{ + static uint32_t last_radio_setting; + static uint8_t last_power_setting; + + ao_mutex_get(&ao_radio_mutex); + if (!ao_radio_configured) + ao_radio_setup(); + if (ao_config.radio_setting != last_radio_setting) { + ao_radio_reg_write(CC115L_FREQ2, ao_config.radio_setting >> 16); + ao_radio_reg_write(CC115L_FREQ1, ao_config.radio_setting >> 8); + ao_radio_reg_write(CC115L_FREQ0, ao_config.radio_setting); + last_radio_setting = ao_config.radio_setting; + } + if (ao_config.radio_power != last_power_setting) { + ao_radio_reg_write(CC115L_PA, ao_config.radio_power); + last_power_setting = ao_config.radio_power; + } +} + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode); + +#define ao_radio_put() ao_mutex_put(&ao_radio_mutex) + +struct ao_radio_tone { + uint8_t value; + uint8_t len; +}; + +struct ao_radio_tone *ao_radio_tone; +uint8_t ao_radio_tone_count; +uint8_t ao_radio_tone_current; +uint8_t ao_radio_tone_offset; + +int16_t +ao_radio_tone_fill(uint8_t *buf, int16_t len) +{ + int16_t ret = 0; + + while (len) { + int16_t this_time; + struct ao_radio_tone *t; + + /* Figure out how many to send of the current value */ + t = &ao_radio_tone[ao_radio_tone_current]; + this_time = t->len - ao_radio_tone_offset; + if (this_time > len) + this_time = len; + + /* queue the data */ + memset(buf, t->value, this_time); + + /* mark as sent */ + len -= this_time; + ao_radio_tone_offset += this_time; + ret += this_time; + + if (ao_radio_tone_offset >= t->len) { + ao_radio_tone_offset = 0; + ao_radio_tone_current++; + if (ao_radio_tone_current >= ao_radio_tone_count) { + trace_add(trace_line, __LINE__, ret, "done with tone"); + return -ret; + } + } + } + trace_add(trace_line, __LINE__, ret, "got some tone"); + return ret; +} + +static void +ao_radio_tone_run(struct ao_radio_tone *tones, int ntones) +{ + ao_radio_get(); + ao_radio_tone = tones; + ao_radio_tone_current = 0; + ao_radio_tone_offset = 0; + _ao_radio_send_lots(ao_radio_tone_fill, AO_RADIO_MODE_RDF); + ao_radio_put(); +} + +void +ao_radio_rdf(void) +{ + struct ao_radio_tone tone; + + tone.value = ao_radio_rdf_value; + tone.len = AO_RADIO_RDF_LEN; + ao_radio_tone_run(&tone, 1); +} + +void +ao_radio_continuity(uint8_t c) +{ + struct ao_radio_tone tones[7]; + uint8_t count = 0; + uint8_t i; + + for (i = 0; i < 3; i++) { + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + if (i < c) + tones[count].value = ao_radio_rdf_value; + else + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_TONE_LEN; + count++; + } + tones[count].value = 0x00; + tones[count].len = AO_RADIO_CONT_PAUSE_LEN; + count++; + ao_radio_tone_run(tones, count); +} + +void +ao_radio_rdf_abort(void) +{ + ao_radio_abort = 1; + ao_wakeup(&ao_radio_wake); +} + +static void +ao_radio_test_cmd(void) +{ + uint8_t mode = 2; + static uint8_t radio_on; + ao_cmd_white(); + if (ao_cmd_lex_c != '\n') { + ao_cmd_decimal(); + mode = (uint8_t) ao_cmd_lex_u32; + } + mode++; + if ((mode & 2) && !radio_on) { +#if HAS_MONITOR + ao_monitor_disable(); +#endif +#if PACKET_HAS_SLAVE + ao_packet_slave_stop(); +#endif + ao_radio_get(); + ao_radio_set_len(0xff); + ao_radio_set_mode(AO_RADIO_MODE_RDF|AO_RADIO_MODE_BITS_FIXED); + ao_radio_strobe(CC115L_SFTX); + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + radio_on = 1; + } + if (mode == 3) { + printf ("Hit a character to stop..."); flush(); + getchar(); + putchar('\n'); + } + if ((mode & 1) && radio_on) { + ao_radio_idle(); + ao_radio_put(); + radio_on = 0; +#if HAS_MONITOR + ao_monitor_enable(); +#endif + } +} + +static inline int16_t +ao_radio_gpio_bits(void) +{ + return AO_CC115L_DONE_INT_PORT->idr & ((1 << AO_CC115L_FIFO_INT_PIN) | + (1 << AO_CC115L_DONE_INT_PIN)); +} + +static void +ao_radio_wait_fifo(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_fifo && !ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_fifo"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static void +ao_radio_wait_done(void) +{ + ao_arch_block_interrupts(); + while (!ao_radio_done && !ao_radio_abort) { + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wait_done"); + ao_sleep(&ao_radio_wake); + } + ao_arch_release_interrupts(); + trace_add(trace_line, __LINE__, ao_radio_gpio_bits(), "wake bits"); + trace_add(trace_line, __LINE__, ao_radio_fifo, "wake fifo"); + trace_add(trace_line, __LINE__, ao_radio_done, "wake done"); + trace_add(trace_line, __LINE__, ao_radio_abort, "wake abort"); +} + +static uint8_t tx_data[(AO_RADIO_MAX_SEND + 4) * 2]; + +static uint8_t *ao_radio_send_buf; +static int16_t ao_radio_send_len; + +static int16_t +ao_radio_send_fill(uint8_t *buf, int16_t len) +{ + int16_t this_time; + + this_time = ao_radio_send_len; + if (this_time > len) + this_time = len; + memcpy(buf, ao_radio_send_buf, this_time); + ao_radio_send_buf += this_time; + ao_radio_send_len -= this_time; + if (ao_radio_send_len == 0) + return -this_time; + return this_time; +} + +void +ao_radio_send(const void *d, uint8_t size) +{ + int i; + + ao_radio_get(); + ao_radio_send_len = ao_fec_encode(d, size, tx_data); + ao_radio_send_buf = tx_data; + _ao_radio_send_lots(ao_radio_send_fill, AO_RADIO_MODE_PACKET_TX); + ao_radio_put(); +} + +#define AO_RADIO_LOTS 64 + +static void +_ao_radio_send_lots(ao_radio_fill_func fill, uint8_t mode) +{ + uint8_t buf[AO_RADIO_LOTS], *b; + int cnt; + int total = 0; + uint8_t done = 0; + uint8_t started = 0; + uint8_t fifo_space; + + fifo_space = CC115L_FIFO_SIZE; + ao_radio_done = 0; + ao_radio_fifo = 0; + while (!done) { + cnt = (*fill)(buf, sizeof(buf)); + trace_add(trace_line, __LINE__, cnt, "send data count"); + if (cnt < 0) { + done = 1; + cnt = -cnt; + } + total += cnt; + + /* At the last buffer, set the total length */ + if (done) { + ao_radio_set_len(total & 0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_FIXED); + } else { + ao_radio_set_len(0xff); + ao_radio_set_mode(mode | AO_RADIO_MODE_BITS_INFINITE); + } + + b = buf; + while (cnt) { + uint8_t this_len = cnt; + + /* Wait for some space in the fifo */ + while (!ao_radio_abort && (fifo_space = ao_radio_tx_fifo_space()) == 0) { + trace_add(trace_line, __LINE__, this_len, "wait for space"); + ao_radio_wait_fifo(); + } + if (ao_radio_abort || ao_radio_done) + break; + trace_add(trace_line, __LINE__, fifo_space, "got space"); + if (this_len > fifo_space) + this_len = fifo_space; + + cnt -= this_len; + + ao_radio_done = 0; + ao_radio_fifo = 0; + ao_radio_fifo_write(b, this_len); + b += this_len; + + ao_exti_enable(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN); + ao_exti_enable(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN); + + if (!started) { + ao_radio_pa_on(); + ao_radio_strobe(CC115L_STX); + started = 1; + } + } + if (ao_radio_abort || ao_radio_done) + break; + } + if (ao_radio_abort) + ao_radio_idle(); + ao_radio_wait_done(); + ao_radio_pa_off(); +} + +void +ao_radio_send_aprs(ao_radio_fill_func fill) +{ + ao_radio_get(); + _ao_radio_send_lots(fill, AO_RADIO_MODE_APRS); + ao_radio_put(); +} + +#if CC115L_DEBUG +const static char *cc115l_state_name[] = { + [CC115L_STATUS_STATE_IDLE] = "IDLE", + [CC115L_STATUS_STATE_TX] = "TX", + [CC115L_STATUS_STATE_FSTXON] = "FSTXON", + [CC115L_STATUS_STATE_CALIBRATE] = "CALIBRATE", + [CC115L_STATUS_STATE_SETTLING] = "SETTLING", + [CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW] = "TX_FIFO_UNDERFLOW", +}; + +const static struct ao_cc115l_reg ao_cc115l_reg[] = { + { .addr = CC115L_IOCFG2, .name = "IOCFG2" }, + { .addr = CC115L_IOCFG1, .name = "IOCFG1" }, + { .addr = CC115L_IOCFG0, .name = "IOCFG0" }, + { .addr = CC115L_FIFOTHR, .name = "FIFOTHR" }, + { .addr = CC115L_SYNC1, .name = "SYNC1" }, + { .addr = CC115L_SYNC0, .name = "SYNC0" }, + { .addr = CC115L_PKTLEN, .name = "PKTLEN" }, + { .addr = CC115L_PKTCTRL0, .name = "PKTCTRL0" }, + { .addr = CC115L_CHANNR, .name = "CHANNR" }, + { .addr = CC115L_FSCTRL0, .name = "FSCTRL0" }, + { .addr = CC115L_FREQ2, .name = "FREQ2" }, + { .addr = CC115L_FREQ1, .name = "FREQ1" }, + { .addr = CC115L_FREQ0, .name = "FREQ0" }, + { .addr = CC115L_MDMCFG4, .name = "MDMCFG4" }, + { .addr = CC115L_MDMCFG3, .name = "MDMCFG3" }, + { .addr = CC115L_MDMCFG2, .name = "MDMCFG2" }, + { .addr = CC115L_MDMCFG1, .name = "MDMCFG1" }, + { .addr = CC115L_MDMCFG0, .name = "MDMCFG0" }, + { .addr = CC115L_DEVIATN, .name = "DEVIATN" }, + { .addr = CC115L_MCSM1, .name = "MCSM1" }, + { .addr = CC115L_MCSM0, .name = "MCSM0" }, + { .addr = CC115L_RESERVED_0X20, .name = "RESERVED_0X20" }, + { .addr = CC115L_FREND0, .name = "FREND0" }, + { .addr = CC115L_FSCAL3, .name = "FSCAL3" }, + { .addr = CC115L_FSCAL2, .name = "FSCAL2" }, + { .addr = CC115L_FSCAL1, .name = "FSCAL1" }, + { .addr = CC115L_FSCAL0, .name = "FSCAL0" }, + { .addr = CC115L_RESERVED_0X29, .name = "RESERVED_0X29" }, + { .addr = CC115L_RESERVED_0X2A, .name = "RESERVED_0X2A" }, + { .addr = CC115L_RESERVED_0X2B, .name = "RESERVED_0X2B" }, + { .addr = CC115L_TEST2, .name = "TEST2" }, + { .addr = CC115L_TEST1, .name = "TEST1" }, + { .addr = CC115L_TEST0, .name = "TEST0" }, + { .addr = CC115L_PARTNUM, .name = "PARTNUM" }, + { .addr = CC115L_VERSION, .name = "VERSION" }, + { .addr = CC115L_MARCSTATE, .name = "MARCSTATE" }, + { .addr = CC115L_PKTSTATUS, .name = "PKTSTATUS" }, + { .addr = CC115L_TXBYTES, .name = "TXBYTES" }, + { .addr = CC115L_PA, .name = "PA" }, +}; + +#define AO_NUM_CC115L_REG (sizeof ao_cc115l_reg / sizeof ao_cc115l_reg[0]) + +static void ao_radio_show(void) { + uint8_t status = ao_radio_status(); + int i; + + ao_radio_get(); + status = ao_radio_status(); + printf ("Status: %02x\n", status); + printf ("CHIP_RDY: %d\n", (status >> CC115L_STATUS_CHIP_RDY) & 1); + printf ("STATE: %s\n", cc115l_state_name[(status >> CC115L_STATUS_STATE) & CC115L_STATUS_STATE_MASK]); + printf ("MARC: %02x\n", ao_radio_get_marcstate()); + + for (i = 0; i < AO_NUM_CC115L_REG; i++) + printf ("\t%02x %-20.20s\n", ao_radio_reg_read(ao_cc115l_reg[i].addr), ao_cc115l_reg[i].name); + ao_radio_put(); +} + +static void ao_radio_beep(void) { + ao_radio_rdf(); +} + +static void ao_radio_packet(void) { + static const uint8_t packet[] = { +#if 1 + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +#else + 3, 1, 2, 3 +#endif + }; + + ao_radio_send(packet, sizeof (packet)); +} + +#endif /* CC115L_DEBUG */ + +#if HAS_APRS +#include <ao_aprs.h> + +static void +ao_radio_aprs() +{ +#if PACKET_HAS_SLAVE + ao_packet_slave_stop(); +#endif + ao_aprs_send(); +} +#endif + +static const struct ao_cmds ao_radio_cmds[] = { + { ao_radio_test_cmd, "C <1 start, 0 stop, none both>\0Radio carrier test" }, +#if CC115L_DEBUG +#if HAS_APRS + { ao_radio_aprs, "G\0Send APRS packet" }, +#endif + { ao_radio_show, "R\0Show CC115L status" }, + { ao_radio_beep, "b\0Emit an RDF beacon" }, + { ao_radio_packet, "p\0Send a test packet" }, +#endif + { 0, NULL } +}; + +void +ao_radio_init(void) +{ + int i; + + ao_radio_configured = 0; + ao_spi_init_cs (AO_CC115L_SPI_CS_PORT, (1 << AO_CC115L_SPI_CS_PIN)); + +#if 0 + AO_CC115L_SPI_CS_PORT->bsrr = ((uint32_t) (1 << AO_CC115L_SPI_CS_PIN)); + for (i = 0; i < 10000; i++) { + if ((SPI_2_PORT->idr & (1 << SPI_2_MISO_PIN)) == 0) + break; + } + AO_CC115L_SPI_CS_PORT->bsrr = (1 << AO_CC115L_SPI_CS_PIN); + if (i == 10000) + ao_panic(AO_PANIC_SELF_TEST_CC115L); +#endif + + /* Enable the fifo threhold interrupt pin */ + ao_enable_port(AO_CC115L_FIFO_INT_PORT); + ao_exti_setup(AO_CC115L_FIFO_INT_PORT, AO_CC115L_FIFO_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_HIGH, + ao_radio_fifo_isr); + + /* Enable the tx done interrupt pin */ + ao_enable_port(AO_CC115L_DONE_INT_PORT); + ao_exti_setup(AO_CC115L_DONE_INT_PORT, AO_CC115L_DONE_INT_PIN, + AO_EXTI_MODE_FALLING|AO_EXTI_PRIORITY_MED, + ao_radio_done_isr); + + ao_radio_pa_init(); + + ao_cmd_register(&ao_radio_cmds[0]); +} diff --git a/src/drivers/ao_cc115l.h b/src/drivers/ao_cc115l.h new file mode 100644 index 00000000..811c14aa --- /dev/null +++ b/src/drivers/ao_cc115l.h @@ -0,0 +1,226 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_CC115L_H_ +#define _AO_CC115L_H_ + +#define CC115L_BURST 6 +#define CC115L_READ 7 + +/* Register space */ +#define CC115L_IOCFG2 0x00 /* GDO2 Output Pin Configuration */ +#define CC115L_IOCFG1 0x01 /* GDO1 Output Pin Configuration */ +#define CC115L_IOCFG0 0x02 /* GDO0 Output Pin Configuration */ + +#define CC115L_IOCFG_GPIO1_DS 7 +#define CC115L_IOCFG_GPIO_INV 6 + +#define CC115L_IOCFG_GPIO_CFG 0 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR 2 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_THR_PKT 3 +#define CC115L_IOCFG_GPIO_CFG_TXFIFO_UNDERFLOW 5 +#define CC115L_IOCFG_GPIO_CFG_PKT_SYNC_TX 6 +#define CC115L_IOCFG_GPIO_CFG_PLL_LOCKED 10 +#define CC115L_IOCFG_GPIO_CFG_SERIAL_CLK 11 +#define CC115L_IOCFG_GPIO_CFG_SYNC_DATA 12 +#define CC115L_IOCFG_GPIO_CFG_ASYNC_DATA 13 +#define CC115L_IOCFG_GPIO_CFG_PA_PD 27 +#define CC115L_IOCFG_GPIO_CFG_CHIP_RDYn 41 +#define CC115L_IOCFG_GPIO_CFG_XOSC_STABLE 43 +#define CC115L_IOCFG_GPIO_CFG_HIGHZ 46 +#define CC115L_IOCFG_GPIO_CFG_HW_0 47 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1 48 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_1_5 49 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_2 50 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_3 51 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_4 52 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_6 53 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_8 54 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_12 55 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_16 56 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_24 57 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_32 58 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_48 59 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_64 60 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_96 61 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_128 62 +#define CC115L_IOCFG_GPIO_CFG_CLK_XOSC_192 63 +#define CC115L_IOCFG_GPIO_CFG_MASK 0x3f + +#define CC115L_FIFOTHR 0x03 /* TX FIFO Thresholds */ +#define CC115L_FIFOTHR_THR_MASK 0x0f +#define CC115L_FIFOTHR_THR_61 0 +#define CC115L_FIFOTHR_THR_57 1 +#define CC115L_FIFOTHR_THR_53 2 +#define CC115L_FIFOTHR_THR_49 3 +#define CC115L_FIFOTHR_THR_45 4 +#define CC115L_FIFOTHR_THR_41 5 +#define CC115L_FIFOTHR_THR_37 6 +#define CC115L_FIFOTHR_THR_33 7 +#define CC115L_FIFOTHR_THR_29 8 +#define CC115L_FIFOTHR_THR_25 9 +#define CC115L_FIFOTHR_THR_21 10 +#define CC115L_FIFOTHR_THR_17 11 +#define CC115L_FIFOTHR_THR_13 12 +#define CC115L_FIFOTHR_THR_9 13 +#define CC115L_FIFOTHR_THR_5 14 +#define CC115L_FIFOTHR_THR_1 15 + +#define CC115L_SYNC1 0x04 /* Sync Word, High Byte */ +#define CC115L_SYNC0 0x05 /* Sync Word, Low Byte */ +#define CC115L_PKTLEN 0x06 /* Packet Length */ +#define CC115L_PKTCTRL0 0x08 /* Packet Automation Control */ +#define CC115L_PKTCTRL0_PKT_FORMAT 4 +#define CC115L_PKTCTRL0_PKT_FORMAT_NORMAL 0 +#define CC115L_PKTCTRL0_PKT_FORMAT_SYNC_SERIAL 1 +#define CC115L_PKTCTRL0_PKT_FORMAT_RANDOM 2 +#define CC115L_PKTCTRL0_PKT_FORMAT_ASYNC_SERIAL 3 +#define CC115L_PKTCTRL0_PKT_FORMAT_MASK 3 +#define CC115L_PKTCTRL0_PKT_CRC_EN 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_FIXED 0 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_VARIABLE 1 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_INFINITE 2 +#define CC115L_PKTCTRL0_PKT_LENGTH_CONFIG_MASK 3 +#define CC115L_CHANNR 0x0a /* Channel Number */ +#define CC115L_FSCTRL0 0x0c /* Frequency Synthesizer Control */ +#define CC115L_FREQ2 0x0d /* Frequency Control Word, High Byte */ +#define CC115L_FREQ1 0x0e /* Frequency Control Word, Middle Byte */ +#define CC115L_FREQ0 0x0f /* Frequency Control Word, Low Byte */ +#define CC115L_MDMCFG4 0x10 /* Modem Configuration */ +#define CC115L_MDMCFG4_DRATE_E 0 +#define CC115L_MDMCFG3 0x11 /* Modem Configuration */ +#define CC115L_MDMCFG2 0x12 /* Modem Configuration */ +#define CC115L_MDMCFG2_MOD_FORMAT 4 +#define CC115L_MDMCFG2_MOD_FORMAT_2FSK 0 +#define CC115L_MDMCFG2_MOD_FORMAT_GFSK 1 +#define CC115L_MDMCFG2_MOD_FORMAT_OOK 3 +#define CC115L_MDMCFG2_MOD_FORMAT_4FSK 4 +#define CC115L_MDMCFG2_MOD_FORMAT_MASK 7 +#define CC115L_MDMCFG2_MANCHESTER_EN 3 +#define CC115L_MDMCFG2_SYNC_MODE 0 +#define CC115L_MDMCFG2_SYNC_MODE_NONE 0 +#define CC115L_MDMCFG2_SYNC_MODE_16BITS 1 +#define CC115L_MDMCFG2_SYNC_MODE_32BITS 3 +#define CC115L_MDMCFG2_SYNC_MODE_MASK 3 +#define CC115L_MDMCFG1 0x13 /* Modem Configuration */ +#define CC115L_MDMCFG1_NUM_PREAMBLE 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_2 0 +#define CC115L_MDMCFG1_NUM_PREAMBLE_3 1 +#define CC115L_MDMCFG1_NUM_PREAMBLE_4 2 +#define CC115L_MDMCFG1_NUM_PREAMBLE_6 3 +#define CC115L_MDMCFG1_NUM_PREAMBLE_8 4 +#define CC115L_MDMCFG1_NUM_PREAMBLE_12 5 +#define CC115L_MDMCFG1_NUM_PREAMBLE_16 6 +#define CC115L_MDMCFG1_NUM_PREAMBLE_24 7 +#define CC115L_MDMCFG1_NUM_PREAMBLE_MASK 7 +#define CC115L_MDMCFG1_CHANSPC_E 0 +#define CC115L_MDMCFG0 0x14 /* Modem Configuration */ +#define CC115L_DEVIATN 0x15 /* Modem Deviation Setting */ +#define CC115L_DEVIATN_DEVIATION_E 4 +#define CC115L_DEVIATN_DEVIATION_E_MASK 7 +#define CC115L_DEVIATN_DEVIATION_M 0 +#define CC115L_DEVIATN_DEVIATION_M_MASK 7 +#define CC115L_MCSM1 0x17 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM1_TXOFF_MODE 0 +#define CC115L_MCSM1_TXOFF_MODE_IDLE 0 +#define CC115L_MCSM1_TXOFF_MODE_FSTXON 1 +#define CC115L_MCSM1_TXOFF_MODE_TX 2 +#define CC115L_MCSM1_TXOFF_MODE_MASK 3 +#define CC115L_MCSM0 0x18 /* Main Radio Control State Machine Configuration */ +#define CC115L_MCSM0_FS_AUTOCAL 4 +#define CC115L_MCSM0_FS_AUTOCAL_NEVER 0 +#define CC115L_MCSM0_FS_AUTOCAL_IDLE_TO_TX 1 +#define CC115L_MCSM0_FS_AUTOCAL_TX_TO_IDLE 2 +#define CC115L_MCSM0_FS_AUTOCAL_4TH_TX_TO_IDLE 3 +#define CC115L_MCSM0_FS_AUTOCAL_MASK 3 +#define CC115L_MCSM0_PO_TIMEOUT 2 +#define CC115L_MCSM0_PO_TIMEOUT_1 0 +#define CC115L_MCSM0_PO_TIMEOUT_16 1 +#define CC115L_MCSM0_PO_TIMEOUT_64 2 +#define CC115L_MCSM0_PO_TIMEOUT_256 3 +#define CC115L_MCSM0_PO_TIMEOUT_MASK 3 +#define CC115L_MCSM0_XOSC_FORCE_ON 0 +#define CC115L_RESERVED_0X20 0x20 /* Use setting from SmartRF Studio */ +#define CC115L_FREND0 0x22 /* Front End TX Configuration */ +#define CC115L_FSCAL3 0x23 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL2 0x24 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL1 0x25 /* Frequency Synthesizer Calibration */ +#define CC115L_FSCAL0 0x26 /* Frequency Synthesizer Calibration */ +#define CC115L_RESERVED_0X29 0x29 /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2A 0x2a /* Use setting from SmartRF Studio */ +#define CC115L_RESERVED_0X2B 0x2b /* Use setting from SmartRF Studio */ +#define CC115L_TEST2 0x2c /* Various Test Settings */ +#define CC115L_TEST1 0x2d /* Various Test Settings */ +#define CC115L_TEST0 0x2e /* Various Test Settings */ + +/* Status registers (use BURST bit to select these) */ +#define CC115L_PARTNUM (0x30|(1<<CC115L_BURST)) /* Part number for CC115L */ +#define CC115L_VERSION (0x31|(1<<CC115L_BURST)) /* Current version number */ +#define CC115L_MARCSTATE (0x35|(1<<CC115L_BURST)) /* Control state machine state */ +#define CC115L_MARCSTATE_MASK 0x1f +#define CC115L_MARCSTATE_SLEEP 0x00 +#define CC115L_MARCSTATE_IDLE 0x01 +#define CC115L_MARCSTATE_XOFF 0x02 +#define CC115L_MARCSTATE_VCOON_MC 0x03 +#define CC115L_MARCSTATE_REGON_MC 0x04 +#define CC115L_MARCSTATE_MANCAL 0x05 +#define CC115L_MARCSTATE_VCOON 0x06 +#define CC115L_MARCSTATE_REGON 0x07 +#define CC115L_MARCSTATE_STARTCAL 0x08 +#define CC115L_MARCSTATE_BWBOOST 0x09 +#define CC115L_MARCSTATE_FS_LOCK 0x0a +#define CC115L_MARCSTATE_ENDCAL 0x0c +#define CC115L_MARCSTATE_FSTXON 0x12 +#define CC115L_MARCSTATE_TX 0x13 +#define CC115L_MARCSTATE_TX_END 0x14 +#define CC115L_MARCSTATE_TX_UNDERFLOW 0x16 +#define CC115L_PKTSTATUS (0x38|(1<<CC115L_BURST)) /* Current GDOx status and packet status */ +#define CC115L_TXBYTES (0x3a|(1<<CC115L_BURST)) /* Underflow and number of bytes in the TX FIFO */ +#define CC115L_TXBYTES_TXFIFO_UNDERFLOW 7 +#define CC115L_TXBYTES_NUM_TX_BYTES 0 +#define CC115L_TXBYTES_NUM_TX_BYTES_MASK 0x7f + +/* Command strobes (no BURST bit for these) */ +#define CC115L_SRES 0x30 +#define CC115L_SFSTXON 0x31 +#define CC115L_SXOFF 0x32 +#define CC115L_SCAL 0x33 +#define CC115L_STX 0x35 +#define CC115L_SIDLE 0x36 +#define CC115L_SPWD 0x39 +#define CC115L_SFTX 0x3b +#define CC115L_SNOP 0x3d + +#define CC115L_PA 0x3e +#define CC115L_FIFO 0x3f + +#define CC115L_FIFO_SIZE 64 + +/* Status byte */ +#define CC115L_STATUS_CHIP_RDY 7 +#define CC115L_STATUS_STATE 4 +#define CC115L_STATUS_STATE_IDLE 0 +#define CC115L_STATUS_STATE_TX 2 +#define CC115L_STATUS_STATE_FSTXON 3 +#define CC115L_STATUS_STATE_CALIBRATE 4 +#define CC115L_STATUS_STATE_SETTLING 5 +#define CC115L_STATUS_STATE_TX_FIFO_UNDERFLOW 7 +#define CC115L_STATUS_STATE_MASK 7 + +#define CC115L_STATUS_FIFO_BYTES_AVAILABLE 0 +#endif /* _AO_CC115L_H_ */ diff --git a/src/drivers/ao_companion.c b/src/drivers/ao_companion.c index 0ebe8429..0f405253 100644 --- a/src/drivers/ao_companion.c +++ b/src/drivers/ao_companion.c @@ -18,7 +18,7 @@ #include <ao.h> #include <ao_companion.h> -#ifdef MEGAMETRUM +#ifdef TELEMEGA #define ao_spi_slow(b) #define ao_spi_fast(b) #endif diff --git a/src/drivers/ao_fat.c b/src/drivers/ao_fat.c new file mode 100644 index 00000000..afd645cd --- /dev/null +++ b/src/drivers/ao_fat.c @@ -0,0 +1,1654 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef AO_FAT_TEST +#include "ao.h" +#endif + +#include "ao_fat.h" +#include "ao_bufio.h" + +/* Include FAT commands */ +#ifndef AO_FAT_TEST +#define FAT_COMMANDS 1 +#endif + +/* Spew FAT tracing */ +#define FAT_TRACE 0 + +#ifdef DBG +#undef DBG +#endif + +#if FAT_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +/* + * Basic file system types + */ + +typedef ao_fat_offset_t offset_t; +typedef ao_fat_sector_t sector_t; +typedef ao_fat_cluster_t cluster_t; +typedef ao_fat_dirent_t dirent_t; +typedef ao_fat_cluster_offset_t cluster_offset_t; + +/* Global FAT lock */ +static uint8_t ao_fat_mutex; + +/* Partition information, sector numbers */ + +static uint8_t partition_type; +static sector_t partition_start, partition_end; + +#define AO_FAT_BAD_CLUSTER 0xffffff7 +#define AO_FAT_LAST_CLUSTER 0xfffffff +#define AO_FAT_IS_LAST_CLUSTER(c) (((c) & 0xffffff8) == 0xffffff8) +#define AO_FAT_IS_LAST_CLUSTER16(c) (((c) & 0xfff8) == 0xfff8) + + +#define SECTOR_SIZE 512 +#define SECTOR_MASK (SECTOR_SIZE - 1) +#define SECTOR_SHIFT 9 + +#define DIRENT_SIZE 32 + +/* File system parameters */ +static uint8_t sectors_per_cluster; +static uint32_t bytes_per_cluster; +static sector_t reserved_sector_count; +static uint8_t number_fat; +static dirent_t root_entries; +static sector_t sectors_per_fat; +static cluster_t number_cluster; +static sector_t fat_start; +static sector_t root_start; +static sector_t data_start; +static cluster_t next_free; +static uint8_t filesystem_full; + +/* FAT32 extra data */ +static uint8_t fat32; +static uint8_t fsinfo_dirty; +static cluster_t root_cluster; +static sector_t fsinfo_sector; +static cluster_t free_count; + +/* + * Deal with LSB FAT data structures + */ +static uint32_t +get_u32(uint8_t *base) +{ + return ((uint32_t) base[0] | + ((uint32_t) base[1] << 8) | + ((uint32_t) base[2] << 16) | + ((uint32_t) base[3] << 24)); +} + +static void +put_u32(uint8_t *base, uint32_t value) +{ + base[0] = value; + base[1] = value >> 8; + base[2] = value >> 16; + base[3] = value >> 24; +} + +static uint16_t +get_u16(uint8_t *base) +{ + return ((uint16_t) base[0] | ((uint16_t) base[1] << 8)); +} + +static void +put_u16(uint8_t *base, uint16_t value) +{ + base[0] = value; + base[1] = value >> 8; +} + +static uint8_t +_ao_fat_cluster_valid(cluster_t cluster) +{ + return (2 <= cluster && cluster < number_cluster); +} + +/* Start using a sector */ +static uint8_t * +_ao_fat_sector_get(sector_t sector) +{ + sector += partition_start; + if (sector >= partition_end) + return NULL; + return ao_bufio_get(sector); +} + +/* Finish using a sector, 'w' is 1 if modified */ +#define _ao_fat_sector_put(b,w) ao_bufio_put(b,w) + +/* Get the next cluster entry in the chain */ +static cluster_t +_ao_fat_entry_read(cluster_t cluster) +{ + sector_t sector; + cluster_t offset; + uint8_t *buf; + cluster_t ret; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> (SECTOR_SHIFT); + offset = cluster & SECTOR_MASK; + buf = _ao_fat_sector_get(fat_start + sector); + if (!buf) + return 0; + + if (fat32) { + ret = get_u32(buf + offset); + ret &= 0xfffffff; + } else { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + _ao_fat_sector_put(buf, 0); + return ret; +} + +/* Replace the referenced cluster entry in the chain with + * 'new_value'. Return the previous value. + */ +static cluster_t +_ao_fat_entry_replace(cluster_t cluster, cluster_t new_value) +{ + sector_t sector; + cluster_offset_t offset; + uint8_t *buf; + cluster_t ret; + cluster_t old_value; + uint8_t fat; + + if (!_ao_fat_cluster_valid(cluster)) + return 0xfffffff7; + + /* Convert from cluster index to byte index */ + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> SECTOR_SHIFT; + offset = cluster & SECTOR_MASK; + + new_value &= 0xfffffff; + for (fat = 0; fat < number_fat; fat++) { + buf = _ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector); + if (!buf) + return 0; + if (fat32) { + old_value = get_u32(buf + offset); + put_u32(buf + offset, new_value | (old_value & 0xf0000000)); + if (fat == 0) { + ret = old_value & 0xfffffff; + + /* Track the free count if it wasn't marked + * invalid when we mounted the file system + */ + if (free_count != 0xffffffff) { + if (new_value && !ret) { + --free_count; + fsinfo_dirty = 1; + } else if (!new_value && ret) { + ++free_count; + fsinfo_dirty = 1; + } + } + } + } else { + if (fat == 0) { + ret = get_u16(buf + offset); + if (AO_FAT_IS_LAST_CLUSTER16(ret)) + ret |= 0xfff0000; + } + put_u16(buf + offset, new_value); + } + _ao_fat_sector_put(buf, 1); + } + return ret; + +} + +/* + * Walk a cluster chain and mark + * all of them as free + */ +static void +_ao_fat_free_cluster_chain(cluster_t cluster) +{ + while (_ao_fat_cluster_valid(cluster)) { + if (cluster < next_free) { + next_free = cluster; + fsinfo_dirty = 1; + } + cluster = _ao_fat_entry_replace(cluster, 0x00000000); + } +} + +/* + * _ao_fat_cluster_seek + * + * The basic file system operation -- map a file cluster index to a + * partition cluster number. Done by computing the cluster number and + * then walking that many clusters from the first cluster. Returns + * 0xffff if we walk off the end of the file or the cluster chain + * is damaged somehow + */ +static cluster_t +_ao_fat_cluster_seek(cluster_t cluster, cluster_t distance) +{ + while (distance) { + cluster = _ao_fat_entry_read(cluster); + if (!_ao_fat_cluster_valid(cluster)) + break; + distance--; + } + return cluster; +} + +/* + * _ao_fat_cluster_set_size + * + * Set the number of clusters in the specified chain, + * freeing extra ones or alocating new ones as needed + * + * Returns AO_FAT_BAD_CLUSTER on allocation failure + */ + +static cluster_t +_ao_fat_cluster_set_size(cluster_t first_cluster, cluster_t size) +{ + cluster_t have; + cluster_t last_cluster; + cluster_t next_cluster; + + /* Walk the cluster chain to the + * spot where it needs to change. That + * will either be the end of the chain (in case it needs to grow), + * or after the desired number of clusters, in which case it needs to shrink + */ + next_cluster = first_cluster; + last_cluster = 0; + DBG("\tclusters:"); + for (have = 0; have < size; have++) { + DBG(" %08x", next_cluster); + if (!_ao_fat_cluster_valid(next_cluster)) + break; + last_cluster = next_cluster; + next_cluster = _ao_fat_entry_read(next_cluster); + } + DBG("\n"); + + /* At this point, last_cluster points to the last valid + * cluster in the file, if any. That's the spot in the FAT + * that needs to be rewritten, either to truncate the file by + * writing an END marker, or to extend the file by writing + * more clusters. next_cluster will contain the value of the + * FAT at last_cluster. + * + * If this is at the head of the cluster chain, then + * last_cluster will be zero and next_cluster will + * be the first cluster in the chain. + */ + if (have == size) { + /* The file is large enough, truncate as needed */ + if (_ao_fat_cluster_valid(next_cluster)) { + DBG("truncate between %08x and %08x\n", last_cluster, next_cluster); + if (last_cluster) + /* + * Otherwise, rewrite the last cluster + * in the chain with a LAST marker + */ + (void) _ao_fat_entry_replace(last_cluster, + AO_FAT_LAST_CLUSTER); + else + /* + * If the file is getting erased, then + * rewrite the directory entry cluster + * value + */ + first_cluster = 0; + + /* Clear the remaining clusters in the chain */ + _ao_fat_free_cluster_chain(next_cluster); + + /* The file system is no longer full (if it was) */ + filesystem_full = 0; + } else { + DBG("unchanged FAT chain\n"); + /* The chain is already the right length, don't mess with it */ + ; + } + } else { + cluster_t need; + cluster_t free; + + if (filesystem_full) + return AO_FAT_BAD_CLUSTER; + + /* Set next_free if it has wrapped or wasn't set before */ + if (next_free < 2 || number_cluster <= next_free) { + next_free = 2; + fsinfo_dirty = 1; + } + + /* See if there are enough free clusters in the file system */ + need = size - have; + +#define loop_cluster for (free = next_free; need > 0;) +#define next_cluster \ + if (++free == number_cluster) \ + free = 2; \ + if (free == next_free) \ + break; \ + + loop_cluster { + if (!_ao_fat_entry_read(free)) + need--; + next_cluster; + } + + /* Still need some, tell the user that we've failed */ + if (need) { + filesystem_full = 1; + return AO_FAT_BAD_CLUSTER; + } + + /* Now go allocate those clusters and + * thread them onto the chain + */ + need = size - have; + loop_cluster { + if (_ao_fat_entry_read(free) == 0) { + next_free = free + 1; + if (next_free >= number_cluster) + next_free = 2; + fsinfo_dirty = 1; + DBG("\tadd cluster. old %08x new %08x\n", last_cluster, free); + if (last_cluster) + _ao_fat_entry_replace(last_cluster, free); + else + first_cluster = free; + last_cluster = free; + need--; + } + next_cluster; + } +#undef loop_cluster +#undef next_cluster + DBG("\tlast cluster %08x\n", last_cluster); + /* Mark the new end of the chain */ + _ao_fat_entry_replace(last_cluster, AO_FAT_LAST_CLUSTER); + } + + DBG("\tfirst cluster %08x\n", first_cluster); + return first_cluster; +} + +/* Start using a root directory entry */ +static uint8_t * +_ao_fat_root_get(dirent_t e) +{ + offset_t byte = e * DIRENT_SIZE; + sector_t sector = byte >> SECTOR_SHIFT; + cluster_offset_t offset = byte & SECTOR_MASK; + uint8_t *buf; + + if (fat32) { + cluster_t cluster_distance = sector / sectors_per_cluster; + sector_t sector_index = sector % sectors_per_cluster; + cluster_t cluster = _ao_fat_cluster_seek(root_cluster, cluster_distance); + + if (_ao_fat_cluster_valid(cluster)) + sector = data_start + (cluster-2) * sectors_per_cluster + sector_index; + else + return NULL; + } else { + if (e >= root_entries) + return NULL; + sector = root_start + sector; + } + + buf = _ao_fat_sector_get(sector); + if (!buf) + return NULL; + return buf + offset; +} + +/* Finish using a root directory entry, 'w' is 1 if modified */ +static void +_ao_fat_root_put(uint8_t *root, dirent_t e, uint8_t write) +{ + cluster_offset_t offset = ((e * DIRENT_SIZE) & SECTOR_MASK); + uint8_t *buf = root - offset; + + _ao_fat_sector_put(buf, write); +} + +/* + * _ao_fat_root_extend + * + * On FAT32, make the root directory at least 'ents' entries long + */ +static int8_t +_ao_fat_root_extend(dirent_t ents) +{ + offset_t byte_size; + cluster_t cluster_size; + if (!fat32) + return 0; + + byte_size = (ents + 1) * 0x20; + cluster_size = (byte_size + bytes_per_cluster - 1) / bytes_per_cluster; + if (_ao_fat_cluster_set_size(root_cluster, cluster_size) != AO_FAT_BAD_CLUSTER) + return 1; + return 0; +} + +/* + * _ao_fat_setup_partition + * + * Load the boot block and find the first partition + */ +static uint8_t +_ao_fat_setup_partition(void) +{ + uint8_t *mbr; + uint8_t *partition; + uint32_t partition_size; + + mbr = ao_bufio_get(0); + if (!mbr) + return AO_FAT_FILESYSTEM_MBR_READ_FAILURE; + + /* Check the signature */ + if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) { + DBG ("Invalid MBR signature %02x %02x\n", + mbr[0x1fe], mbr[0x1ff]); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE; + } + + /* Check to see if it's actually a boot block, in which + * case it's presumably not a paritioned device + */ + + if (mbr[0] == 0xeb) { + partition_start = 0; + partition_size = get_u16(mbr + 0x13); + if (partition_size == 0) + partition_size = get_u32(mbr + 0x20); + } else { + /* Just use the first partition */ + partition = &mbr[0x1be]; + + partition_type = partition[4]; + switch (partition_type) { + case 4: /* FAT16 up to 32M */ + case 6: /* FAT16 over 32M */ + break; + case 0x0b: /* FAT32 up to 2047GB */ + case 0x0c: /* FAT32 LBA */ + break; + default: + DBG ("Invalid partition type %02x\n", partition_type); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE; + } + + partition_start = get_u32(partition+8); + partition_size = get_u32(partition+12); + if (partition_size == 0) { + DBG ("Zero-sized partition\n"); + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION; + } + } + partition_end = partition_start + partition_size; + ao_bufio_put(mbr, 0); + return AO_FAT_FILESYSTEM_SUCCESS; +} + +static uint8_t +_ao_fat_setup_fs(void) +{ + uint8_t *boot = _ao_fat_sector_get(0); + uint32_t data_sectors; + + if (!boot) + return AO_FAT_FILESYSTEM_BOOT_READ_FAILURE; + + /* Check the signature */ + if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) { + DBG ("Invalid BOOT signature %02x %02x\n", + boot[0x1fe], boot[0x1ff]); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE; + } + + /* Check the sector size */ + if (get_u16(boot + 0xb) != SECTOR_SIZE) { + DBG ("Invalid sector size %d\n", + get_u16(boot + 0xb)); + _ao_fat_sector_put(boot, 0); + return AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE; + } + + sectors_per_cluster = boot[0xd]; + bytes_per_cluster = sectors_per_cluster << SECTOR_SHIFT; + reserved_sector_count = get_u16(boot+0xe); + number_fat = boot[0x10]; + root_entries = get_u16(boot + 0x11); + sectors_per_fat = get_u16(boot+0x16); + fat32 = 0; + if (sectors_per_fat == 0) { + fat32 = 1; + sectors_per_fat = get_u32(boot+0x24); + root_cluster = get_u32(boot+0x2c); + fsinfo_sector = get_u16(boot + 0x30); + } + _ao_fat_sector_put(boot, 0); + + free_count = 0xffffffff; + next_free = 0; + if (fat32 && fsinfo_sector) { + uint8_t *fsinfo = _ao_fat_sector_get(fsinfo_sector); + + if (fsinfo) { + free_count = get_u32(fsinfo + 0x1e8); + next_free = get_u32(fsinfo + 0x1ec); + _ao_fat_sector_put(fsinfo, 0); + } + } + + fat_start = reserved_sector_count;; + root_start = fat_start + number_fat * sectors_per_fat; + data_start = root_start + ((root_entries * DIRENT_SIZE + SECTOR_MASK) >> SECTOR_SHIFT); + + data_sectors = (partition_end - partition_start) - data_start; + + number_cluster = data_sectors / sectors_per_cluster; + + return AO_FAT_FILESYSTEM_SUCCESS; +} + +/* + * State for an open file + */ + +struct ao_file { + struct ao_fat_dirent *dirent; + offset_t offset; + offset_t cluster_offset; + cluster_t cluster; + uint8_t busy; +}; + +#define AO_FAT_NFILE 8 + +static struct ao_fat_dirent ao_file_dirent[AO_FAT_NFILE]; + +static struct ao_fat_dirent * +_ao_fat_file_dirent_alloc(struct ao_fat_dirent *want) +{ + int8_t d; + struct ao_fat_dirent *free = NULL, *dirent; + + for (d = 0; d < AO_FAT_NFILE; d++) { + + dirent = &ao_file_dirent[d]; + /* See if there's another user of this file already */ + if (want && dirent->name[0] != 0) { + if (dirent->entry == want->entry) + return dirent; + } else { + if (!free) { + free = dirent; + if (!want) + break; + } + } + } + if (free && want) + *free = *want; + return free; +} + +static struct ao_file ao_file_table[AO_FAT_NFILE]; + +static int8_t +_ao_fat_fd_alloc(struct ao_fat_dirent *dirent) +{ + int8_t fd; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (!ao_file_table[fd].busy) { + ao_file_table[fd].dirent = _ao_fat_file_dirent_alloc(dirent); + ao_file_table[fd].busy = 1; + ao_file_table[fd].offset = 0; + ao_file_table[fd].cluster_offset = 0; + ao_file_table[fd].cluster = ao_file_table[fd].dirent->cluster; + + return fd; + } + return -AO_FAT_EMFILE; +} + +static void +_ao_fat_fd_free(int8_t fd) +{ + struct ao_file *file = &ao_file_table[fd]; + struct ao_fat_dirent *dirent = file->dirent; + memset(&ao_file_table[fd], '\0', sizeof (struct ao_file)); + + /* Check and see if another ao_file references the same dirent */ + for (fd = 0; fd < AO_FAT_NFILE; fd++) + if (ao_file_table[fd].dirent == dirent) + return; + memset(dirent, '\0', sizeof (struct ao_fat_dirent)); +} + +static struct ao_file * +_ao_fat_fd_to_file(int8_t fd) +{ + struct ao_file *file; + if (fd < 0 || AO_FAT_NFILE <= fd) + return NULL; + + file = &ao_file_table[fd]; + if (!file->busy) + return NULL; + return file; +} + +static uint8_t ao_filesystem_setup; +static uint8_t ao_filesystem_status; + +static uint8_t +_ao_fat_setup(void) +{ + if (!ao_filesystem_setup) { + + ao_filesystem_setup = 1; + ao_bufio_setup(); + + /* Re-initialize all global state; this will help to allow the + * file system to get swapped someday + */ + partition_type = partition_start = partition_end = 0; + sectors_per_cluster = bytes_per_cluster = reserved_sector_count = 0; + number_fat = root_entries = sectors_per_fat = 0; + number_cluster = fat_start = root_start = data_start = 0; + next_free = filesystem_full = 0; + fat32 = fsinfo_dirty = root_cluster = fsinfo_sector = free_count = 0; + + /* Reset open file table */ + memset(&ao_file_table, '\0', sizeof (ao_file_table)); + + ao_filesystem_status = _ao_fat_setup_partition(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + ao_filesystem_status = _ao_fat_setup_fs(); + if (ao_filesystem_status != AO_FAT_FILESYSTEM_SUCCESS) + return ao_filesystem_status; + } + return ao_filesystem_status; +} + +void +ao_fat_unmount(void) +{ + ao_filesystem_setup = 0; +} + +/* + * Basic file operations + */ + +static uint32_t +_ao_fat_current_sector(struct ao_file *file) +{ + cluster_t cluster_offset; + uint32_t sector_offset; + uint16_t sector_index; + cluster_t cluster; + + DBG("current sector offset %d size %d\n", + file->offset, file->dirent->size); + + if (file->offset > file->dirent->size) { + printf ("file offset %d larger than size %d\n", + file->offset, file->dirent->size); + return 0xffffffff; + } + + sector_offset = file->offset >> SECTOR_SHIFT; + + if (!file->cluster || file->offset < file->cluster_offset) { + file->cluster = file->dirent->cluster; + file->cluster_offset = 0; + DBG("\treset to start of file %08x\n", file->cluster); + } + + if (file->cluster_offset + bytes_per_cluster <= file->offset) { + cluster_t cluster_distance; + + cluster_offset = sector_offset / sectors_per_cluster; + + cluster_distance = cluster_offset - file->cluster_offset / bytes_per_cluster; + + DBG("\tseek forward %d clusters\n", cluster_distance); + cluster = _ao_fat_cluster_seek(file->cluster, cluster_distance); + + if (!_ao_fat_cluster_valid(cluster)) { + printf ("invalid cluster %08x\n", cluster); + return 0xffffffff; + } + file->cluster = cluster; + file->cluster_offset = cluster_offset * bytes_per_cluster; + } + + sector_index = sector_offset % sectors_per_cluster; + DBG("current cluster %08x sector_index %d sector %d\n", + file->cluster, sector_index, + data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index); + return data_start + (uint32_t) (file->cluster-2) * sectors_per_cluster + sector_index; +} + +/* + * _ao_fat_invaldate_cluster_offset + * + * When the file size gets shrunk, invalidate + * any file structures referencing clusters beyond that point + */ + +static void +_ao_fat_invalidate_cluster_offset(struct ao_fat_dirent *dirent) +{ + int8_t fd; + struct ao_file *file; + + for (fd = 0; fd < AO_FAT_NFILE; fd++) { + file = &ao_file_table[fd]; + if (!file->busy) + continue; + if (file->dirent == dirent) { + if (file->cluster_offset >= dirent->size) { + file->cluster_offset = 0; + file->cluster = dirent->cluster; + } + } + } +} + + +/* + * _ao_fat_set_size + * + * Set the size of the current file, truncating or extending + * the cluster chain as needed + */ +static int8_t +_ao_fat_set_size(struct ao_file *file, uint32_t size) +{ + uint8_t *dent; + cluster_t first_cluster; + cluster_t have_clusters, need_clusters; + + DBG ("Set size %d\n", size); + if (size == file->dirent->size) { + DBG("\tsize match\n"); + return AO_FAT_SUCCESS; + } + + first_cluster = file->dirent->cluster; + have_clusters = (file->dirent->size + bytes_per_cluster - 1) / bytes_per_cluster; + need_clusters = (size + bytes_per_cluster - 1) / bytes_per_cluster; + + DBG ("\tfirst cluster %08x have %d need %d\n", first_cluster, have_clusters, need_clusters); + if (have_clusters != need_clusters) { + if (file->cluster && size > file->cluster_offset) { + cluster_t offset_clusters = (file->cluster_offset + bytes_per_cluster) / bytes_per_cluster; + cluster_t extra_clusters = need_clusters - offset_clusters; + cluster_t next_cluster; + + DBG ("\tset size relative offset_clusters %d extra_clusters %d\n", + offset_clusters, extra_clusters); + + /* Need one more to account for file->cluster, which we already have */ + next_cluster = _ao_fat_cluster_set_size(file->cluster, extra_clusters + 1); + if (next_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } else { + DBG ("\tset size absolute need_clusters %d\n", need_clusters); + first_cluster = _ao_fat_cluster_set_size(first_cluster, need_clusters); + + if (first_cluster == AO_FAT_BAD_CLUSTER) + return -AO_FAT_ENOSPC; + } + } + + DBG ("\tupdate directory size\n"); + /* Update the directory entry */ + dent = _ao_fat_root_get(file->dirent->entry); + if (!dent) { + printf ("dent update failed\n"); + return -AO_FAT_EIO; + } + put_u32(dent + 0x1c, size); + put_u16(dent + 0x1a, first_cluster); + if (fat32) + put_u16(dent + 0x14, first_cluster >> 16); + _ao_fat_root_put(dent, file->dirent->entry, 1); + + file->dirent->size = size; + file->dirent->cluster = first_cluster; + if (have_clusters > need_clusters) + _ao_fat_invalidate_cluster_offset(file->dirent); + DBG ("set size done\n"); + return AO_FAT_SUCCESS; +} + +/* + * _ao_fat_root_init + * + * Initialize a root directory entry + */ +static void +_ao_fat_root_init(uint8_t *dent, char name[11], uint8_t attr) +{ + memset(dent, '\0', 0x20); + memmove(dent, name, 11); + + dent[0x0b] = 0x00; + dent[0x0c] = 0x00; + dent[0x0d] = 0x00; + + /* XXX fix time */ + put_u16(dent + 0x0e, 0); + /* XXX fix date */ + put_u16(dent + 0x10, 0); + /* XXX fix date */ + put_u16(dent + 0x12, 0); + + /* XXX fix time */ + put_u16(dent + 0x16, 0); + /* XXX fix date */ + put_u16(dent + 0x18, 0); + + /* cluster number */ + /* Low cluster bytes */ + put_u16(dent + 0x1a, 0); + /* FAT32 high cluster bytes */ + put_u16(dent + 0x14, 0); + + /* size */ + put_u32(dent + 0x1c, 0); +} + + +static void +_ao_fat_dirent_init(struct ao_fat_dirent *dirent, uint8_t *dent, uint16_t entry) +{ + memcpy(dirent->name, dent + 0x00, 11); + dirent->attr = dent[0x0b]; + dirent->size = get_u32(dent+0x1c); + dirent->cluster = get_u16(dent+0x1a); + if (fat32) + dirent->cluster |= (cluster_t) get_u16(dent + 0x14) << 16; + dirent->entry = entry; +} + +/* + * _ao_fat_flush_fsinfo + * + * Write out any fsinfo changes to disk + */ + +static void +_ao_fat_flush_fsinfo(void) +{ + uint8_t *fsinfo; + + if (!fat32) + return; + + if (!fsinfo_dirty) + return; + fsinfo_dirty = 0; + if (!fsinfo_sector) + return; + + fsinfo = _ao_fat_sector_get(fsinfo_sector); + if (fsinfo) { + put_u32(fsinfo + 0x1e8, free_count); + put_u32(fsinfo + 0x1ec, next_free); + _ao_fat_sector_put(fsinfo, 1); + } +} + +/* + * Public API + */ + +/* + * ao_fat_sync + * + * Flush any pending I/O to storage + */ + +static void +_ao_fat_sync(void) +{ + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return; + _ao_fat_flush_fsinfo(); + ao_bufio_flush(); +} + +void +ao_fat_sync(void) +{ + ao_mutex_get(&ao_fat_mutex); + _ao_fat_sync(); + ao_mutex_put(&ao_fat_mutex); +} + +/* + * ao_fat_full + * + * Returns TRUE if the filesystem cannot take + * more data + */ + +int8_t +ao_fat_full(void) +{ + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ao_mutex_put(&ao_fat_mutex); + return 1; + } + ao_mutex_put(&ao_fat_mutex); + return filesystem_full; +} + +static int8_t +_ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + uint8_t *dent; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + dent = _ao_fat_root_get(*entry); + if (!dent) + return -AO_FAT_EDIREOF; + + if (dent[0] == AO_FAT_DENT_END) { + _ao_fat_root_put(dent, *entry, 0); + return -AO_FAT_EDIREOF; + } + if (dent[0] != AO_FAT_DENT_EMPTY && (dent[0xb] & 0xf) != 0xf) { + _ao_fat_dirent_init(dirent, dent, *entry); + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + return AO_FAT_SUCCESS; + } + _ao_fat_root_put(dent, *entry, 0); + (*entry)++; + } +} + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_readdir(entry, dirent); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_open + * + * Open an existing file. + */ +static int8_t +_ao_fat_open(char name[11], uint8_t mode) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int8_t status; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + for (;;) { + status = _ao_fat_readdir(&entry, &dirent); + if (status < 0) { + if (status == -AO_FAT_EDIREOF) + return -AO_FAT_ENOENT; + return status; + } + if (!memcmp(name, dirent.name, 11)) { + if (AO_FAT_IS_DIR(dirent.attr)) + return -AO_FAT_EISDIR; + if (!AO_FAT_IS_FILE(dirent.attr)) + return -AO_FAT_EPERM; + if (mode > AO_FAT_OPEN_READ && (dirent.attr & AO_FAT_FILE_READ_ONLY)) + return -AO_FAT_EACCESS; + return _ao_fat_fd_alloc(&dirent); + } + } + return -AO_FAT_ENOENT; +} + +int8_t +ao_fat_open(char name[11], uint8_t mode) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_open(name, mode); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_close + * + * Close the currently open file + */ +static int8_t +_ao_fat_close(int8_t fd) +{ + struct ao_file *file; + + file = _ao_fat_fd_to_file(fd); + if (!file) + return -AO_FAT_EBADF; + + _ao_fat_fd_free(fd); + _ao_fat_sync(); + return AO_FAT_SUCCESS; +} + +int8_t +ao_fat_close(int8_t fd) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_close(fd); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_creat + * + * Open and truncate an existing file or + * create a new file + */ + +static int8_t +_ao_fat_creat(char name[11]) +{ + uint16_t entry; + int8_t fd; + int8_t status; + uint8_t *dent; + struct ao_file *file; + + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) + return -AO_FAT_EIO; + + fd = _ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (fd >= 0) { + file = &ao_file_table[fd]; + status = _ao_fat_set_size(file, 0); + if (status < 0) { + _ao_fat_close(fd); + fd = status; + } + } else { + if (fd == -AO_FAT_ENOENT) { + entry = 0; + for (;;) { + dent = _ao_fat_root_get(entry); + if (!dent) { + + if (_ao_fat_root_extend(entry)) + continue; + fd = -AO_FAT_ENOSPC; + break; + } + if (dent[0] == AO_FAT_DENT_EMPTY || dent[0] == AO_FAT_DENT_END) { + fd = _ao_fat_fd_alloc(NULL); + if (fd < 0) { + _ao_fat_root_put(dent, entry, 0); + break; + } + + file = &ao_file_table[fd]; + /* Initialize the dent */ + _ao_fat_root_init(dent, name, AO_FAT_FILE_REGULAR); + + /* Now initialize the dirent from the dent */ + _ao_fat_dirent_init(file->dirent, dent, entry); + + /* And write the dent to storage */ + _ao_fat_root_put(dent, entry, 1); + + status = -AO_FAT_SUCCESS; + break; + } else { + _ao_fat_root_put(dent, entry, 0); + } + entry++; + } + } + } + return fd; +} + +int8_t +ao_fat_creat(char name[11]) +{ + int8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_creat(name); + ao_mutex_put(&ao_fat_mutex); + return status; +} + +/* + * ao_fat_map_current + * + * Map the sector pointed at by the current file offset + */ + +static void * +ao_fat_map_current(struct ao_file *file, int len, cluster_offset_t *offsetp, cluster_offset_t *this_time) +{ + cluster_offset_t offset; + sector_t sector; + void *buf; + + offset = file->offset & SECTOR_MASK; + sector = _ao_fat_current_sector(file); + if (sector == 0xffffffff) { + printf ("invalid sector at offset %d\n", file->offset); + return NULL; + } + buf = _ao_fat_sector_get(sector); + if (!buf) + printf ("sector get failed. Sector %d. Partition end %d\n", sector, partition_end); + if (offset + len < SECTOR_SIZE) + *this_time = len; + else + *this_time = SECTOR_SIZE - offset; + *offsetp = offset; + return buf; +} + +/* + * ao_fat_read + * + * Read from the file + */ +int +ao_fat_read(int8_t fd, void *dst, int len) +{ + uint8_t *dst_b = dst; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) + len = file->dirent->size - file->offset; + + if (len < 0) + len = 0; + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + printf ("map_current failed\n"); + ret = -AO_FAT_EIO; + break; + } + memcpy(dst_b, buf + offset, this_time); + _ao_fat_sector_put(buf, 0); + + ret += this_time; + len -= this_time; + dst_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_write + * + * Write to the file, extended as necessary + */ +int +ao_fat_write(int8_t fd, void *src, int len) +{ + uint8_t *src_b = src; + cluster_offset_t this_time; + cluster_offset_t offset; + uint8_t *buf; + int ret = 0; + struct ao_file *file; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + if (file->offset + len > file->dirent->size) { + ret = _ao_fat_set_size(file, file->offset + len); + if (ret < 0) + goto done; + } + + while (len) { + buf = ao_fat_map_current(file, len, &offset, &this_time); + if (!buf) { + printf ("map_current failed\n"); + ret = -AO_FAT_EIO; + break; + } + memcpy(buf + offset, src_b, this_time); + _ao_fat_sector_put(buf, 1); + + ret += this_time; + len -= this_time; + src_b += this_time; + file->offset = file->offset + this_time; + } +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_seek + * + * Set the position for the next I/O operation + * Note that this doesn't actually change the size + * of the file if the requested position is beyond + * the current file length, that would take a future + * write + */ +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence) +{ + offset_t new_offset; + struct ao_file *file; + int32_t ret; + + ao_mutex_get(&ao_fat_mutex); + file = _ao_fat_fd_to_file(fd); + if (!file) { + ret = -AO_FAT_EBADF; + goto done; + } + + new_offset = file->offset; + switch (whence) { + case AO_FAT_SEEK_SET: + new_offset = pos; + break; + case AO_FAT_SEEK_CUR: + new_offset += pos; + break; + case AO_FAT_SEEK_END: + new_offset = file->dirent->size + pos; + break; + } + ret = file->offset = new_offset; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +/* + * ao_fat_unlink + * + * Remove a file from the directory, marking + * all clusters as free + */ +int8_t +ao_fat_unlink(char name[11]) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int8_t ret; + + ao_mutex_get(&ao_fat_mutex); + if (_ao_fat_setup() != AO_FAT_FILESYSTEM_SUCCESS) { + ret = -AO_FAT_EIO; + goto done; + } + + while (ao_fat_readdir(&entry, &dirent)) { + if (memcmp(name, dirent.name, 11) == 0) { + uint8_t *next; + uint8_t *ent; + uint8_t delete; + + if (AO_FAT_IS_DIR(dirent.attr)) { + ret = -AO_FAT_EISDIR; + goto done; + } + if (!AO_FAT_IS_FILE(dirent.attr)) { + ret = -AO_FAT_EPERM; + goto done; + } + + _ao_fat_free_cluster_chain(dirent.cluster); + next = _ao_fat_root_get(dirent.entry + 1); + if (next && next[0] != AO_FAT_DENT_END) + delete = AO_FAT_DENT_EMPTY; + else + delete = AO_FAT_DENT_END; + if (next) + _ao_fat_root_put(next, dirent.entry + 1, 0); + ent = _ao_fat_root_get(dirent.entry); + if (ent) { + memset(ent, '\0', DIRENT_SIZE); + *ent = delete; + _ao_fat_root_put(ent, dirent.entry, 1); + } + ao_bufio_flush(); + ret = AO_FAT_SUCCESS; + goto done; + } + } + ret = -AO_FAT_ENOENT; +done: + ao_mutex_put(&ao_fat_mutex); + return ret; +} + +int8_t +ao_fat_rename(char old[11], char new[11]) +{ + return -AO_FAT_EIO; +} + +#if FAT_COMMANDS + +static const char *filesystem_errors[] = { + [AO_FAT_FILESYSTEM_SUCCESS] = "FAT file system operating normally", + [AO_FAT_FILESYSTEM_MBR_READ_FAILURE] = "MBR media read error", + [AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE] = "MBR signature invalid", + [AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE] = "Unsupported paritition type", + [AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION] = "Partition has zero sectors", + [AO_FAT_FILESYSTEM_BOOT_READ_FAILURE] = "Boot block media read error", + [AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE] = "Boot block signature invalid", + [AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE] = "Sector size not 512", +}; + +static void +ao_fat_mbr_cmd(void) +{ + uint8_t status; + + ao_mutex_get(&ao_fat_mutex); + status = _ao_fat_setup(); + if (status == AO_FAT_FILESYSTEM_SUCCESS) { + printf ("partition type: %02x\n", partition_type); + printf ("partition start: %08x\n", partition_start); + + printf ("partition end: %08x\n", partition_end); + + printf ("fat32: %d\n", fat32); + printf ("sectors per cluster %d\n", sectors_per_cluster); + printf ("reserved sectors %d\n", reserved_sector_count); + printf ("number of FATs %d\n", number_fat); + printf ("root entries %d\n", root_entries); + printf ("sectors per fat %d\n", sectors_per_fat); + + printf ("fat start %d\n", fat_start); + printf ("root start %d\n", root_start); + printf ("data start %d\n", data_start); + } else { + printf ("FAT filesystem not available: %s\n", filesystem_errors[status]); + } + ao_mutex_put(&ao_fat_mutex); +} + +struct ao_fat_attr { + uint8_t bit; + char label; +}; + +static const struct ao_fat_attr ao_fat_attr[] = { + { .bit = AO_FAT_FILE_READ_ONLY, .label = 'R' }, + { .bit = AO_FAT_FILE_HIDDEN, .label = 'H' }, + { .bit = AO_FAT_FILE_SYSTEM, .label = 'S' }, + { .bit = AO_FAT_FILE_VOLUME_LABEL, .label = 'V' }, + { .bit = AO_FAT_FILE_DIRECTORY, .label = 'D' }, + { .bit = AO_FAT_FILE_ARCHIVE, .label = 'A' }, +}; + +#define NUM_FAT_ATTR (sizeof (ao_fat_attr) / sizeof (ao_fat_attr[0])) + +static void +ao_fat_list_cmd(void) +{ + uint16_t entry = 0; + struct ao_fat_dirent dirent; + int i; + int8_t status; + + while ((status = ao_fat_readdir(&entry, &dirent)) == AO_FAT_SUCCESS) { + for (i = 0; i < 8; i++) + putchar(dirent.name[i]); + putchar('.'); + for (; i < 11; i++) + putchar(dirent.name[i]); + for (i = 0; i < NUM_FAT_ATTR; i++) + putchar (dirent.attr & ao_fat_attr[i].bit ? ao_fat_attr[i].label : ' '); + printf (" @%08x %d\n", dirent.cluster, dirent.size); + } + if (status != -AO_FAT_EDIREOF) + printf ("readdir failed: %d\n", status); +} + +static uint8_t +ao_fat_parse_name(char name[11]) +{ + uint8_t c; + + name[0] = '\0'; + ao_cmd_white(); + c = 0; + while (ao_cmd_lex_c != '\n') { + if (ao_cmd_lex_c == '.') { + for (; c < 8; c++) + name[c] = ' '; + } else { + if (c < 11) + name[c++] = ao_cmd_lex_c; + } + ao_cmd_lex(); + } + while (c < 11) + name[c++] = ' '; +} + +static void +ao_fat_dump_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[32]; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + for (i = 0; i < cnt; i++) + putchar(buf[i]); + } + ao_fat_close(fd); +} + +static void +ao_fat_write_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[64]; + char c; + int status; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_creat(name); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + flush(); + while ((c = getchar()) != 4) { + if (c == '\r') c = '\n'; + if (ao_echo()) { + if (c == '\n') putchar ('\r'); + putchar(c); flush(); + } + status = ao_fat_write(fd, &c, 1); + if (status != 1) { + printf ("Write failure %d\n", status); + break; + } + } + ao_fat_close(fd); +} + +static void +put32(uint32_t a) +{ + ao_cmd_put16(a >> 16); + ao_cmd_put16(a); +} + +static void +ao_fat_hexdump_cmd(void) +{ + char name[11]; + int8_t fd; + int cnt, i; + char buf[8]; + uint32_t addr; + + ao_fat_parse_name(name); + if (name[0] == '\0') { + ao_cmd_status = ao_cmd_syntax_error; + return; + } + + fd = ao_fat_open(name, AO_FAT_OPEN_READ); + if (fd < 0) { + printf ("Open failed: %d\n", fd); + return; + } + addr = 0; + while ((cnt = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + put32(addr); + for (i = 0; i < cnt; i++) { + putchar(' '); + ao_cmd_put8(buf[i]); + } + putchar('\n'); + addr += cnt; + } + ao_fat_close(fd); +} + +static const struct ao_cmds ao_fat_cmds[] = { + { ao_fat_mbr_cmd, "M\0Show FAT MBR and other info" }, + { ao_fat_list_cmd, "F\0List FAT directory" }, + { ao_fat_dump_cmd, "D <name>\0Dump FAT file" }, + { ao_fat_write_cmd, "W <name>\0Write FAT file (end with ^D)" }, + { ao_fat_hexdump_cmd, "H <name>\0HEX dump FAT file" }, + { 0, NULL }, +}; + +#endif + +void +ao_fat_init(void) +{ + ao_bufio_init(); + ao_cmd_register(&ao_fat_cmds[0]); +} diff --git a/src/drivers/ao_fat.h b/src/drivers/ao_fat.h new file mode 100644 index 00000000..01435363 --- /dev/null +++ b/src/drivers/ao_fat.h @@ -0,0 +1,141 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_FAT_H_ +#define _AO_FAT_H_ + +void +ao_fat_init(void); + +#define AO_FAT_FILE_REGULAR 0x00 +#define AO_FAT_FILE_READ_ONLY 0x01 +#define AO_FAT_FILE_HIDDEN 0x02 +#define AO_FAT_FILE_SYSTEM 0x04 +#define AO_FAT_FILE_VOLUME_LABEL 0x08 +#define AO_FAT_FILE_DIRECTORY 0x10 +#define AO_FAT_FILE_ARCHIVE 0x20 + +#define AO_FAT_DENT_EMPTY 0xe5 +#define AO_FAT_DENT_END 0x00 + +#define AO_FAT_IS_FILE(attr) (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY)) == 0) +#define AO_FAT_IS_DIR(attr) (((attr) & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == AO_FAT_FILE_DIRECTORY) + +/* API error codes */ +#define AO_FAT_SUCCESS 0 +#define AO_FAT_EPERM 1 +#define AO_FAT_ENOENT 2 +#define AO_FAT_EIO 4 +#define AO_FAT_EBADF 9 +#define AO_FAT_EACCESS 13 +#define AO_FAT_EEXIST 17 +#define AO_FAT_ENOTDIR 20 +#define AO_FAT_EISDIR 21 +#define AO_FAT_EMFILE 24 +#define AO_FAT_EFBIG 27 +#define AO_FAT_ENOSPC 28 +#define AO_FAT_EDIREOF 29 + +/* ao_fat_setup return values */ +#define AO_FAT_FILESYSTEM_SUCCESS 0 +#define AO_FAT_FILESYSTEM_MBR_READ_FAILURE 1 +#define AO_FAT_FILESYSTEM_INVALID_MBR_SIGNATURE 2 +#define AO_FAT_FILESYSTEM_INVALID_PARTITION_TYPE 3 +#define AO_FAT_FILESYSTEM_ZERO_SIZED_PARTITION 4 + +#define AO_FAT_FILESYSTEM_BOOT_READ_FAILURE 5 +#define AO_FAT_FILESYSTEM_INVALID_BOOT_SIGNATURE 6 +#define AO_FAT_FILESYSTEM_INVALID_SECTOR_SIZE 7 + +void +ao_fat_sync(void); + +void +ao_fat_unmount(void); + +int8_t +ao_fat_full(void); + +int8_t +ao_fat_open(char name[11], uint8_t mode); + +#define AO_FAT_OPEN_READ 0 +#define AO_FAT_OPEN_WRITE 1 +#define AO_FAT_OPEN_RW 2 + +int8_t +ao_fat_creat(char name[11]); + +int8_t +ao_fat_close(int8_t fd); + +int +ao_fat_read(int8_t fd, void *dest, int len); + +int +ao_fat_write(int8_t fd, void *src, int len); + +#define AO_FAT_SEEK_SET 0 +#define AO_FAT_SEEK_CUR 1 +#define AO_FAT_SEEK_END 2 + +int32_t +ao_fat_seek(int8_t fd, int32_t pos, uint8_t whence); + +int8_t +ao_fat_unlink(char name[11]); + +int8_t +ao_fat_rename(char old[11], char new[11]); + +/* + * Byte offset within a file. Supports files up to 2GB in size + */ +typedef int32_t ao_fat_offset_t; + +/* + * Cluster index in partition data space + */ +typedef uint32_t ao_fat_cluster_t; + +/* + * Sector offset within partition + */ +typedef uint32_t ao_fat_sector_t; + +/* + * Index within the root directory + */ +typedef uint16_t ao_fat_dirent_t; + +/* + * Offset within a cluster (or sector) + */ +typedef uint16_t ao_fat_cluster_offset_t; + +struct ao_fat_dirent { + char name[11]; + uint8_t attr; + uint32_t size; + ao_fat_cluster_t cluster; + uint16_t entry; +}; + +int8_t +ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent); + +#endif /* _AO_FAT_H_ */ diff --git a/src/drivers/ao_log_fat.c b/src/drivers/ao_log_fat.c new file mode 100644 index 00000000..af77401c --- /dev/null +++ b/src/drivers/ao_log_fat.c @@ -0,0 +1,95 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" +#include "ao_log.h" +#include "ao_fat.h" + +static uint8_t log_year, log_month, log_day; +static uint8_t log_open; +static int8_t log_fd; +static uint8_t log_mutex; + +static void +ao_log_open(void) +{ + char name[12]; + int8_t status; + + sprintf(name,"%04d%02d%02dLOG", 2000 + log_year, log_month, log_day); + status = ao_fat_open(name, AO_FAT_OPEN_WRITE); + if (status >= 0) { + log_fd = status; + ao_fat_seek(log_fd, 0, AO_FAT_SEEK_END); + log_open = 1; + } else if (status == -AO_FAT_ENOENT) { + status = ao_fat_creat(name); + if (status == AO_FAT_SUCCESS) { + log_fd = status; + log_open = 1; + } + } +} + +static void +ao_log_close(void) +{ + if (log_open) { + log_open = 0; + ao_fat_close(log_fd); + log_fd = -1; + } +} + +uint8_t +ao_log_full(void) +{ + return ao_fat_full(); +} + +uint8_t +ao_log_mega(struct ao_log_mega *log) +{ + uint8_t wrote = 0; + ao_mutex_get(&log_mutex); + if (log->type == AO_LOG_GPS_TIME) { + if (log_open && + (log_year != log->u.gps.year || + log_month != log->u.gps.month || + log_day != log->u.gps.day)) { + ao_log_close(); + } + if (!log_open) { + log_year = log->u.gps.year; + log_month = log->u.gps.month; + log_day = log->u.gps.day; + ao_log_open(); + } + } + if (log_open) { + wrote = ao_fat_write(log_fd, log, sizeof (*log)) == AO_FAT_SUCCESS; + ao_fat_sync(); + } + ao_mutex_put(&log_mutex); + return wrote; +} + +void +ao_log_flush(void) +{ + ao_fat_sync(); +} diff --git a/src/drivers/ao_rfpa0133.c b/src/drivers/ao_rfpa0133.c new file mode 100644 index 00000000..a98e261a --- /dev/null +++ b/src/drivers/ao_rfpa0133.c @@ -0,0 +1,47 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" + +static void +ao_rfpa0133_set_power(uint8_t power) +{ + ao_gpio_set(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, power & 1); + ao_gpio_set(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, (power >> 1) & 1); +} + +void +ao_radio_pa_on(void) +{ + ao_rfpa0133_set_power(ao_config.radio_amp); + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 1); +} + +void +ao_radio_pa_off(void) +{ + ao_gpio_set(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_rfpa0133_set_power(0); +} + +void +ao_radio_pa_init(void) +{ + ao_enable_output(AO_PA_POWER_GPIO, AO_PA_POWER_PIN, AO_PA_POWER, 0); + ao_enable_output(AO_PA_GAIN_8_GPIO, AO_PA_GAIN_8_PIN, AO_PA_GAIN_8, 0); + ao_enable_output(AO_PA_GAIN_16_GPIO, AO_PA_GAIN_16_PIN, AO_PA_GAIN_16, 0); +} diff --git a/src/drivers/ao_rfpa0133.h b/src/drivers/ao_rfpa0133.h new file mode 100644 index 00000000..2ba7f699 --- /dev/null +++ b/src/drivers/ao_rfpa0133.h @@ -0,0 +1,30 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_RFPA0133_H_ +#define _AO_RFPA0133_H_ + +void +ao_rfpa0133_on(void); + +void +ao_rfpa0133_off(void); + +void +ao_rfpa0133_init(void); + +#endif /* _AO_RFPA0133_H_ */ diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c new file mode 100644 index 00000000..c13017f0 --- /dev/null +++ b/src/drivers/ao_sdcard.c @@ -0,0 +1,700 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" +#include "ao_sdcard.h" + +extern uint8_t ao_radio_mutex; + +#define ao_sdcard_get_slow() do { ao_mutex_get(&ao_radio_mutex); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz); } while (0) +#define ao_sdcard_get() do { ao_mutex_get(&ao_radio_mutex); ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST); } while (0) +#define ao_sdcard_put() do { ao_spi_put(AO_SDCARD_SPI_BUS); ao_mutex_put(&ao_radio_mutex); } while (0) +#define ao_sdcard_send_fixed(d,l) ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_send(d,l) ao_spi_send((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_recv(d,l) ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_select() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0) +#define ao_sdcard_deselect() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1) + +/* Include SD card commands */ +#define SDCARD_DEBUG 0 + +/* Spew SD tracing */ +#define SDCARD_TRACE 0 + +/* Emit error and warning messages */ +#define SDCARD_WARN 0 + +static uint8_t initialized; +static uint8_t present; +static uint8_t mutex; +static enum ao_sdtype sdtype; + +#define ao_sdcard_lock() ao_mutex_get(&mutex) +#define ao_sdcard_unlock() ao_mutex_put(&mutex) + +#if SDCARD_TRACE +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +#if SDCARD_WARN +#define WARN(...) printf(__VA_ARGS__) +#else +#define WARN(...) +#endif + +#define later(x,y) ((int16_t) ((x) - (y)) >= 0) + +/* + * Wait while the card is busy. The card will return a stream of 0xff + * when it is ready to accept a command + */ + +static uint8_t +ao_sdcard_wait_busy(void) +{ + uint16_t timeout = ao_time() + SDCARD_BUSY_TIMEOUT; + uint8_t reply; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG("\t\twait busy %02x\n", reply); + if (reply == 0xff) + break; + if (later(ao_time(), timeout)) { + WARN("wait busy timeout\n"); + return 0; + } + } + return 1; +} + + +/* + * Send an SD command and await the status reply + */ + +static uint8_t +ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) +{ + uint8_t data[6]; + uint8_t reply; + int i; + uint16_t timeout; + + DBG ("\tsend_cmd %d arg %08x\n", cmd, arg); + + /* Wait for the card to not be busy */ + if (cmd != SDCARD_GO_IDLE_STATE) { + if (!ao_sdcard_wait_busy()) + return SDCARD_STATUS_TIMEOUT; + } + + data[0] = cmd & 0x3f | 0x40; + data[1] = arg >> 24; + data[2] = arg >> 16; + data[3] = arg >> 8; + data[4] = arg; + if (cmd == SDCARD_GO_IDLE_STATE) + data[5] = 0x95; /* Valid for 0 arg */ + else if (cmd == SDCARD_SEND_IF_COND) + data[5] = 0x87; /* Valid for 0x1aa arg */ + else + data[5] = 0xff; /* no CRC */ + ao_sdcard_send(data, 6); + + /* The first reply byte will be the status, + * which must have the high bit clear + */ + timeout = ao_time() + SDCARD_CMD_TIMEOUT; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG ("\t\tgot byte %02x\n", reply); + if ((reply & 0x80) == 0) + break; + if (later(ao_time(), timeout)) { + WARN("send_cmd %02x timeout\n", cmd); + return SDCARD_STATUS_TIMEOUT; + } + } +#if SDCARD_WARN + if (reply != SDCARD_STATUS_READY_STATE && reply != SDCARD_STATUS_IDLE_STATE) + WARN("send_cmd %d failed %02x\n", cmd, reply); +#endif + return reply; +} + +/* + * Retrieve any reply, discarding the trailing CRC byte + */ +static void +ao_sdcard_recv_reply(uint8_t *reply, int len) +{ + uint8_t discard; + + if (len) + ao_sdcard_recv(reply, len); + /* trailing byte */ + ao_sdcard_recv(&discard, 1); +} + +/* + * Switch to 'idle' state. This is used to get the card into SPI mode + */ +static uint8_t +ao_sdcard_go_idle_state(void) +{ + uint8_t ret; + + DBG ("go_idle_state\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_GO_IDLE_STATE, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tgo_idle_state status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_op_cond(void) +{ + uint8_t ret; + + DBG ("send_op_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_OP_COND, 0); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + DBG ("\tsend_op_cond %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4]) +{ + uint8_t ret; + + DBG ("send_if_cond\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SEND_IF_COND, arg); + if (ret != SDCARD_STATUS_IDLE_STATE) { + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; + } + ao_sdcard_recv_reply(send_if_cond_response, 4); + DBG ("send_if_cond status %02x response %02x %02x %02x %02x\n", + ret, + send_if_cond_response[0], + send_if_cond_response[1], + send_if_cond_response[2], + send_if_cond_response[3]); + ao_sdcard_deselect(); + return ret; +} + +/* + * _ao_sdcard_send_status + * + * Get the 2-byte status value. + * + * Called from other functions with CS held low already, + * hence prefixing the name with '_' + */ +static uint16_t +_ao_sdcard_send_status(void) +{ + uint8_t ret; + uint8_t extra; + + DBG ("send_status\n"); + ret = ao_sdcard_send_cmd(SDCARD_SEND_STATUS, 0); + ao_sdcard_recv_reply(&extra, 1); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret | (extra << 8); +} + +/* + * ao_sdcard_set_blocklen + * + * Set the block length for future read and write commands + */ +static uint8_t +ao_sdcard_set_blocklen(uint32_t blocklen) +{ + uint8_t ret; + + DBG ("set_blocklen %d\n", blocklen); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen); + ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tsend_if_cond failed %02x\n", ret); + return ret; +} + +/* + * _ao_sdcard_app_cmd + * + * Send the app command prefix + * + * Called with the CS held low, hence + * the '_' prefix + */ +static uint8_t +_ao_sdcard_app_cmd(void) +{ + uint8_t ret; + + DBG ("app_cmd\n"); + ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0); + ao_sdcard_recv_reply(NULL, 0); + DBG ("\tapp_cmd status %02x\n"); + return ret; +} + +static uint8_t +ao_sdcard_app_send_op_cond(uint32_t arg) +{ + uint8_t ret; + + DBG("send_op_comd\n"); + ao_sdcard_select(); + ret = _ao_sdcard_app_cmd(); + if (ret != SDCARD_STATUS_IDLE_STATE) + goto bail; + ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg); + ao_sdcard_recv_reply(NULL, 0); +bail: + ao_sdcard_deselect(); + DBG ("\tapp_send_op_cond status %02x\n", ret); + return ret; +} + +static uint8_t +ao_sdcard_read_ocr(uint8_t read_ocr_response[4]) +{ + uint8_t ret; + + DBG ("read_ocr\n"); + ao_sdcard_select(); + ret = ao_sdcard_send_cmd(SDCARD_READ_OCR, 0); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("\tread_ocr failed %02x\n", ret); + else { + ao_sdcard_recv_reply(read_ocr_response, 4); + DBG ("\tread_ocr status %02x response %02x %02x %02x %02x\n", ret, + read_ocr_response[0], read_ocr_response[1], + read_ocr_response[2], read_ocr_response[3]); + } + ao_sdcard_deselect(); + return ret; +} + +/* + * Follow the flow-chart defined by the SD group to + * initialize the card and figure out what kind it is + */ +static void +ao_sdcard_setup(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + DBG ("Testing sdcard\n"); + + ao_sdcard_get_slow(); + /* + * min 74 clocks with CS high + */ + ao_sdcard_send_fixed(0xff, 10); + + /* Reset the card and get it into SPI mode */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) + goto bail; + + /* Figure out what kind of card we have */ + sdtype = ao_sdtype_unknown; + + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_OP_COND_RETRY; i++) { + ao_delay(AO_MS_TO_TICKS(10)); + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + sdtype = ao_sdtype_mmc3; + } else { + /* SD */ + if (sdver2 != 0) { + ret = ao_sdcard_read_ocr(response); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + if ((response[0] & 0xc0) == 0xc0) + sdtype = ao_sdtype_sd2block; + else + sdtype = ao_sdtype_sd2byte; + } else { + sdtype = ao_sdtype_sd1; + } + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } + + DBG ("SD card detected, type %d\n", sdtype); +bail: + ao_sdcard_put(); +} + +static uint8_t +_ao_sdcard_reset(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_RETRY) { + ret = 0x3f; + goto bail; + } + + /* Follow the setup path to get the card out of idle state and + * up and running again + */ + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_IDLE_RETRY; i++) { + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } +bail: + return ret; +} + +/* + * The card will send 0xff until it is ready to send + * the data block at which point it will send the START_BLOCK + * marker followed by the data. This function waits while + * the card is sending 0xff + */ +static uint8_t +ao_sdcard_wait_block_start(void) +{ + uint8_t v; + uint16_t timeout = ao_time() + SDCARD_BLOCK_TIMEOUT; + + DBG ("\twait_block_start\n"); + for (;;) { + ao_sdcard_recv(&v, 1); + DBG("\t\trecv %02x\n", v); + if (v != 0xff) + break; + if (later(ao_time(), timeout)) { + printf ("wait block start timeout\n"); + return 0xff; + } + } + return v; +} + +/* + * Read a block of 512 bytes from the card + */ +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t start_block; + uint8_t crc[2]; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("read block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) { + uint16_t status; + WARN ("read block command failed %d status %02x\n", block, ret); + status = _ao_sdcard_send_status(); + WARN ("\tstatus now %04x\n", status); + goto bail; + } + + ao_sdcard_send_fixed(0xff, 1); + + /* Wait for the data start block marker */ + start_block = ao_sdcard_wait_block_start(); + if (start_block != SDCARD_DATA_START_BLOCK) { + WARN ("wait block start failed %02x\n", start_block); + ret = 0x3f; + goto bail; + } + + ao_sdcard_recv(data, 512); + ao_sdcard_recv(crc, 2); + bail: + ao_sdcard_deselect(); + if (ret == SDCARD_STATUS_READY_STATE) + break; + if (ret == SDCARD_STATUS_IDLE_STATE) { + ret = _ao_sdcard_reset(); + if (ret != SDCARD_STATUS_READY_STATE) + break; + } + } + ao_sdcard_put(); + ao_sdcard_unlock(); + +#if SDCARD_WARN + if (ret != SDCARD_STATUS_READY_STATE) + WARN("read failed\n"); + else if (tries) + WARN("took %d tries to read %d\n", tries + 1, block); +#endif + + DBG("read %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + return ret == SDCARD_STATUS_READY_STATE; +} + +/* + * Write a block of 512 bytes to the card + */ +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ + uint8_t ret; + uint8_t response[1]; + uint8_t start_block[8]; + uint16_t status; + static uint8_t check_data[512]; + int i; + int tries; + + ao_sdcard_lock(); + if (!initialized) { + ao_sdcard_setup(); + initialized = 1; + if (sdtype != ao_sdtype_unknown) + present = 1; + } + if (!present) { + ao_sdcard_unlock(); + return 0; + } + DBG("write block %d\n", block); + if (sdtype != ao_sdtype_sd2block) + block <<= 9; + + ao_sdcard_get(); + + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + + /* Write a pad byte followed by the data start block marker */ + start_block[0] = 0xff; + start_block[1] = SDCARD_DATA_START_BLOCK; + ao_sdcard_send(start_block, 2); + + /* Send the data */ + ao_sdcard_send(data, 512); + + /* Fake the CRC */ + ao_sdcard_send_fixed(0xff, 2); + + /* See if the card liked the data */ + ao_sdcard_recv(response, sizeof (response)); + if ((response[0] & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) { + int i; + WARN("Data not accepted, response"); + for (i = 0; i < sizeof (response); i++) + WARN(" %02x", response[i]); + WARN("\n"); + ret = 0x3f; + goto bail; + } + + /* Wait for the bus to go idle (should be done with an interrupt?) */ + if (!ao_sdcard_wait_busy()) { + ret = 0x3f; + goto bail; + } + + /* Check the current status after the write completes */ + status = _ao_sdcard_send_status(); + if ((status & 0xff) != SDCARD_STATUS_READY_STATE) { + WARN ("send status after write %04x\n", status); + ret = status & 0xff; + goto bail; + } + bail: + ao_sdcard_deselect(); + DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + if (ret == SDCARD_STATUS_READY_STATE) + break; + } + ao_sdcard_put(); + ao_sdcard_unlock(); + if (tries) + WARN("took %d tries to write %d\n", tries + 1, block); + + return ret == SDCARD_STATUS_READY_STATE; +} + +#if SDCARD_DEBUG +static uint8_t test_data[512]; + +static void +ao_sdcard_test_read(void) +{ + int i; + + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) + return; + + for (i = 0; i < 100; i++) { + printf ("."); flush(); + if (!ao_sdcard_read_block(ao_cmd_lex_u32+i, test_data)) { + printf ("read error %d\n", i); + return; + } + } + printf ("data:"); + for (i = 0; i < 18; i++) + printf (" %02x", test_data[i]); + printf ("\n"); +} + +static void +ao_sdcard_test_write(void) +{ + int i; + printf ("data:"); + for (i = 0; i < 16; i++) { + test_data[i]++; + printf (" %02x", test_data[i]); + } + printf ("\n"); + if (!ao_sdcard_write_block(1, test_data)) { + printf ("write error\n"); + return; + } +} + +static const struct ao_cmds ao_sdcard_cmds[] = { + { ao_sdcard_test_read, "x\0Test read" }, + { ao_sdcard_test_write, "y\0Test read" }, + { 0, NULL }, +}; +#endif + +void +ao_sdcard_init(void) +{ + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_SCK_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MISO_PIN, STM_PUPDR_PULL_UP); + stm_pupdr_set(AO_SDCARD_SPI_PORT, AO_SDCARD_SPI_MOSI_PIN, STM_PUPDR_PULL_UP); + ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN)); +#if SDCARD_DEBUG + ao_cmd_register(&ao_sdcard_cmds[0]); +#endif +} diff --git a/src/drivers/ao_sdcard.h b/src/drivers/ao_sdcard.h new file mode 100644 index 00000000..50b70c73 --- /dev/null +++ b/src/drivers/ao_sdcard.h @@ -0,0 +1,82 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_SDCARD_H_ +#define _AO_SDCARD_H_ + +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data); + +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data); + +void +ao_sdcard_init(void); + +/* Commands */ +#define SDCARD_GO_IDLE_STATE 0 +#define SDCARD_SEND_OP_COND 1 +#define SDCARD_SEND_IF_COND 8 +#define SDCARD_SEND_CSD 9 +#define SDCARD_SEND_CID 10 +#define SDCARD_SEND_STATUS 13 +#define SDCARD_SET_BLOCKLEN 16 +#define SDCARD_READ_BLOCK 17 +#define SDCARD_WRITE_BLOCK 24 +#define SDCARD_WRITE_MULTIPLE_BLOCK 25 +#define SDCARD_ERASE_WR_BLK_START 32 +#define SDCARD_ERASE_WR_BLK_END 33 +#define SDCARD_ERASE 38 +#define SDCARD_APP_CMD 55 +#define SDCARD_READ_OCR 58 + +/* App commands */ +#define SDCARD_APP_SET_WR_BLK_ERASE_COUNT 23 +#define SDCARD_APP_SEND_OP_COMD 41 + +/* Status */ +#define SDCARD_STATUS_READY_STATE 0x00 +#define SDCARD_STATUS_IDLE_STATE 0x01 +#define SDCARD_STATUS_ERASE_RESET 0x02 +#define SDCARD_STATUS_ILLEGAL_COMMAND 0x04 +#define SDCARD_STATUS_COM_CRC_ERROR 0x08 +#define SDCARD_STATUS_ERASE_SEQ_ERROR 0x10 +#define SDCARD_STATUS_ADDRESS_ERROR 0x20 +#define SDCARD_STATUS_PARAMETER_ERROR 0x40 +#define SDCARD_STATUS_TIMEOUT 0xff + +#define SDCARD_DATA_START_BLOCK 0xfe +#define SDCARD_STOP_TRAN_TOKEN 0xfd +#define SDCARD_WRITE_MULTIPLE_TOKEN 0xfc +#define SDCARD_DATA_RES_MASK 0x1f +#define SDCARD_DATA_RES_ACCEPTED 0x05 + +#define SDCARD_CMD_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BUSY_TIMEOUT AO_MS_TO_TICKS(20) +#define SDCARD_BLOCK_TIMEOUT AO_MS_TO_TICKS(200) +#define SDCARD_IDLE_RETRY 10 +#define SDCARD_OP_COND_RETRY 10 + +enum ao_sdtype { + ao_sdtype_unknown, + ao_sdtype_mmc3, + ao_sdtype_sd1, + ao_sdtype_sd2byte, + ao_sdtype_sd2block, +}; + +#endif /* _AO_SDCARD_H_ */ diff --git a/src/megametrum-v0.1/.gitignore b/src/megametrum-v0.1/.gitignore deleted file mode 100644 index b04d3950..00000000 --- a/src/megametrum-v0.1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -ao_product.h -megametrum-*.elf diff --git a/src/stm-bringup/Makefile b/src/stm-bringup/Makefile index 5cc94bd9..1bc5aaad 100644 --- a/src/stm-bringup/Makefile +++ b/src/stm-bringup/Makefile @@ -8,8 +8,8 @@ endif CC=arm-none-eabi-gcc OBJCOPY=arm-none-eabi-objcopy -PDCLIB=/home/keithp/sat -C_LIB=$(PDCLIB)/lib/pdclib.a +PDCLIB=/opt/cortex +C_LIB=$(PDCLIB)/lib/pdclib-cortex-m3.a C_INC=-I$(PDCLIB)/include DEF_CFLAGS=-g -std=gnu99 -Os -mlittle-endian -mthumb -ffreestanding -nostdlib -I. -I../../src/stm $(C_INC) diff --git a/src/stm/Makefile.defs b/src/stm/Makefile.defs index 3ce6eead..c8bb7d70 100644 --- a/src/stm/Makefile.defs +++ b/src/stm/Makefile.defs @@ -13,8 +13,8 @@ vpath ao-make-product.5c ../util objcopy -O ihex $*.elf $@ CC=arm-none-eabi-gcc -SAT=/home/keithp/sat -SAT_CLIB=$(SAT)/lib/pdclib.a +SAT=/opt/cortex +SAT_CLIB=$(SAT)/lib/pdclib-cortex-m3.a SAT_CFLAGS=-I$(SAT)/include ifndef VERSION diff --git a/src/stm/ao_eeprom_stm.c b/src/stm/ao_eeprom_stm.c index 5a75a97d..58783f1a 100644 --- a/src/stm/ao_eeprom_stm.c +++ b/src/stm/ao_eeprom_stm.c @@ -55,10 +55,16 @@ ao_storage_erase(ao_pos_t pos) __reentrant static void ao_intflash_unlock(void) { + /* Disable backup write protection */ + stm_pwr.cr |= (1 << STM_PWR_CR_DBP); + /* Unlock Data EEPROM and FLASH_PECR register */ stm_flash.pekeyr = STM_FLASH_PEKEYR_PEKEY1; stm_flash.pekeyr = STM_FLASH_PEKEYR_PEKEY2; + if (stm_flash.pecr & (1 << STM_FLASH_PECR_PELOCK)) + printf ("eeprom unlock failed\n"); + /* Configure the FTDW bit (FLASH_PECR[8]) to execute * word write, whatever the previous value of the word * being written to @@ -68,8 +74,8 @@ ao_intflash_unlock(void) (0 << STM_FLASH_PECR_EOPIE) | (0 << STM_FLASH_PECR_FPRG) | (0 << STM_FLASH_PECR_ERASE) | - (0 << STM_FLASH_PECR_FTDW) | - (1 << STM_FLASH_PECR_DATA) | + (1 << STM_FLASH_PECR_FTDW) | + (0 << STM_FLASH_PECR_DATA) | (0 << STM_FLASH_PECR_PROG) | (0 << STM_FLASH_PECR_OPTLOCK) | (0 << STM_FLASH_PECR_PRGLOCK) | @@ -97,15 +103,9 @@ ao_intflash_write32(uint16_t pos, uint32_t w) addr = (uint32_t *) (stm_eeprom + pos); - /* Erase previous word */ - *addr = 0; + /* Write a word to a valid address in the data EEPROM */ + *addr = w; ao_intflash_wait(); - - if (w) { - /* Write a word to a valid address in the data EEPROM */ - *addr = w; - ao_intflash_wait(); - } } static void diff --git a/src/stm/ao_i2c_stm.c b/src/stm/ao_i2c_stm.c index 779e2275..809b5c6f 100644 --- a/src/stm/ao_i2c_stm.c +++ b/src/stm/ao_i2c_stm.c @@ -36,7 +36,7 @@ static uint16_t ao_i2c_addr[STM_NUM_I2C]; uint8_t ao_i2c_mutex[STM_NUM_I2C]; # define I2C_HIGH_SLOW 5000 /* ns, 100kHz clock */ -#ifdef MEGAMETRUM +#ifdef TELEMEGA # define I2C_HIGH_FAST 2000 /* ns, 167kHz clock */ #else # define I2C_HIGH_FAST 1000 /* ns, 333kHz clock */ diff --git a/src/stm/ao_spi_stm.c b/src/stm/ao_spi_stm.c index 7b4af964..56329c24 100644 --- a/src/stm/ao_spi_stm.c +++ b/src/stm/ao_spi_stm.c @@ -160,6 +160,8 @@ ao_spi_recv(void *block, uint16_t len, uint8_t spi_index) uint8_t mosi_dma_index = ao_spi_stm_info[AO_SPI_INDEX(spi_index)].mosi_dma_index; uint8_t miso_dma_index = ao_spi_stm_info[AO_SPI_INDEX(spi_index)].miso_dma_index; + spi_dev_null = 0xff; + /* Set up transmit DMA to make the SPI hardware actually run */ ao_dma_set_transfer(mosi_dma_index, &stm_spi->dr, diff --git a/src/stm/ao_timer.c b/src/stm/ao_timer.c index e07625d8..8b7c2327 100644 --- a/src/stm/ao_timer.c +++ b/src/stm/ao_timer.c @@ -18,15 +18,12 @@ #include "ao.h" #include <ao_task.h> -volatile __data AO_TICK_TYPE ao_tick_count; +volatile AO_TICK_TYPE ao_tick_count; -uint16_t ao_time(void) +AO_TICK_TYPE +ao_time(void) { - uint16_t v; - ao_arch_critical( - v = ao_tick_count; - ); - return v; + return ao_tick_count; } #if AO_DATA_ALL diff --git a/src/telebt-v0.0/.sdcdbrc b/src/telebt-v0.0/.sdcdbrc deleted file mode 100644 index 710b4a2f..00000000 --- a/src/telebt-v0.0/.sdcdbrc +++ /dev/null @@ -1 +0,0 @@ ---directory=.. diff --git a/src/telebt-v0.0/Makefile b/src/telebt-v0.0/Makefile deleted file mode 100644 index e89639ab..00000000 --- a/src/telebt-v0.0/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -# -# TeleBT v0.0 build -# - -TELEBT_VER=0.0 -TELEBT_DEF=0_0 - -include ../product/Makefile.telebt - diff --git a/src/telebt-v0.1/.gitignore b/src/telebt-v0.1/.gitignore deleted file mode 100644 index 1acfbfcc..00000000 --- a/src/telebt-v0.1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -telebt-* -ao_product.h diff --git a/src/telebt-v0.1/Makefile b/src/telebt-v0.1/Makefile deleted file mode 100644 index 90cd3cac..00000000 --- a/src/telebt-v0.1/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# -# TeleBT v0.1 build -# - -TELEBT_VER=0.1 -TELEBT_DEF=0_1 - -TELEBT_INC = \ - ao_25lc1024.h - -TELEBT_SRC = \ - ao_beep.c \ - ao_log_single.c \ - ao_log_telem.c \ - ao_report.c \ - ao_spi.c \ - ao_storage.c \ - ao_m25.c - -include ../product/Makefile.telebt - diff --git a/src/telebt-v0.0/.gitignore b/src/telebt-v1.0/.gitignore index 1acfbfcc..1acfbfcc 100644 --- a/src/telebt-v0.0/.gitignore +++ b/src/telebt-v1.0/.gitignore diff --git a/src/telebt-v0.1/.sdcdbrc b/src/telebt-v1.0/.sdcdbrc index b9f6129c..b9f6129c 100644 --- a/src/telebt-v0.1/.sdcdbrc +++ b/src/telebt-v1.0/.sdcdbrc diff --git a/src/product/Makefile.telebt b/src/telebt-v1.0/Makefile index fd52cec4..911a8b09 100644 --- a/src/product/Makefile.telebt +++ b/src/telebt-v1.0/Makefile @@ -1,8 +1,9 @@ # # TeleBT build file # -# Define TELEBT_VER, TELEBT_DEF, TELEBT_INC and TELEBT_SRC -# and include this file + +TELEBT_VER=1.0 +TELEBT_DEF=1_0 vpath %.c ..:../core:../cc1111:../drivers:../product vpath %.h ..:../core:../cc1111:../drivers:../product @@ -18,8 +19,7 @@ INC = \ ao_arch.h \ ao_arch_funcs.h \ cc1111.h \ - ao_product.h \ - $(TELEBT_INC) + ao_product.h CORE_SRC = \ ao_cmd.c \ @@ -35,6 +35,7 @@ CORE_SRC = \ CC1111_SRC = \ ao_dbg.c \ + ao_adc.c \ ao_dma.c \ ao_led.c \ ao_packet.c \ @@ -57,8 +58,7 @@ SRC = \ $(CORE_SRC) \ $(CC1111_SRC) \ $(DRIVER_SRC) \ - $(PRODUCT_SRC) \ - $(TELEBT_SRC) + $(PRODUCT_SRC) PROGNAME = telebt-v$(TELEBT_VER) PROG = $(PROGNAME)-$(VERSION).ihx diff --git a/src/telebt-v1.0/ao_pins.h b/src/telebt-v1.0/ao_pins.h new file mode 100644 index 00000000..9e47f3b8 --- /dev/null +++ b/src/telebt-v1.0/ao_pins.h @@ -0,0 +1,101 @@ +/* + * Copyright © 2010 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_PINS_H_ +#define _AO_PINS_H_ + +#define HAS_RADIO 1 +#define HAS_FLIGHT 0 +#define HAS_USB 1 +#define HAS_BEEP 0 +#define HAS_SERIAL_1 1 +#define HAS_SERIAL_1_ALT_1 1 +#define HAS_SERIAL_1_ALT_2 0 +#define HAS_SERIAL_1_HW_FLOW 1 +#define USE_SERIAL_1_STDIN 1 +#define DELAY_SERIAL_1_STDIN 1 +#define HAS_DBG 1 +#define HAS_EEPROM 0 +#define HAS_LOG 0 +#define USE_INTERNAL_FLASH 0 +#define HAS_BTM 1 +#define DBG_ON_P1 1 +#define DBG_ON_P0 0 +#define PACKET_HAS_MASTER 1 +#define PACKET_HAS_SLAVE 0 +#define AO_LED_RED 1 +#define AO_LED_BLUE 2 +#define LEDS_AVAILABLE (AO_LED_RED|AO_LED_BLUE) +#define AO_MONITOR_LED AO_LED_RED +#define AO_BT_LED AO_LED_BLUE +#define BT_LINK_ON_P2 0 +#define BT_LINK_ON_P1 1 +#define BT_LINK_PIN_INDEX 7 +#define BT_LINK_PIN P1_7 +#define HAS_MONITOR 1 +#define LEGACY_MONITOR 0 + +#define HAS_ADC 1 +#define AO_PAD_ADC_BATT 0 +#define AO_ADC_PINS (1 << AO_PAD_ADC_BATT) + +struct ao_adc { + int16_t batt; +}; + +#define AO_ADC_DUMP(p) \ + printf ("tick: %5u batt %5d\n", \ + (p)->tick, \ + (p)->adc.batt) + +#if DBG_ON_P1 + + #define DBG_CLOCK (1 << 4) /* mi0 */ + #define DBG_DATA (1 << 5) /* mo0 */ + #define DBG_RESET_N (1 << 3) /* c0 */ + + #define DBG_CLOCK_PIN (P1_4) + #define DBG_DATA_PIN (P1_5) + #define DBG_RESET_N_PIN (P1_3) + + #define DBG_PORT_NUM 1 + #define DBG_PORT P1 + #define DBG_PORT_SEL P1SEL + #define DBG_PORT_INP P1INP + #define DBG_PORT_DIR P1DIR + +#endif /* DBG_ON_P1 */ + +#if DBG_ON_P0 + + #define DBG_CLOCK (1 << 3) + #define DBG_DATA (1 << 4) + #define DBG_RESET_N (1 << 5) + + #define DBG_CLOCK_PIN (P0_3) + #define DBG_DATA_PIN (P0_4) + #define DBG_RESET_N_PIN (P0_5) + + #define DBG_PORT_NUM 0 + #define DBG_PORT P0 + #define DBG_PORT_SEL P0SEL + #define DBG_PORT_INP P0INP + #define DBG_PORT_DIR P0DIR + +#endif /* DBG_ON_P0 */ + +#endif /* _AO_PINS_H_ */ diff --git a/src/product/ao_telebt.c b/src/telebt-v1.0/ao_telebt.c index b147eabc..935cde7d 100644 --- a/src/product/ao_telebt.c +++ b/src/telebt-v1.0/ao_telebt.c @@ -17,10 +17,6 @@ #include "ao.h" -#if HAS_LOG -__code uint8_t ao_log_format = AO_LOG_FORMAT_NONE; /* until we actually log stuff */ -#endif - void main(void) { @@ -33,32 +29,16 @@ main(void) ao_task_init(); ao_timer_init(); -#if HAS_BEEP - ao_beep_init(); -#endif ao_cmd_init(); -#if HAS_EEPROM - ao_spi_init(); - ao_storage_init(); -#endif ao_usb_init(); ao_monitor_init(); -#if HAS_LOG - ao_report_init(); -#endif ao_radio_init(); ao_packet_master_init(); + ao_adc_init(); ao_btm_init(); -#if HAS_LOG - ao_log_single_init(); -#endif #if HAS_DBG ao_dbg_init(); #endif -#if HAS_AES - ao_aes_init(); - ao_radio_cmac_init(); -#endif ao_config_init(); ao_start_scheduler(); } diff --git a/src/telegps-v0.1/.gitignore b/src/telegps-v0.1/.gitignore new file mode 100644 index 00000000..b1e261f5 --- /dev/null +++ b/src/telegps-v0.1/.gitignore @@ -0,0 +1,2 @@ +ao_product.h +telegps-*.elf diff --git a/src/telegps-v0.1/Makefile b/src/telegps-v0.1/Makefile new file mode 100644 index 00000000..aae37660 --- /dev/null +++ b/src/telegps-v0.1/Makefile @@ -0,0 +1,98 @@ +# +# AltOS build +# +# + +include ../stm/Makefile.defs + +INC = \ + ao.h \ + ao_arch.h \ + ao_arch_funcs.h \ + ao_pins.h \ + ao_product.h \ + ao_task.h \ + ao_whiten.h \ + ao_cc115l.h \ + ao_fec.h \ + stm32l.h \ + ao_sdcard.h \ + ao_bufio.h \ + ao_fat.h \ + Makefile + + +#PROFILE=ao_profile.c +#PROFILE_DEF=-DAO_PROFILE=1 + +#SAMPLE_PROFILE=ao_sample_profile.c \ +# ao_sample_profile_timer.c +#SAMPLE_PROFILE_DEF=-DHAS_SAMPLE_PROFILE=1 + +#STACK_GUARD=ao_mpu_stm.c +#STACK_GUARD_DEF=-DHAS_STACK_GUARD=1 + +ALTOS_SRC = \ + ao_interrupt.c \ + ao_product.c \ + ao_romconfig.c \ + ao_cmd.c \ + ao_config.c \ + ao_task.c \ + ao_led.c \ + ao_stdio.c \ + ao_panic.c \ + ao_timer.c \ + ao_mutex.c \ + ao_freq.c \ + ao_dma_stm.c \ + ao_spi_stm.c \ + ao_usb_stm.c \ + ao_exti_stm.c \ + ao_serial_stm.c \ + ao_gps_skytraq.c \ + ao_cc115l.c \ + ao_fec_tx.c \ + ao_rfpa0133.c \ + ao_aprs.c \ + ao_storage.c \ + ao_eeprom_stm.c \ + ao_sdcard.c \ + ao_bufio.c \ + ao_fat.c \ + ao_log_fat.c \ + ao_gps_report_mega.c \ + ao_telemetry.c \ + $(SAMPLE_PROFILE) + +PRODUCT=TeleGPS-v0.1 +PRODUCT_DEF=-DTELEGPS +IDPRODUCT=0x0025 + +CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) $(SAMPLE_PROFILE_DEF) $(STACK_GUARD_DEF) -Os -g + +PROGNAME=telegps-v0.1 +PROG=$(PROGNAME)-$(VERSION).elf + +SRC=$(ALTOS_SRC) ao_telegps.c +OBJ=$(SRC:.c=.o) + +all: $(PROG) + +$(PROG): Makefile $(OBJ) altos.ld + $(call quiet,CC) $(LDFLAGS) $(CFLAGS) -o $(PROG) $(OBJ) $(SAT_CLIB) -lgcc + +$(OBJ): $(INC) + +ao_product.h: ao-make-product.5c ../Version + $(call quiet,NICKLE,$<) $< -m altusmetrum.org -i $(IDPRODUCT) -p $(PRODUCT) -v $(VERSION) > $@ + +distclean: clean + +clean: + rm -f *.o $(PROGNAME)-*.elf + rm -f ao_product.h + +install: + +uninstall: diff --git a/src/telegps-v0.1/ao_pins.h b/src/telegps-v0.1/ao_pins.h new file mode 100644 index 00000000..5bea2681 --- /dev/null +++ b/src/telegps-v0.1/ao_pins.h @@ -0,0 +1,182 @@ +/* + * Copyright © 2012 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#ifndef _AO_PINS_H_ +#define _AO_PINS_H_ + +#define HAS_TASK_QUEUE 1 + +/* 8MHz High speed external crystal */ +#define AO_HSE 8000000 + +/* PLLVCO = 96MHz (so that USB will work) */ +#define AO_PLLMUL 12 +#define AO_RCC_CFGR_PLLMUL (STM_RCC_CFGR_PLLMUL_12) + +/* SYSCLK = 32MHz (no need to go faster than CPU) */ +#define AO_PLLDIV 3 +#define AO_RCC_CFGR_PLLDIV (STM_RCC_CFGR_PLLDIV_3) + +/* HCLK = 32MHz (CPU clock) */ +#define AO_AHB_PRESCALER 1 +#define AO_RCC_CFGR_HPRE_DIV STM_RCC_CFGR_HPRE_DIV_1 + +/* Run APB1 at 16MHz (HCLK/2) */ +#define AO_APB1_PRESCALER 2 +#define AO_RCC_CFGR_PPRE1_DIV STM_RCC_CFGR_PPRE2_DIV_2 + +/* Run APB2 at 16MHz (HCLK/2) */ +#define AO_APB2_PRESCALER 2 +#define AO_RCC_CFGR_PPRE2_DIV STM_RCC_CFGR_PPRE2_DIV_2 + +#define HAS_SERIAL_1 1 +#define USE_SERIAL_1_STDIN 0 +#define SERIAL_1_PB6_PB7 1 +#define SERIAL_1_PA9_PA10 0 + +#define HAS_SERIAL_2 1 +#define USE_SERIAL_2_STDIN 0 +#define SERIAL_2_PA2_PA3 1 +#define SERIAL_2_PD5_PD6 0 + +#define HAS_SERIAL_3 0 +#define USE_SERIAL_3_STDIN 0 +#define SERIAL_3_PB10_PB11 0 +#define SERIAL_3_PC10_PC11 1 +#define SERIAL_3_PD8_PD9 0 + +#define ao_gps_getchar ao_serial2_getchar +#define ao_gps_putchar ao_serial2_putchar +#define ao_gps_set_speed ao_serial2_set_speed +#define ao_gps_fifo (ao_stm_usart2.rx_fifo) + +#define HAS_EEPROM 1 +#define USE_INTERNAL_FLASH 1 +#define HAS_USB 1 +#define HAS_BEEP 0 +#define HAS_RADIO 1 +#define HAS_TELEMETRY 1 +#define HAS_APRS 1 +#define HAS_RADIO_RECV 0 + +#define HAS_SPI_1 1 +#define SPI_1_PA5_PA6_PA7 1 /* SD card */ +#define SPI_1_PB3_PB4_PB5 0 +#define SPI_1_PE13_PE14_PE15 0 +#define SPI_1_OSPEEDR STM_OSPEEDR_10MHz + +#define SPI_1_PORT (&stm_gpioa) +#define SPI_1_SCK_PIN 5 +#define SPI_1_MISO_PIN 6 +#define SPI_1_MOSI_PIN 7 + +#define HAS_SPI_2 1 +#define SPI_2_PB13_PB14_PB15 1 /* CC115L */ +#define SPI_2_PD1_PD3_PD4 0 +#define SPI_2_OSPEEDR STM_OSPEEDR_10MHz + +#define SPI_2_PORT (&stm_gpiob) +#define SPI_2_SCK_PIN 13 +#define SPI_2_MISO_PIN 14 +#define SPI_2_MOSI_PIN 15 + +#define HAS_I2C_1 0 +#define I2C_1_PB8_PB9 1 + +#define HAS_I2C_2 0 +#define I2C_2_PB10_PB11 1 + +#define PACKET_HAS_SLAVE 0 +#define PACKET_HAS_MASTER 0 + +#define LOW_LEVEL_DEBUG 0 + +#define LED_PORT_0_ENABLE STM_RCC_AHBENR_GPIOAEN +#define LED_PORT_0 (&stm_gpioa) +#define LED_PORT_0_MASK (0xff) +#define LED_PORT_0_SHIFT 0 +#define LED_PIN_RED 0 +#define LED_PIN_GREEN 2 +#define AO_LED_RED (1 << LED_PIN_RED) +#define AO_LED_GREEN (1 << LED_PIN_GREEN) + +#define LEDS_AVAILABLE (AO_LED_RED | AO_LED_GREEN) + +#define HAS_GPS 1 +#define HAS_FLIGHT 0 +#define HAS_ADC 0 +#define HAS_LOG 0 + +/* + * Telemetry monitoring + */ +#define HAS_MONITOR 0 +#define LEGACY_MONITOR 0 +#define HAS_MONITOR_PUT 0 +#define AO_MONITOR_LED AO_LED_GREEN + +/* + * Radio (cc115l) + */ + +/* gets pretty close to 434.550 */ + +#define AO_RADIO_CAL_DEFAULT 0x10b6a5 + +#define HAS_RADIO_POWER 1 + +#define AO_FEC_DEBUG 0 +#define AO_CC115L_SPI_CS_PORT (&stm_gpiob) +#define AO_CC115L_SPI_CS_PIN 12 +#define AO_CC115L_SPI_BUS AO_SPI_2_PB13_PB14_PB15 +#define AO_CC115L_SPI stm_spi2 + +#define AO_CC115L_FIFO_INT_GPIO_IOCFG CC115L_IOCFG2 +#define AO_CC115L_FIFO_INT_PORT (&stm_gpioa) +#define AO_CC115L_FIFO_INT_PIN (9) + +#define AO_CC115L_DONE_INT_GPIO_IOCFG CC115L_IOCFG0 +#define AO_CC115L_DONE_INT_PORT (&stm_gpioa) +#define AO_CC115L_DONE_INT_PIN (10) + +#define HAS_RADIO_AMP 1 + +/* + * Power amplifier (RFPA0133) + */ + +#define AO_PA_POWER_GPIO (&stm_gpiob) +#define AO_PA_POWER_PIN 1 +#define AO_PA_GAIN_8_GPIO (&stm_gpiob) +#define AO_PA_GAIN_8_PIN 10 +#define AO_PA_GAIN_16_GPIO (&stm_gpiob) +#define AO_PA_GAIN_16_PIN 11 + +/* + * SD card + */ + +#define AO_SDCARD_SPI_BUS AO_SPI_1_PA5_PA6_PA7 +#define AO_SDCARD_SPI_PORT SPI_1_PORT +#define AO_SDCARD_SPI_SCK_PIN SPI_1_SCK_PIN +#define AO_SDCARD_SPI_MISO_PIN SPI_1_MISO_PIN +#define AO_SDCARD_SPI_MOSI_PIN SPI_1_MOSI_PIN +#define AO_SDCARD_SPI_CS_PORT (&stm_gpioa) +#define AO_SDCARD_SPI_CS_PIN 4 +#define AO_SDCARD_SPI stm_spi1 + +#endif /* _AO_PINS_H_ */ diff --git a/src/telegps-v0.1/ao_telegps.c b/src/telegps-v0.1/ao_telegps.c new file mode 100644 index 00000000..68116bfb --- /dev/null +++ b/src/telegps-v0.1/ao_telegps.c @@ -0,0 +1,68 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <ao.h> +#include <ao_exti.h> +#include <ao_fat.h> + +uint16_t ao_flight_number = 1; + +int +main(void) +{ + ao_clock_init(); + +#if HAS_STACK_GUARD + ao_mpu_init(); +#endif + + ao_task_init(); +// ao_led_init(LEDS_AVAILABLE); +// ao_led_on(AO_LED_RED); + ao_timer_init(); + + + ao_spi_init(); + ao_dma_init(); + ao_exti_init(); + + ao_storage_init(); + + ao_serial_init(); + + ao_cmd_init(); + + ao_usb_init(); + ao_radio_init(); + + ao_fat_init(); + + ao_gps_init(); + ao_gps_report_mega_init(); + + ao_telemetry_init(); + ao_telemetry_set_interval(AO_SEC_TO_TICKS(1)); + ao_rdf_set(1); + +#if HAS_SAMPLE_PROFILE + ao_sample_profile_init(); +#endif + ao_config_init(); + + ao_start_scheduler(); + return 0; +} diff --git a/src/telelco-v0.1/Makefile b/src/telelco-v0.1/Makefile index d2702dd6..a4a83d02 100644 --- a/src/telelco-v0.1/Makefile +++ b/src/telelco-v0.1/Makefile @@ -61,7 +61,7 @@ ALTOS_SRC = \ ao_radio_cmac_cmd.c PRODUCT=TeleLCO-v0.1 -PRODUCT_DEF=-DMEGAMETRUM +PRODUCT_DEF=-DTELEMEGA IDPRODUCT=0x0023 CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) -Os -g diff --git a/src/telemega-v0.1/.gitignore b/src/telemega-v0.1/.gitignore new file mode 100644 index 00000000..e67759a2 --- /dev/null +++ b/src/telemega-v0.1/.gitignore @@ -0,0 +1,2 @@ +ao_product.h +telemega-*.elf diff --git a/src/megametrum-v0.1/Makefile b/src/telemega-v0.1/Makefile index a5fdcbb2..16393ea0 100644 --- a/src/megametrum-v0.1/Makefile +++ b/src/telemega-v0.1/Makefile @@ -95,16 +95,16 @@ ALTOS_SRC = \ $(SAMPLE_PROFILE) \ $(STACK_GUARD) -PRODUCT=MegaMetrum-v0.1 -PRODUCT_DEF=-DMEGAMETRUM +PRODUCT=TeleMega-v0.1 +PRODUCT_DEF=-DTELEMEGA IDPRODUCT=0x0023 CFLAGS = $(PRODUCT_DEF) $(STM_CFLAGS) $(PROFILE_DEF) $(SAMPLE_PROFILE_DEF) $(STACK_GUARD_DEF) -Os -g -PROGNAME=megametrum-v0.1 +PROGNAME=telemega-v0.1 PROG=$(PROGNAME)-$(VERSION).elf -SRC=$(ALTOS_SRC) ao_megametrum.c +SRC=$(ALTOS_SRC) ao_telemega.c OBJ=$(SRC:.c=.o) all: $(PROG) diff --git a/src/megametrum-v0.1/ao_pins.h b/src/telemega-v0.1/ao_pins.h index 4c645871..4c645871 100644 --- a/src/megametrum-v0.1/ao_pins.h +++ b/src/telemega-v0.1/ao_pins.h diff --git a/src/megametrum-v0.1/ao_megametrum.c b/src/telemega-v0.1/ao_telemega.c index fbdab64a..fbdab64a 100644 --- a/src/megametrum-v0.1/ao_megametrum.c +++ b/src/telemega-v0.1/ao_telemega.c diff --git a/src/megametrum-v0.1/stlink-pins b/src/telemega-v0.1/stlink-pins index 3bd16d0b..390f8e5d 100644 --- a/src/megametrum-v0.1/stlink-pins +++ b/src/telemega-v0.1/stlink-pins @@ -42,6 +42,14 @@ NRST 2 5 SWDIO 3 4 SWCLK 4 2 +Altus Metrum standard 4-pin connector to MegaMetrum v0.1 misc connector: + + AMstd MM v0.1 +gnd 1 1 +nrst 2 2 +swdio 3 8 +swclk 4 9 + MegaAccel: Jumpers diff --git a/src/teleshield-v0.1/ao_pins.h b/src/teleshield-v0.1/ao_pins.h index 701e25fc..30239afc 100644 --- a/src/teleshield-v0.1/ao_pins.h +++ b/src/teleshield-v0.1/ao_pins.h @@ -46,6 +46,7 @@ #define AO_LED_RED 1 #define AO_LED_GREEN 2 #define AO_MONITOR_LED AO_LED_RED + #define AO_BT_LED AO_LED_GREEN #define LEDS_AVAILABLE (AO_LED_RED|AO_LED_GREEN) #define SPI_CS_ON_P1 1 #define SPI_CS_ON_P0 0 @@ -60,6 +61,7 @@ #define LEGACY_MONITOR 0 #define HAS_RSSI 0 #define HAS_AES 0 + #define HAS_RADIO 1 #endif #if DBG_ON_P1 diff --git a/src/test/.gitignore b/src/test/.gitignore index 8d79d168..8f74c348 100644 --- a/src/test/.gitignore +++ b/src/test/.gitignore @@ -7,6 +7,7 @@ ao_gps_test ao_gps_test_skytraq ao_convert_test ao_convert_pa_test +ao_fat_test ao_fec_test ao_flight_test_mm ao_flight_test_noisy_accel diff --git a/src/test/Makefile b/src/test/Makefile index fccc7937..a62b59c5 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -2,7 +2,7 @@ vpath % ..:../core:../drivers:../util:../micropeak PROGS=ao_flight_test ao_flight_test_baro ao_flight_test_accel ao_flight_test_noisy_accel ao_flight_test_mm \ ao_gps_test ao_gps_test_skytraq ao_convert_test ao_convert_pa_test ao_fec_test \ - ao_aprs_test ao_micropeak_test + ao_aprs_test ao_micropeak_test ao_fat_test INCS=ao_kalman.h ao_ms5607.h ao_log.h ao_data.h altitude-pa.h altitude.h @@ -30,7 +30,7 @@ ao_flight_test_accel: ao_flight_test.c ao_host.h ao_flight.c ao_sample.c ao_kal cc $(CFLAGS) -o $@ -DFORCE_ACCEL=1 ao_flight_test.c ao_flight_test_mm: ao_flight_test.c ao_host.h ao_flight.c ao_sample.c ao_kalman.c $(INCS) - cc -DMEGAMETRUM=1 $(CFLAGS) -o $@ $< -lm + cc -DTELEMEGA=1 $(CFLAGS) -o $@ $< -lm ao_gps_test: ao_gps_test.c ao_gps_sirf.c ao_gps_print.c ao_host.h cc $(CFLAGS) -o $@ $< @@ -64,3 +64,6 @@ check: ao_fec_test ao_flight_test ao_flight_test_baro run-tests ao_micropeak_test: ao_micropeak_test.c ao_microflight.c ao_kalman.h cc $(CFLAGS) -o $@ ao_micropeak_test.c -lm + +ao_fat_test: ao_fat_test.c ao_fat.c ao_bufio.c + cc $(CFLAGS) -o $@ ao_fat_test.c -lssl diff --git a/src/test/ao_aprs_test.c b/src/test/ao_aprs_test.c index 3b31f2d3..dd5eac4d 100644 --- a/src/test/ao_aprs_test.c +++ b/src/test/ao_aprs_test.c @@ -107,7 +107,7 @@ int main(int argc, char **argv) } void -ao_radio_send_lots(ao_radio_fill_func fill) +ao_radio_send_aprs(ao_radio_fill_func fill) { int16_t len; uint8_t done = 0; diff --git a/src/test/ao_fat_test.c b/src/test/ao_fat_test.c new file mode 100644 index 00000000..d1309024 --- /dev/null +++ b/src/test/ao_fat_test.c @@ -0,0 +1,565 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <getopt.h> +#include <math.h> +#include <unistd.h> +#include <fcntl.h> +#include <openssl/md5.h> + +#define AO_FAT_TEST + +void +ao_mutex_get(uint8_t *mutex) +{ +} + +void +ao_mutex_put(uint8_t *mutex) +{ +} + +void +ao_panic(uint8_t panic) +{ + printf ("panic %d\n", panic); + abort(); +} + +#define AO_PANIC_BUFIO 15 + +#define ao_cmd_success 0 + +uint8_t ao_cmd_status; +uint32_t ao_cmd_lex_u32; + +void +ao_cmd_decimal() +{ +} + +#define ao_cmd_register(x) + +struct ao_cmds { + void (*func)(void); + const char *help; +}; + +int fs_fd; + +uint64_t total_reads, total_writes; + +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ + ++total_reads; + lseek(fs_fd, block * 512, 0); + return read(fs_fd, data, 512) == 512; +} + +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ + ++total_writes; + lseek(fs_fd, block * 512, 0); + return write(fs_fd, data, 512) == 512; +} + +struct fs_param { + int fat; + int blocks; +} fs_params[] = { + { .fat = 16, .blocks = 16384 }, + { .fat = 32, .blocks = 16384 }, + { .fat = 16, .blocks = 65536 }, + { .fat = 32, .blocks = 65536 }, + { .fat = 16, .blocks = 1048576 }, + { .fat = 32, .blocks = 1048576 }, + { .fat = 0, .blocks = 0 }, +}; + +char *fs = "fs.fat"; +struct fs_param *param; + +void +ao_sdcard_init(void) +{ + char cmd[1024]; + + if (fs_fd) { + close(fs_fd); + fs_fd = 0; + } + snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -F %d -C %s %d", + fs, param->fat, fs, param->blocks); + if (system (cmd) != 0) { + fprintf(stderr, "'%s' failed\n", cmd); + exit(1); + } + fs_fd = open(fs, 2); + if (fs_fd < 0) { + perror (fs); + exit(1); + } +} + +#include "ao_bufio.c" +void +check_bufio(char *where) +{ + int b; + + for (b = 0; b < AO_NUM_BUF; b++) { + if (ao_bufio[b].busy) { + printf ("%s: buffer %d busy. block %d seqno %u\n", + where, b, ao_bufio[b].block, ao_bufio[b].seqno & 0xffff); + abort(); + } + } +} + + +void +check_fat(void); + +#include "ao_fat.c" + +/* Get the next cluster entry in the chain */ +static cluster_t +ao_fat_entry_raw_read(cluster_t cluster, uint8_t fat) +{ + sector_t sector; + cluster_offset_t offset; + uint8_t *buf; + cluster_t ret; + + if (fat32) + cluster <<= 2; + else + cluster <<= 1; + sector = cluster >> SECTOR_SHIFT; + offset = cluster & SECTOR_MASK; + buf = _ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector); + if (!buf) + return 0; + if (fat32) + ret = get_u32(buf + offset); + else + ret = get_u16(buf + offset); + _ao_fat_sector_put(buf, 0); + return ret; +} + +void +dump_fat(void) +{ + int e; + + printf ("\n **** FAT ****\n\n"); + for (e = 0; e < number_cluster; e++) { + if ((e & 0xf) == 0x0) + printf ("%04x: ", e); + if (fat32) + printf (" %08x", ao_fat_entry_raw_read(e, 0)); + else + printf (" %04x", ao_fat_entry_raw_read(e, 0)); + if ((e & 0xf) == 0xf) + putchar ('\n'); + } + if (e & 0xf) + putchar('\n'); +} + +void +fat_list(void) +{ + dirent_t entry = 0; + struct ao_fat_dirent dirent; + + printf (" **** Root directory ****\n"); + while (ao_fat_readdir(&entry, &dirent)) { + printf ("%04x: %-8.8s.%-3.3s %02x %04x %d\n", + entry, + dirent.name, + dirent.name + 8, + dirent.attr, + dirent.cluster, + dirent.size); + } + + printf (" **** End of root directory ****\n"); +} + +void +fatal(char *msg, ...) +{ +// dump_fat(); +// fat_list(); + + va_list l; + va_start(l, msg); + vfprintf(stderr, msg, l); + va_end(l); + + abort(); +} + +void +check_fat(void) +{ + cluster_t e; + int f; + + for (e = 0; e < number_cluster; e++) { + cluster_t v = ao_fat_entry_raw_read(e, 0); + for (f = 1; f < number_fat; f++) { + cluster_t o = ao_fat_entry_raw_read(e, f); + if (o != v) + fatal ("fats differ at %08x (0 %08x %d %08x)\n", e, v, f, o); + } + } +} + +cluster_t +check_file(dirent_t dent, cluster_t first_cluster, dirent_t *used) +{ + cluster_t clusters = 0; + cluster_t cluster; + + if (!first_cluster) + return 0; + + for (cluster = first_cluster; + fat32 ? !AO_FAT_IS_LAST_CLUSTER(cluster) : !AO_FAT_IS_LAST_CLUSTER16(cluster); + cluster = ao_fat_entry_raw_read(cluster, 0)) + { + if (!_ao_fat_cluster_valid(cluster)) + fatal("file %d: invalid cluster %08x\n", dent, cluster); + if (used[cluster]) + fatal("file %d: duplicate cluster %08x also in file %d\n", dent, cluster, used[cluster]-1); + used[cluster] = dent; + clusters++; + } + return clusters; +} + +void +check_fs(void) +{ + dirent_t r; + cluster_t cluster, chain; + dirent_t *used; + uint8_t *dent; + + check_fat(); + + used = calloc(sizeof (dirent_t), number_cluster); + + for (r = 0; (dent = _ao_fat_root_get(r)); r++) { + cluster_t clusters; + offset_t size; + cluster_t first_cluster; + char name[11]; + + if (!dent) + fatal("cannot map dent %d\n", r); + memcpy(name, dent+0, 11); + first_cluster = get_u16(dent + 0x1a); + if (fat32) + first_cluster |= (cluster_t) get_u16(dent + 0x14) << 16; + size = get_u32(dent + 0x1c); + _ao_fat_root_put(dent, r, 0); + + if (name[0] == AO_FAT_DENT_END) { + break; + } + + clusters = check_file(r + 1, first_cluster, used); + if (size == 0) { + if (clusters != 0) + fatal("file %d: zero sized, but %d clusters\n", clusters); + } else { + if (size > clusters * bytes_per_cluster) + fatal("file %d: size %u beyond clusters %d (%u)\n", + r, size, clusters, clusters * bytes_per_cluster); + if (size <= (clusters - 1) * bytes_per_cluster) + fatal("file %d: size %u too small clusters %d (%u)\n", + r, size, clusters, clusters * bytes_per_cluster); + } + } + if (!fat32) { + for (; r < root_entries; r++) { + uint8_t *dent = _ao_fat_root_get(r); + if (!dent) + fatal("cannot map dent %d\n", r); + if (dent[0] != AO_FAT_DENT_END) + fatal("found non-zero dent past end %d\n", r); + _ao_fat_root_put(dent, r, 0); + } + } else { + check_file((dirent_t) -1, root_cluster, used); + } + + for (cluster = 0; cluster < 2; cluster++) { + chain = ao_fat_entry_raw_read(cluster, 0); + + if (fat32) { + if ((chain & 0xffffff8) != 0xffffff8) + fatal("cluster %d: not marked busy\n", cluster); + } else { + if ((chain & 0xfff8) != 0xfff8) + fatal("cluster %d: not marked busy\n", cluster); + } + } + for (; cluster < number_cluster; cluster++) { + chain = ao_fat_entry_raw_read(cluster, 0); + + if (chain != 0) { + if (used[cluster] == 0) + fatal("cluster %d: marked busy, but not in any file\n", cluster); + } else { + if (used[cluster] != 0) + fatal("cluster %d: marked free, but found in file %d\n", cluster, used[cluster]-1); + } + } +} + +#define NUM_FILES 100 +#define LINES_FILE 500000 + +uint32_t sizes[NUM_FILES]; + +unsigned char md5[NUM_FILES][MD5_DIGEST_LENGTH]; + +void +micro_test_fs(void) +{ + int8_t fd; + char name[] = "FOO "; + char buf[512]; + int len; + + printf ("write once\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + ao_fat_write(fd, "hello world\n", 12); + ao_fat_close(fd); + } + + printf ("read first\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + len = ao_fat_read(fd, buf, sizeof(buf)); + write (1, buf, len); + ao_fat_close(fd); + } + + printf ("write again\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + ao_fat_write(fd, "hi\n", 3); + ao_fat_close(fd); + } + + printf ("read again\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + len = ao_fat_read(fd, buf, sizeof(buf)); + write (1, buf, len); + ao_fat_close(fd); + } + + printf ("write 3\n"); + if ((fd = ao_fat_creat(name)) >= 0) { + int l; + char c; + + for (l = 0; l < 10; l++) { + for (c = ' '; c < '~'; c++) + ao_fat_write(fd, &c, 1); + c = '\n'; + ao_fat_write(fd, &c, 1); + } + ao_fat_close(fd); + } + + printf ("read 3\n"); + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0) + write (1, buf, len); + ao_fat_close(fd); + } + + check_fs(); + printf ("all done\n"); +} + + +void +short_test_fs(void) +{ + int len; + int8_t fd; + char buf[345]; + + if ((fd = ao_fat_open("HELLO TXT",AO_FAT_OPEN_READ)) >= 0) { + printf ("File contents for HELLO.TXT\n"); + while ((len = ao_fat_read(fd, buf, sizeof(buf)))) + write(1, buf, len); + ao_fat_close(fd); + } + + if ((fd = ao_fat_creat("NEWFILE TXT")) >= 0) { + printf ("Create new file\n"); + for (len = 0; len < 2; len++) + ao_fat_write(fd, "hello, world!\n", 14); + ao_fat_seek(fd, 0, AO_FAT_SEEK_SET); + printf ("read new file\n"); + while ((len = ao_fat_read(fd, buf, sizeof (buf)))) + write (1, buf, len); + ao_fat_close(fd); + } + + check_fs(); +} + +void +long_test_fs(void) +{ + char name[12]; + int id; + MD5_CTX ctx; + unsigned char md5_check[MD5_DIGEST_LENGTH]; + char buf[337]; + int len; + int8_t fd; + uint64_t total_file_size = 0; + + total_reads = total_writes = 0; + + printf (" **** Creating %d files\n", NUM_FILES); + + memset(sizes, '\0', sizeof (sizes)); + for (id = 0; id < NUM_FILES; id++) { + sprintf(name, "D%07dTXT", id); + if ((id % ((NUM_FILES+49)/50)) == 0) { + printf ("."); fflush(stdout); + } + if ((fd = ao_fat_creat(name)) >= 0) { + int j; + char line[64]; + check_bufio("file created"); + MD5_Init(&ctx); + for (j = 0; j < LINES_FILE; j++) { + int len, ret; + sprintf (line, "Hello, world %d %d\r\n", id, j); + len = strlen(line); + ret = ao_fat_write(fd, line, len); + if (ret <= 0) + break; + total_file_size += ret; + MD5_Update(&ctx, line, ret); + sizes[id] += ret; + if (ret != len) + printf ("write failed %d\n", ret); + } + ao_fat_close(fd); + MD5_Final(&md5[id][0], &ctx); + check_bufio("file written"); + } + } + + printf ("\n **** Write IO: read %llu write %llu data sectors %llu\n", total_reads, total_writes, (total_file_size + 511) / 512); + + check_bufio("all files created"); + printf (" **** All done creating files\n"); + check_fs(); + + total_reads = total_writes = 0; + + printf (" **** Comparing %d files\n", NUM_FILES); + + for (id = 0; id < NUM_FILES; id++) { + uint32_t size; + sprintf(name, "D%07dTXT", id); + size = 0; + if ((id % ((NUM_FILES+49)/50)) == 0) { + printf ("."); fflush(stdout); + } + if ((fd = ao_fat_open(name, AO_FAT_OPEN_READ)) >= 0) { + MD5_Init(&ctx); + while ((len = ao_fat_read(fd, buf, sizeof(buf))) > 0) { + MD5_Update(&ctx, buf, len); + size += len; + } + ao_fat_close(fd); + MD5_Final(md5_check, &ctx); + if (size != sizes[id]) + fatal("file %d: size differs %d written %d read\n", + id, sizes[id], size); + if (memcmp(md5_check, &md5[id][0], sizeof (md5_check)) != 0) + fatal ("file %d: checksum failed\n", id); + check_bufio("file shown"); + } + } + printf ("\n **** Read IO: read %llu write %llu\n", total_reads, total_writes); +} + +char *params[] = { + "-F 16 -C %s 16384", + "-F 32 -C %s 16384", + "-F 16 -C %s 65536", + "-F 32 -C %s 65536", + "-F 16 -C %s 1048576", + "-F 32 -C %s 1048576", + NULL +}; + +void +do_test(void (*test)(void)) +{ + ao_fat_init(); + + check_bufio("top"); + _ao_fat_setup(); + + check_fs(); + check_bufio("after setup"); + (*test)(); + ao_fat_unmount(); +} + +int +main(int argc, char **argv) +{ + int p; + + if (argv[1]) + fs = argv[1]; + + for (p = 0; fs_params[p].fat; p++) { + param = &fs_params[p]; + + do_test(micro_test_fs); + do_test(short_test_fs); + do_test(long_test_fs); + } + unlink (fs); + + return 0; +} diff --git a/src/test/ao_flight_test.c b/src/test/ao_flight_test.c index cdd1f236..99bed7ee 100644 --- a/src/test/ao_flight_test.c +++ b/src/test/ao_flight_test.c @@ -35,7 +35,7 @@ #define AO_MS_TO_SPEED(ms) ((int16_t) ((ms) * 16)) #define AO_MSS_TO_ACCEL(mss) ((int16_t) ((mss) * 16)) -#if MEGAMETRUM +#if TELEMEGA #define AO_ADC_NUM_SENSE 6 #define HAS_MS5607 1 #define HAS_MPU6000 1 @@ -195,7 +195,7 @@ struct ao_cmds { #define ao_xmemcmp(d,s,c) memcmp(d,s,c) #define AO_NEED_ALTITUDE_TO_PRES 1 -#if MEGAMETRUM +#if TELEMEGA #include "ao_convert_pa.c" #include <ao_ms5607.h> struct ao_ms5607_prom ms5607_prom; @@ -333,7 +333,7 @@ ao_insert(void) #else double accel = 0.0; #endif -#if MEGAMETRUM +#if TELEMEGA double height; ao_ms5607_convert(&ao_data_static.ms5607_raw, &ao_data_static.ms5607_cooked); @@ -373,7 +373,7 @@ ao_insert(void) if (!ao_summary) { printf("%7.2f height %8.2f accel %8.3f " -#if MEGAMETRUM +#if TELEMEGA "roll %8.3f angle %8.3f qangle %8.3f " "accel_x %8.3f accel_y %8.3f accel_z %8.3f gyro_x %8.3f gyro_y %8.3f gyro_z %8.3f " #endif @@ -381,7 +381,7 @@ ao_insert(void) time, height, accel, -#if MEGAMETRUM +#if TELEMEGA ao_mpu6000_gyro(ao_sample_roll_angle) / 100.0, ao_mpu6000_gyro(ao_sample_angle) / 100.0, ao_sample_qangle, @@ -555,7 +555,7 @@ int32(uint8_t *bytes, int off) static int log_format; -#if MEGAMETRUM +#if TELEMEGA static double ao_vec_norm(double x, double y, double z) @@ -774,7 +774,7 @@ ao_sleep(void *wchan) for (;;) { if (ao_records_read > 2 && ao_flight_state == ao_flight_startup) { -#if MEGAMETRUM +#if TELEMEGA ao_data_static.mpu6000 = ao_ground_mpu6000; #else ao_data_static.adc.accel = ao_flight_ground_accel; @@ -800,8 +800,8 @@ ao_sleep(void *wchan) if (words[nword] == NULL) break; } -#if MEGAMETRUM - if (log_format == AO_LOG_FORMAT_MEGAMETRUM && nword == 30 && strlen(words[0]) == 1) { +#if TELEMEGA + if (log_format == AO_LOG_FORMAT_TELEMEGA && nword == 30 && strlen(words[0]) == 1) { int i; struct ao_ms5607_value value; @@ -885,7 +885,7 @@ ao_sleep(void *wchan) continue; } #else - if (nword == 4 && log_format != AO_LOG_FORMAT_MEGAMETRUM) { + if (nword == 4 && log_format != AO_LOG_FORMAT_TELEMEGA) { type = words[0][0]; tick = strtoul(words[1], NULL, 16); a = strtoul(words[2], NULL, 16); @@ -1002,7 +1002,7 @@ ao_sleep(void *wchan) if (type != 'F' && !ao_flight_started) continue; -#if MEGAMETRUM +#if TELEMEGA (void) a; (void) b; #else diff --git a/telemetrum.inf b/telemetrum.inf index b4a84b97..386dd286 100755 --- a/telemetrum.inf +++ b/telemetrum.inf @@ -24,24 +24,88 @@ DefaultDestDir = 12 %TeleMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000B, AltusMetrumSerial
%TeleDongle% = AltusMetrum.Install, USB\VID_FFFE&PID_000C, AltusMetrumSerial
%TeleTerra% = AltusMetrum.Install, USB\VID_FFFE&PID_000D, AltusMetrumSerial
+%TeleBT% = AltusMetrum.Install, USB\VID_FFFE&PID_000e, AltusMetrumSerial
+%TeleLaunch% = AltusMetrum.Install, USB\VID_FFFE&PID_000f, AltusMetrumSerial
+%TeleLCO% = AltusMetrum.Install, USB\VID_FFFE&PID_0010, AltusMetrumSerial
+%TeleScience% = AltusMetrum.Install, USB\VID_FFFE&PID_0011, AltusMetrumSerial
+%TelePyro% = AltusMetrum.Install, USB\VID_FFFE&PID_0012, AltusMetrumSerial
+%TeleShield% = AltusMetrum.Install, USB\VID_FFFE&PID_0013, AltusMetrumSerial
+%TeleMega% = AltusMetrum.Install, USB\VID_FFFE&PID_0023, AltusMetrumSerial
+%MegaDongle = AltusMetrum.Install, USB\VID_FFFE&PID_0024, AltusMetrumSerial
+%TeleGPS% = AltusMetrum.Install, USB\VID_FFFE&PID_0025, AltusMetrumSerial
+%AltusMetrum26% = AltusMetrum.Install, USB\VID_FFFE&PID_0026, AltusMetrumSerial
+%AltusMetrum27% = AltusMetrum.Install, USB\VID_FFFE&PID_0027, AltusMetrumSerial
+%AltusMetrum28% = AltusMetrum.Install, USB\VID_FFFE&PID_0028, AltusMetrumSerial
+%AltusMetrum29% = AltusMetrum.Install, USB\VID_FFFE&PID_0029, AltusMetrumSerial
+%AltusMetrum2a% = AltusMetrum.Install, USB\VID_FFFE&PID_002a, AltusMetrumSerial
+%AltusMetrum2b% = AltusMetrum.Install, USB\VID_FFFE&PID_002b, AltusMetrumSerial
+%AltusMetrum2c% = AltusMetrum.Install, USB\VID_FFFE&PID_002c, AltusMetrumSerial
[Models.NTx86]
%AltusMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000A, AltusMetrumSerial
%TeleMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000B, AltusMetrumSerial
%TeleDongle% = AltusMetrum.Install, USB\VID_FFFE&PID_000C, AltusMetrumSerial
%TeleTerra% = AltusMetrum.Install, USB\VID_FFFE&PID_000D, AltusMetrumSerial
+%TeleBT% = AltusMetrum.Install, USB\VID_FFFE&PID_000e, AltusMetrumSerial
+%TeleLaunch% = AltusMetrum.Install, USB\VID_FFFE&PID_000f, AltusMetrumSerial
+%TeleLCO% = AltusMetrum.Install, USB\VID_FFFE&PID_0010, AltusMetrumSerial
+%TeleScience% = AltusMetrum.Install, USB\VID_FFFE&PID_0011, AltusMetrumSerial
+%TelePyro% = AltusMetrum.Install, USB\VID_FFFE&PID_0012, AltusMetrumSerial
+%TeleShield% = AltusMetrum.Install, USB\VID_FFFE&PID_0013, AltusMetrumSerial
+%TeleMega% = AltusMetrum.Install, USB\VID_FFFE&PID_0023, AltusMetrumSerial
+%MegaDongle = AltusMetrum.Install, USB\VID_FFFE&PID_0024, AltusMetrumSerial
+%TeleGPS% = AltusMetrum.Install, USB\VID_FFFE&PID_0025, AltusMetrumSerial
+%AltusMetrum26% = AltusMetrum.Install, USB\VID_FFFE&PID_0026, AltusMetrumSerial
+%AltusMetrum27% = AltusMetrum.Install, USB\VID_FFFE&PID_0027, AltusMetrumSerial
+%AltusMetrum28% = AltusMetrum.Install, USB\VID_FFFE&PID_0028, AltusMetrumSerial
+%AltusMetrum29% = AltusMetrum.Install, USB\VID_FFFE&PID_0029, AltusMetrumSerial
+%AltusMetrum2a% = AltusMetrum.Install, USB\VID_FFFE&PID_002a, AltusMetrumSerial
+%AltusMetrum2b% = AltusMetrum.Install, USB\VID_FFFE&PID_002b, AltusMetrumSerial
+%AltusMetrum2c% = AltusMetrum.Install, USB\VID_FFFE&PID_002c, AltusMetrumSerial
[Models.NTamd64]
%AltusMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000A, AltusMetrumSerial
%TeleMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000B, AltusMetrumSerial
%TeleDongle% = AltusMetrum.Install, USB\VID_FFFE&PID_000C, AltusMetrumSerial
%TeleTerra% = AltusMetrum.Install, USB\VID_FFFE&PID_000D, AltusMetrumSerial
+%TeleBT% = AltusMetrum.Install, USB\VID_FFFE&PID_000e, AltusMetrumSerial
+%TeleLaunch% = AltusMetrum.Install, USB\VID_FFFE&PID_000f, AltusMetrumSerial
+%TeleLCO% = AltusMetrum.Install, USB\VID_FFFE&PID_0010, AltusMetrumSerial
+%TeleScience% = AltusMetrum.Install, USB\VID_FFFE&PID_0011, AltusMetrumSerial
+%TelePyro% = AltusMetrum.Install, USB\VID_FFFE&PID_0012, AltusMetrumSerial
+%TeleShield% = AltusMetrum.Install, USB\VID_FFFE&PID_0013, AltusMetrumSerial
+%TeleMega% = AltusMetrum.Install, USB\VID_FFFE&PID_0023, AltusMetrumSerial
+%MegaDongle = AltusMetrum.Install, USB\VID_FFFE&PID_0024, AltusMetrumSerial
+%TeleGPS% = AltusMetrum.Install, USB\VID_FFFE&PID_0025, AltusMetrumSerial
+%AltusMetrum26% = AltusMetrum.Install, USB\VID_FFFE&PID_0026, AltusMetrumSerial
+%AltusMetrum27% = AltusMetrum.Install, USB\VID_FFFE&PID_0027, AltusMetrumSerial
+%AltusMetrum28% = AltusMetrum.Install, USB\VID_FFFE&PID_0028, AltusMetrumSerial
+%AltusMetrum29% = AltusMetrum.Install, USB\VID_FFFE&PID_0029, AltusMetrumSerial
+%AltusMetrum2a% = AltusMetrum.Install, USB\VID_FFFE&PID_002a, AltusMetrumSerial
+%AltusMetrum2b% = AltusMetrum.Install, USB\VID_FFFE&PID_002b, AltusMetrumSerial
+%AltusMetrum2c% = AltusMetrum.Install, USB\VID_FFFE&PID_002c, AltusMetrumSerial
[Models.NTia64]
%AltusMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000A, AltusMetrumSerial
%TeleMetrum% = AltusMetrum.Install, USB\VID_FFFE&PID_000B, AltusMetrumSerial
%TeleDongle% = AltusMetrum.Install, USB\VID_FFFE&PID_000C, AltusMetrumSerial
%TeleTerra% = AltusMetrum.Install, USB\VID_FFFE&PID_000D, AltusMetrumSerial
+%TeleBT% = AltusMetrum.Install, USB\VID_FFFE&PID_000e, AltusMetrumSerial
+%TeleLaunch% = AltusMetrum.Install, USB\VID_FFFE&PID_000f, AltusMetrumSerial
+%TeleLCO% = AltusMetrum.Install, USB\VID_FFFE&PID_0010, AltusMetrumSerial
+%TeleScience% = AltusMetrum.Install, USB\VID_FFFE&PID_0011, AltusMetrumSerial
+%TelePyro% = AltusMetrum.Install, USB\VID_FFFE&PID_0012, AltusMetrumSerial
+%TeleShield% = AltusMetrum.Install, USB\VID_FFFE&PID_0013, AltusMetrumSerial
+%TeleMega% = AltusMetrum.Install, USB\VID_FFFE&PID_0023, AltusMetrumSerial
+%MegaDongle = AltusMetrum.Install, USB\VID_FFFE&PID_0024, AltusMetrumSerial
+%TeleGPS% = AltusMetrum.Install, USB\VID_FFFE&PID_0025, AltusMetrumSerial
+%AltusMetrum26% = AltusMetrum.Install, USB\VID_FFFE&PID_0026, AltusMetrumSerial
+%AltusMetrum27% = AltusMetrum.Install, USB\VID_FFFE&PID_0027, AltusMetrumSerial
+%AltusMetrum28% = AltusMetrum.Install, USB\VID_FFFE&PID_0028, AltusMetrumSerial
+%AltusMetrum29% = AltusMetrum.Install, USB\VID_FFFE&PID_0029, AltusMetrumSerial
+%AltusMetrum2a% = AltusMetrum.Install, USB\VID_FFFE&PID_002a, AltusMetrumSerial
+%AltusMetrum2b% = AltusMetrum.Install, USB\VID_FFFE&PID_002b, AltusMetrumSerial
+%AltusMetrum2c% = AltusMetrum.Install, USB\VID_FFFE&PID_002c, AltusMetrumSerial
;----------------------------------------------------------------------------
; Installation sections
|