first cut at turnon scripts for EasyTimer v2
[fw/altos] / altoslib / AltosConfigData.java
index 05fc2031c3bc2cd067d7d8ce2eab7eab626373d5..6b980be6b641dd784e06c020d07a743eea036b0c 100644 (file)
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  */
 
-package org.altusmetrum.altoslib_11;
+package org.altusmetrum.altoslib_14;
 
 import java.util.*;
 import java.text.*;
 import java.util.concurrent.*;
 
-public class AltosConfigData implements Iterable<String> {
+/* Don't change the field names in this structure; they're part of all .eeprom files */
+public class AltosConfigData {
 
        /* Version information */
        public String   manufacturer;
@@ -33,9 +34,7 @@ public class AltosConfigData implements Iterable<String> {
        public int      log_space;
        public String   version;
        public int      altitude_32;
-
-       /* Strings returned */
-       public LinkedList<String>       __lines;
+       public int      config_major, config_minor;
 
        /* Config information */
        /* HAS_FLIGHT*/
@@ -55,6 +54,8 @@ public class AltosConfigData implements Iterable<String> {
 
        /* HAS_ACCEL */
        public int      accel_cal_plus, accel_cal_minus;
+       private int     accel_cal_plus_cooked, accel_cal_minus_cooked;
+       private boolean accel_cal_adjusted;
        public int      pad_orientation;
 
        /* HAS_LOG */
@@ -77,16 +78,26 @@ public class AltosConfigData implements Iterable<String> {
        public int              aprs_interval;
        public int              aprs_ssid;
        public int              aprs_format;
+       public int              aprs_offset;
 
        /* HAS_BEEP */
        public int              beep;
 
+       /* HAS_RADIO_10MW */
+       public int              radio_10mw;
+
+       public int              report_feet;
+
+       /* HAS_GPS_MOSAIC */
+       public int              gps_receiver;
+
        /* Storage info replies */
        public int      storage_size;
        public int      storage_erase_unit;
 
        /* Log listing replies */
        public int      stored_flight;
+       public AltosEepromFlight[] flights;
 
        /* HAS_TRACKER */
        public int      tracker_motion;
@@ -96,14 +107,13 @@ public class AltosConfigData implements Iterable<String> {
        public int      accel_zero_along, accel_zero_across, accel_zero_through;
 
        /* ms5607 data */
-       public int      ms5607_reserved;
-       public int      ms5607_sens;
-       public int      ms5607_off;
-       public int      ms5607_tcs;
-       public int      ms5607_tco;
-       public int      ms5607_tref;
-       public int      ms5607_tempsens;
-       public int      ms5607_crc;
+       AltosMs5607     ms5607;
+
+       public AltosMs5607 ms5607() {
+               if (ms5607 == null)
+                       ms5607 = new AltosMs5607();
+               return ms5607;
+       }
 
        public static String get_string(String line, String label) throws  ParseException {
                if (line.startsWith(label)) {
@@ -142,21 +152,17 @@ public class AltosConfigData implements Iterable<String> {
                throw new ParseException("mismatch", 0);
        }
 
-       public Iterator<String> iterator() {
-               return __lines.iterator();
-       }
-
        public int log_space() {
-               if (log_space > 0)
+               if (log_space != AltosLib.MISSING)
                        return log_space;
 
-               if (storage_size > 0) {
+               if (storage_size != AltosLib.MISSING) {
                        int     space = storage_size;
 
-                       if (storage_erase_unit > 0 && use_flash_for_config())
+                       if (storage_erase_unit != AltosLib.MISSING && use_flash_for_config())
                                space -= storage_erase_unit;
 
-                       if (space > 0)
+                       if (space != AltosLib.MISSING)
                                return space;
                }
                return 0;
@@ -165,7 +171,7 @@ public class AltosConfigData implements Iterable<String> {
        public int log_available() {
                switch (log_format) {
                case AltosLib.AO_LOG_FORMAT_TINY:
-                       if (stored_flight == 0)
+                       if (flights == null)
                                return 1;
                        return 0;
                case AltosLib.AO_LOG_FORMAT_TELEMETRY:
@@ -178,10 +184,10 @@ public class AltosConfigData implements Iterable<String> {
                        int     log_space = log_space();
                        int     log_used;
 
-                       if (stored_flight <= 0)
+                       if (flights == null)
                                log_used = 0;
                        else
-                               log_used = stored_flight * log_max;
+                               log_used = flights.length * log_max;
                        int     log_avail;
 
                        if (log_used >= log_space)
@@ -193,7 +199,47 @@ public class AltosConfigData implements Iterable<String> {
                }
        }
 
+       public int invert_accel_value(int value) {
+               if (value == AltosLib.MISSING)
+                       return AltosLib.MISSING;
+
+               switch (log_format) {
+               case AltosLib.AO_LOG_FORMAT_FULL:
+                       return 0x7fff - value;
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA_OLD:
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA:
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA_3:
+                       return 4095 - value;
+               case AltosLib.AO_LOG_FORMAT_TELEMETRUM:
+                       /*
+                        * TeleMetrum v2 and later use the same log format, but
+                        * have different accelerometers. This is the only place
+                        * it matters in altoslib.
+                        */
+                       if (product.startsWith("TeleMetrum-v2"))
+                               return 4095 - value;
+                       /* fall through */
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA_4:
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA_5:
+               case AltosLib.AO_LOG_FORMAT_TELEMEGA_6:
+               case AltosLib.AO_LOG_FORMAT_EASYMEGA_2:
+               case AltosLib.AO_LOG_FORMAT_EASYMEGA_3:
+               case AltosLib.AO_LOG_FORMAT_EASYMOTOR:
+                       /* ADXL375 */
+                       return -value;
+               case AltosLib.AO_LOG_FORMAT_EASYTIMER_2:
+                       /* BMI088 */
+                       return -value;
+               default:
+                       if (product.startsWith("EasyTimer-"))
+                               return -value;
+                       return AltosLib.MISSING;
+               }
+       }
+
        public boolean has_monitor_battery() {
+               if (product == null)
+                       return false;
                if (product.startsWith("TeleBT"))
                        return true;
                return false;
@@ -237,8 +283,6 @@ public class AltosConfigData implements Iterable<String> {
        }
 
        public void reset() {
-               __lines = new LinkedList<String>();
-
                manufacturer = null;
                product = null;
                serial = AltosLib.MISSING;
@@ -246,6 +290,8 @@ public class AltosConfigData implements Iterable<String> {
                log_format = AltosLib.AO_LOG_FORMAT_UNKNOWN;
                log_space = AltosLib.MISSING;
                version = "unknown";
+               config_major = AltosLib.MISSING;
+               config_minor = AltosLib.MISSING;
 
                main_deploy = AltosLib.MISSING;
                apogee_delay = AltosLib.MISSING;
@@ -259,9 +305,12 @@ public class AltosConfigData implements Iterable<String> {
                radio_setting = AltosLib.MISSING;
                telemetry_rate = AltosLib.MISSING;
 
+               accel_cal_plus_cooked = AltosLib.MISSING;
+               accel_cal_minus_cooked = AltosLib.MISSING;
                accel_cal_plus = AltosLib.MISSING;
                accel_cal_minus = AltosLib.MISSING;
                pad_orientation = AltosLib.MISSING;
+               accel_cal_adjusted = false;
 
                flight_log_max = AltosLib.MISSING;
                log_fixed = AltosLib.MISSING;
@@ -269,31 +318,110 @@ public class AltosConfigData implements Iterable<String> {
 
                aes_key = null;
 
-               pyro = 0;
-               npyro = 0;
+               pyro = AltosLib.MISSING;
+               npyro = AltosLib.MISSING;
                pyros = null;
                pyro_firing_time = AltosLib.MISSING;
 
                aprs_interval = AltosLib.MISSING;
                aprs_ssid = AltosLib.MISSING;
                aprs_format = AltosLib.MISSING;
+               aprs_offset = AltosLib.MISSING;
 
                beep = AltosLib.MISSING;
 
+               radio_10mw = AltosLib.MISSING;
+
+               report_feet = AltosLib.MISSING;
+
+               gps_receiver = AltosLib.MISSING;
+
                tracker_motion = AltosLib.MISSING;
                tracker_interval = AltosLib.MISSING;
 
                storage_size = AltosLib.MISSING;
                storage_erase_unit = AltosLib.MISSING;
-               stored_flight = AltosLib.MISSING;
+               stored_flight = 0;
+               flights = null;
 
                accel_zero_along = AltosLib.MISSING;
                accel_zero_across = AltosLib.MISSING;
                accel_zero_through = AltosLib.MISSING;
        }
 
+       /* Return + accel calibration relative to a specific pad orientation */
+       public int accel_cal_plus(int pad_orientation) {
+               adjust_accel_cal();
+               if (!accel_cal_adjusted)
+                       return AltosLib.MISSING;
+
+               switch (pad_orientation) {
+               case AltosLib.AO_PAD_ORIENTATION_ANTENNA_UP:
+               case AltosLib.AO_PAD_ORIENTATION_WORDS_UPRIGHT:
+               case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_UP:
+                       return accel_cal_plus_cooked;
+               case AltosLib.AO_PAD_ORIENTATION_ANTENNA_DOWN:
+               case AltosLib.AO_PAD_ORIENTATION_WORDS_UPSIDEDOWN:
+               case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_DOWN:
+                       return invert_accel_value(accel_cal_minus_cooked);
+               default:
+                       return AltosLib.MISSING;
+               }
+       }
+
+       /* Return - accel calibration relative to a specific pad orientation */
+       public int accel_cal_minus(int pad_orientation) {
+               adjust_accel_cal();
+               if (!accel_cal_adjusted)
+                       return AltosLib.MISSING;
+
+               switch (pad_orientation) {
+               case AltosLib.AO_PAD_ORIENTATION_ANTENNA_UP:
+               case AltosLib.AO_PAD_ORIENTATION_WORDS_UPRIGHT:
+               case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_UP:
+                       return accel_cal_minus_cooked;
+               case AltosLib.AO_PAD_ORIENTATION_ANTENNA_DOWN:
+               case AltosLib.AO_PAD_ORIENTATION_WORDS_UPSIDEDOWN:
+               case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_DOWN:
+                       return invert_accel_value(accel_cal_plus_cooked);
+               default:
+                       return AltosLib.MISSING;
+               }
+       }
+
+       /* Once we have all of the values from the config data, compute the
+        * accel cal values relative to Antenna Up orientation.
+        */
+       private void adjust_accel_cal() {
+               if (!accel_cal_adjusted &&
+                   product != null &&
+                   pad_orientation != AltosLib.MISSING &&
+                   accel_cal_plus != AltosLib.MISSING &&
+                   accel_cal_minus != AltosLib.MISSING)
+               {
+                       switch (pad_orientation) {
+                       case AltosLib.AO_PAD_ORIENTATION_ANTENNA_UP:
+                       case AltosLib.AO_PAD_ORIENTATION_WORDS_UPRIGHT:
+                       case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_UP:
+                               accel_cal_plus_cooked = accel_cal_plus;
+                               accel_cal_minus_cooked = accel_cal_minus;
+                               accel_cal_adjusted = true;
+                               break;
+                       case AltosLib.AO_PAD_ORIENTATION_ANTENNA_DOWN:
+                       case AltosLib.AO_PAD_ORIENTATION_WORDS_UPSIDEDOWN:
+                       case AltosLib.AO_PAD_ORIENTATION_BIG_PARTS_DOWN:
+                               accel_cal_plus_cooked = invert_accel_value(accel_cal_minus);
+                               accel_cal_minus_cooked = invert_accel_value(accel_cal_plus);
+                               accel_cal_adjusted = true;
+                               break;
+                       default:
+                               break;
+                       }
+               }
+       }
+
        public void parse_line(String line) {
-               __lines.add(line);
+
                /* Version replies */
                try { manufacturer = get_string(line, "manufacturer"); } catch (Exception e) {}
                try { product = get_string(line, "product"); } catch (Exception e) {}
@@ -306,17 +434,31 @@ public class AltosConfigData implements Iterable<String> {
 
                /* Version also contains MS5607 info, which we ignore here */
 
-               try { ms5607_reserved = get_int(line, "ms5607 reserved:"); } catch (Exception e) {}
-               try { ms5607_sens = get_int(line, "ms5607 sens:"); } catch (Exception e) {}
-               try { ms5607_off = get_int(line, "ms5607 off:"); } catch (Exception e) {}
-               try { ms5607_tcs = get_int(line, "ms5607 tcs:"); } catch (Exception e) {}
-               try { ms5607_tco = get_int(line, "ms5607 tco:"); } catch (Exception e) {}
-               try { ms5607_tref = get_int(line, "ms5607 tref:"); } catch (Exception e) {}
-               try { ms5607_tempsens = get_int(line, "ms5607 tempsens:"); } catch (Exception e) {}
-               try { ms5607_crc = get_int(line, "ms5607 crc:"); } catch (Exception e) {}
+               try { ms5607().reserved = get_int(line, "ms5607 reserved:"); } catch (Exception e) {}
+               try { ms5607().sens = get_int(line, "ms5607 sens:"); } catch (Exception e) {}
+               try { ms5607().off = get_int(line, "ms5607 off:"); } catch (Exception e) {}
+               try { ms5607().tcs = get_int(line, "ms5607 tcs:"); } catch (Exception e) {}
+               try { ms5607().tco = get_int(line, "ms5607 tco:"); } catch (Exception e) {}
+               try { ms5607().tref = get_int(line, "ms5607 tref:"); } catch (Exception e) {}
+               try { ms5607().tempsens = get_int(line, "ms5607 tempsens:"); } catch (Exception e) {}
+               try { ms5607().crc = get_int(line, "ms5607 crc:"); } catch (Exception e) {}
 
                /* Config show replies */
 
+               try {
+                       if (line.startsWith("Config version")) {
+                               String[] bits = line.split("\\s+");
+                               if (bits.length >= 3) {
+                                       String[] cfg = bits[2].split("\\.");
+
+                                       if (cfg.length >= 2) {
+                                               config_major = Integer.parseInt(cfg[0]);
+                                               config_minor = Integer.parseInt(cfg[1]);
+                                       }
+                               }
+                       }
+               } catch (Exception e) {}
+
                /* HAS_FLIGHT */
                try { main_deploy = get_int(line, "Main deploy:"); } catch (Exception e) {}
                try { apogee_delay = get_int(line, "Apogee delay:"); } catch (Exception e) {}
@@ -344,6 +486,7 @@ public class AltosConfigData implements Iterable<String> {
                                if (bits.length >= 6) {
                                        accel_cal_plus = Integer.parseInt(bits[3]);
                                        accel_cal_minus = Integer.parseInt(bits[5]);
+                                       accel_cal_adjusted = false;
                                }
                        }
                } catch (Exception e) {}
@@ -365,7 +508,7 @@ public class AltosConfigData implements Iterable<String> {
                        pyros = new AltosPyro[npyro];
                        pyro = 0;
                } catch (Exception e) {}
-               if (npyro > 0) {
+               if (npyro != AltosLib.MISSING) {
                        try {
                                AltosPyro p = new AltosPyro(pyro, line);
                                if (pyro < npyro)
@@ -378,10 +521,18 @@ public class AltosConfigData implements Iterable<String> {
                try { aprs_interval = get_int(line, "APRS interval:"); } catch (Exception e) {}
                try { aprs_ssid = get_int(line, "APRS SSID:"); } catch (Exception e) {}
                try { aprs_format = get_int(line, "APRS format:"); } catch (Exception e) {}
+               try { aprs_offset = get_int(line, "APRS offset:"); } catch (Exception e) {}
 
                /* HAS_BEEP */
                try { beep = get_int(line, "Beeper setting:"); } catch (Exception e) {}
 
+               /* HAS_RADIO_10MW */
+               try { radio_10mw = get_int(line, "Radio 10mw limit:"); } catch (Exception e) {}
+
+               try { report_feet = get_int(line, "Report in feet:"); } catch (Exception e) {}
+
+               try { gps_receiver = get_int(line, "GPS receiver:"); } catch (Exception e) {}
+
                /* HAS_TRACKER */
                try {
                        int[] values = get_values(line, "Tracker setting:");
@@ -394,11 +545,36 @@ public class AltosConfigData implements Iterable<String> {
                try { storage_erase_unit = get_int(line, "Storage erase unit:"); } catch (Exception e) {}
 
                /* Log listing replies */
-               try { get_int(line, "flight"); stored_flight++; }  catch (Exception e) {}
+               try {
+                       int flight = get_int(line, "flight");
+                       String[] tokens = line.split("\\s+");
+                       if (tokens.length >= 6) {
+                               int     start = -1, end = -1;
+                               try {
+                                       if (tokens[2].equals("start"))
+                                               start = AltosParse.parse_hex(tokens[3]);
+                                       if (tokens[4].equals("end"))
+                                               end = AltosParse.parse_hex(tokens[5]);
+                                       if (flight != 0 && start >= 0 && end > 0) {
+                                               int len;
+                                               if (flights == null)
+                                                       len = 0;
+                                               else
+                                                       len = flights.length;
+                                               AltosEepromFlight [] new_flights = new AltosEepromFlight[len + 1];
+                                               for (int i = 0; i < len; i++)
+                                                       new_flights[i] = flights[i];
+                                               new_flights[len] = new AltosEepromFlight(flight, start, end);
+                                               flights = new_flights;
+                                               stored_flight = flights.length;
+                                       }
+                               } catch (ParseException pe) { System.out.printf("Parse error %s\n", pe.toString()); }
+                       }
+               }  catch (Exception e) {}
 
                /* HAS_GYRO */
                try {
-                       if (line.startsWith("IMU call along")) {
+                       if (line.startsWith("IMU cal along")) {
                                String[] bits = line.split("\\s+");
                                if (bits.length >= 8) {
                                        accel_zero_along = Integer.parseInt(bits[3]);
@@ -429,23 +605,22 @@ public class AltosConfigData implements Iterable<String> {
        }
 
        public boolean has_frequency() {
-               return radio_frequency >= 0 || radio_setting >= 0 || radio_channel >= 0;
+               return radio_frequency != AltosLib.MISSING || radio_setting != AltosLib.MISSING || radio_channel != AltosLib.MISSING;
        }
 
        public boolean has_telemetry_rate() {
-               return telemetry_rate >= 0;
+               return telemetry_rate != AltosLib.MISSING;
        }
 
        public void set_frequency(double freq) {
                int     frequency = radio_frequency;
                int     setting = radio_setting;
 
-               if (frequency > 0) {
+               if (frequency != AltosLib.MISSING) {
                        radio_frequency = (int) Math.floor (freq * 1000 + 0.5);
                        radio_channel = AltosLib.MISSING;
-               } else if (setting > 0) {
-                       radio_setting =AltosConvert.radio_frequency_to_setting(freq,
-                                                                                   radio_calibration);
+               } else if (setting != AltosLib.MISSING) {
+                       radio_setting =AltosConvert.radio_frequency_to_setting(freq, radio_calibration);
                        radio_channel = AltosLib.MISSING;
                } else {
                        radio_channel = AltosConvert.radio_frequency_to_channel(freq);
@@ -456,12 +631,12 @@ public class AltosConfigData implements Iterable<String> {
                int     channel = radio_channel;
                int     setting = radio_setting;
 
-               if (radio_frequency < 0 && channel < 0 && setting < 0)
+               if (radio_frequency == AltosLib.MISSING && channel == AltosLib.MISSING && setting == AltosLib.MISSING)
                        return AltosLib.MISSING;
 
-               if (channel < 0)
+               if (channel == AltosLib.MISSING)
                        channel = 0;
-               if (setting < 0)
+               if (setting == AltosLib.MISSING)
                        setting = 0;
 
                return AltosConvert.radio_to_frequency(radio_frequency,
@@ -475,6 +650,10 @@ public class AltosConfigData implements Iterable<String> {
                        return false;
                if (product.startsWith("TeleMetrum-v2"))
                        return false;
+               if (product.startsWith("TeleMetrum-v3"))
+                       return false;
+               if (product.startsWith("TeleMetrum-v4"))
+                       return true;
                if (product.startsWith("EasyMega"))
                        return false;
                return true;
@@ -482,70 +661,136 @@ public class AltosConfigData implements Iterable<String> {
 
 
        public boolean mma655x_inverted() throws AltosUnknownProduct {
-               if (product.startsWith("EasyMega-v1"))
-                       return false;
-               if (product.startsWith("TeleMetrum-v2"))
-                       return true;
-               if (product.startsWith("TeleMega-v2"))
-                       return false;
-               if (product.startsWith("TeleMega-v1"))
-                       return false;
+               if (product != null) {
+                       if (product.startsWith("EasyMega-v1"))
+                               return false;
+                       if (product.startsWith("TeleMetrum-v2"))
+                               return true;
+                       if (product.startsWith("TeleMega-v2"))
+                               return false;
+                       if (product.startsWith("TeleMega-v1"))
+                               return false;
+               }
+               throw new AltosUnknownProduct(product);
+       }
+
+       public boolean adxl375_inverted() throws AltosUnknownProduct {
+               if (product != null) {
+                       if (product.startsWith("EasyMega-v2"))
+                               return true;
+                       if (product.startsWith("TeleMetrum-v3"))
+                               return true;
+                       if (product.startsWith("TeleMetrum-v4"))
+                               return true;
+                       if (product.startsWith("TeleMega-v4"))
+                               return true;
+                       if (product.startsWith("TeleMega-v5"))
+                               return true;
+                       if (product.startsWith("TeleMega-v6"))
+                               return true;
+                       if (product.startsWith("EasyMotor-v2"))
+                               return true;
+                       if (product.startsWith("EasyMotor-v3"))
+                               return true;
+               }
+               throw new AltosUnknownProduct(product);
+       }
+
+       public int adxl375_axis() throws AltosUnknownProduct {
+               if (product != null) {
+                       if (product.startsWith("EasyMega-v2"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("TeleMetrum-v3"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("TeleMetrum-v4"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("TeleMega-v4"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("TeleMega-v5"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("TeleMega-v6"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("EasyMotor-v2"))
+                               return AltosAdxl375.X_AXIS;
+                       if (product.startsWith("EasyMotor-v3"))
+                               return AltosAdxl375.X_AXIS;
+
+               }
                throw new AltosUnknownProduct(product);
        }
 
        public void get_values(AltosConfigValues source) throws AltosConfigDataException {
 
                /* HAS_FLIGHT */
-               if (main_deploy >= 0)
+               if (main_deploy != AltosLib.MISSING)
                        main_deploy = source.main_deploy();
-               if (apogee_delay >= 0)
+               if (apogee_delay != AltosLib.MISSING)
                        apogee_delay = source.apogee_delay();
-               if (apogee_lockout >= 0)
+               if (apogee_lockout != AltosLib.MISSING)
                        apogee_lockout = source.apogee_lockout();
 
                /* HAS_RADIO */
                if (has_frequency())
                        set_frequency(source.radio_frequency());
-               if (radio_enable >= 0)
+               if (radio_enable != AltosLib.MISSING)
                        radio_enable = source.radio_enable();
                if (callsign != null)
                        callsign = source.callsign();
-               if (telemetry_rate >= 0)
+               if (telemetry_rate != AltosLib.MISSING)
                        telemetry_rate = source.telemetry_rate();
 
                /* HAS_ACCEL */
-               if (pad_orientation >= 0)
+               if (pad_orientation != AltosLib.MISSING)
                        pad_orientation = source.pad_orientation();
 
+               if (accel_cal_plus_cooked != AltosLib.MISSING)
+                       accel_cal_plus_cooked = source.accel_cal_plus();
+
+               if (accel_cal_minus_cooked != AltosLib.MISSING)
+                       accel_cal_minus_cooked = source.accel_cal_minus();
+
                /* HAS_LOG */
-               if (flight_log_max >= 0)
+               if (flight_log_max != AltosLib.MISSING)
                        flight_log_max = source.flight_log_max();
 
                /* HAS_IGNITE */
-               if (ignite_mode >= 0)
+               if (ignite_mode != AltosLib.MISSING)
                        ignite_mode = source.ignite_mode();
 
                /* AO_PYRO_NUM */
-               if (npyro > 0)
+               if (npyro != AltosLib.MISSING)
                        pyros = source.pyros();
-               if (pyro_firing_time >= 0)
+               if (pyro_firing_time != AltosLib.MISSING)
                        pyro_firing_time = source.pyro_firing_time();
 
                /* HAS_APRS */
-               if (aprs_interval >= 0)
+               if (aprs_interval != AltosLib.MISSING)
                        aprs_interval = source.aprs_interval();
-               if (aprs_ssid >= 0)
+               if (aprs_ssid != AltosLib.MISSING)
                        aprs_ssid = source.aprs_ssid();
-               if (aprs_format >= 0)
+               if (aprs_format != AltosLib.MISSING)
                        aprs_format = source.aprs_format();
+               if (aprs_offset != AltosLib.MISSING)
+                       aprs_offset = source.aprs_offset();
 
                /* HAS_BEEP */
-               if (beep >= 0)
+               if (beep != AltosLib.MISSING)
                        beep = source.beep();
+
+               /* HAS_RADIO_10MW */
+               if (radio_10mw != AltosLib.MISSING)
+                       radio_10mw = source.radio_10mw();
+
+               if (report_feet != AltosLib.MISSING)
+                       report_feet = source.report_feet();
+
+               if (gps_receiver != AltosLib.MISSING)
+                       gps_receiver = source.gps_receiver();
+
                /* HAS_TRACKER */
-               if (tracker_motion >= 0)
+               if (tracker_motion != AltosLib.MISSING)
                        tracker_motion = source.tracker_motion();
-               if (tracker_interval >= 0)
+               if (tracker_interval != AltosLib.MISSING)
                        tracker_interval = source.tracker_interval();
        }
 
@@ -565,7 +810,7 @@ public class AltosConfigData implements Iterable<String> {
                if (log_space() == 0)
                        max_enabled = false;
 
-               if (log_fixed > 0)
+               if (log_fixed != AltosLib.MISSING)
                        max_enabled = false;
 
                switch (log_format) {
@@ -573,19 +818,21 @@ public class AltosConfigData implements Iterable<String> {
                        max_enabled = false;
                        break;
                default:
-                       if (stored_flight > 0)
+                       if (flights != null)
                                max_enabled = false;
                        break;
                }
 
                dest.set_flight_log_max_enabled(max_enabled);
                dest.set_radio_enable(radio_enable);
-               dest.set_flight_log_max_limit(log_space() / 1024);
+               dest.set_flight_log_max_limit(log_space() >> 10, storage_erase_unit >> 10);
                dest.set_flight_log_max(flight_log_max);
                dest.set_ignite_mode(ignite_mode);
                dest.set_pad_orientation(pad_orientation);
+               dest.set_accel_cal(accel_cal_plus(AltosLib.AO_PAD_ORIENTATION_ANTENNA_UP),
+                                  accel_cal_minus(AltosLib.AO_PAD_ORIENTATION_ANTENNA_UP));
                dest.set_callsign(callsign);
-               if (npyro > 0)
+               if (npyro != AltosLib.MISSING)
                        dest.set_pyros(pyros);
                else
                        dest.set_pyros(null);
@@ -593,7 +840,11 @@ public class AltosConfigData implements Iterable<String> {
                dest.set_aprs_interval(aprs_interval);
                dest.set_aprs_ssid(aprs_ssid);
                dest.set_aprs_format(aprs_format);
+               dest.set_aprs_offset(aprs_offset);
                dest.set_beep(beep);
+               dest.set_radio_10mw(radio_10mw);
+               dest.set_report_feet(report_feet);
+               dest.set_gps_receiver(gps_receiver);
                dest.set_tracker_motion(tracker_motion);
                dest.set_tracker_interval(tracker_interval);
        }
@@ -609,104 +860,130 @@ public class AltosConfigData implements Iterable<String> {
        public void save(AltosLink link, boolean remote) throws InterruptedException, TimeoutException {
 
                /* HAS_FLIGHT */
-               if (main_deploy >= 0)
+               if (main_deploy != AltosLib.MISSING)
                        link.printf("c m %d\n", main_deploy);
-               if (apogee_delay >= 0)
+               if (apogee_delay != AltosLib.MISSING)
                        link.printf("c d %d\n", apogee_delay);
-               if (apogee_lockout >= 0)
+               if (apogee_lockout != AltosLib.MISSING)
                        link.printf("c L %d\n", apogee_lockout);
 
                /* HAS_RADIO */
                if (has_frequency()) {
-                       boolean has_frequency = radio_frequency >= 0;
-                       boolean has_setting = radio_setting > 0;
+                       boolean has_frequency = radio_frequency != AltosLib.MISSING;
+                       boolean has_setting = radio_setting != AltosLib.MISSING;
                        double frequency = frequency();
                        link.set_radio_frequency(frequency,
                                                        has_frequency,
                                                        has_setting,
                                                        radio_calibration);
                        /* When remote, reset the dongle frequency at the same time */
-                       if (remote) {
-                               link.flush_output();
+                       if (remote && frequency != link.frequency) {
                                link.stop_remote();
                                link.set_radio_frequency(frequency);
-                               link.flush_output();
                                link.start_remote();
                        }
                }
 
-               if (telemetry_rate >= 0) {
+               if (telemetry_rate != AltosLib.MISSING) {
                        link.printf("c T %d\n", telemetry_rate);
-                       if (remote) {
-                               link.flush_output();
+                       if (remote && telemetry_rate != link.telemetry_rate) {
                                link.stop_remote();
                                link.set_telemetry_rate(telemetry_rate);
-                               link.flush_output();
                                link.start_remote();
                        }
                }
 
                if (callsign != null) {
                        link.printf("c c %s\n", callsign);
-                       if (remote) {
-                               link.flush_output();
+                       if (remote && !callsign.equals(link.callsign)) {
+                               System.out.printf("changing link callsign from %s to %s\n", link.callsign, callsign);
                                link.stop_remote();
                                link.set_callsign(callsign);
-                               link.flush_output();
                                link.start_remote();
                        }
                }
 
-               if (radio_enable >= 0)
+               if (radio_enable != AltosLib.MISSING)
                        link.printf("c e %d\n", radio_enable);
 
                /* HAS_ACCEL */
-               /* UI doesn't support accel cal */
-               if (pad_orientation >= 0)
+               /* set orientation first so that we know how to set the accel cal */
+               if (pad_orientation != AltosLib.MISSING)
                        link.printf("c o %d\n", pad_orientation);
+               int plus = accel_cal_plus(pad_orientation);
+               int minus = accel_cal_minus(pad_orientation);
+               if (plus != AltosLib.MISSING && minus != AltosLib.MISSING) {
+                       if (plus < 0)
+                               plus = 65536 + plus;
+                       if (minus < 0)
+                               minus = 65536 + minus;
+                       if (accel_zero_along != AltosLib.MISSING &&
+                           accel_zero_across != AltosLib.MISSING &&
+                           accel_zero_through != AltosLib.MISSING)
+                               link.printf("c a %d %d %d %d %d\n",
+                                           plus, minus,
+                                           accel_zero_along,
+                                           accel_zero_across,
+                                           accel_zero_through);
+                       else
+                               link.printf("c a %d %d\n", plus, minus);
+               }
 
                /* HAS_LOG */
-               if (flight_log_max != 0)
+               if (flight_log_max != 0 && flight_log_max != AltosLib.MISSING)
                        link.printf("c l %d\n", flight_log_max);
 
                /* HAS_IGNITE */
-               if (ignite_mode >= 0)
+               if (ignite_mode != AltosLib.MISSING)
                        link.printf("c i %d\n", ignite_mode);
 
                /* HAS_AES */
                /* UI doesn't support AES key config */
 
                /* AO_PYRO_NUM */
-               if (npyro > 0) {
+               if (npyro != AltosLib.MISSING) {
                        for (int p = 0; p < pyros.length; p++) {
                                link.printf("c P %s\n",
                                                   pyros[p].toString());
                        }
                }
-               if (pyro_firing_time >= 0)
+               if (pyro_firing_time != AltosLib.MISSING)
                        link.printf("c I %d\n", (int) (pyro_firing_time * 100.0 + 0.5));
 
                /* HAS_APRS */
-               if (aprs_interval >= 0)
+               if (aprs_interval != AltosLib.MISSING)
                        link.printf("c A %d\n", aprs_interval);
-               if (aprs_ssid >= 0)
+               if (aprs_ssid != AltosLib.MISSING)
                        link.printf("c S %d\n", aprs_ssid);
-               if (aprs_format >= 0)
+               if (aprs_format != AltosLib.MISSING)
                        link.printf("c C %d\n", aprs_format);
+               if (aprs_offset != AltosLib.MISSING)
+                       link.printf("c O %d\n", aprs_offset);
 
                /* HAS_BEEP */
-               if (beep >= 0)
+               if (beep != AltosLib.MISSING)
                        link.printf("c b %d\n", beep);
 
+               /* HAS_RADIO_10MW */
+               if (radio_10mw != AltosLib.MISSING)
+                       link.printf("c p %d\n", radio_10mw);
+
+               if (report_feet != AltosLib.MISSING)
+                       link.printf("c u %d\n", report_feet);
+
+               /* HAS_GPS_MOSAIC */
+               if (gps_receiver != AltosLib.MISSING)
+                       link.printf("c g %d\n", gps_receiver);
+
                /* HAS_TRACKER */
-               if (tracker_motion >= 0 && tracker_interval >= 0)
+               if (tracker_motion != AltosLib.MISSING && tracker_interval != AltosLib.MISSING)
                        link.printf("c t %d %d\n", tracker_motion, tracker_interval);
 
                /* HAS_GYRO */
                /* UI doesn't support accel cal */
 
                link.printf("c w\n");
-               link.flush_output();
+               read_link(link, "Saved");
        }
 
        public AltosConfigData(AltosLink link) throws InterruptedException, TimeoutException {
@@ -722,5 +999,6 @@ public class AltosConfigData implements Iterable<String> {
                        read_link(link, "done");
                        break;
                }
+               adjust_accel_cal();
        }
 }