void
CirclingComputer::TurnRate(CirclingInfo &circling_info,
                           const NMEAInfo &basic,
                           const FlyingState &flight)
{
  if (!basic.time_available || !flight.flying || !turn_rate_delta_time.IsDefined()) {
    circling_info.turn_rate = Angle::Zero();
    circling_info.turn_rate_heading = Angle::Zero();
    circling_info.turn_rate_smoothed = Angle::Zero();
    circling_info.turn_rate_heading_smoothed = Angle::Zero();
    last_track = basic.track;
    last_heading = basic.attitude.heading;

    // initialize turn_rate_delta_time on first call
    if (basic.time_available)
      turn_rate_delta_time.Update(basic.time, 1./3., 10);
    return;
  }

  const auto dt = turn_rate_delta_time.Update(basic.time, 1./3., 10);
  if (dt < 0) {
    circling_info.turn_rate = Angle::Zero();
    circling_info.turn_rate_heading = Angle::Zero();
    circling_info.turn_rate_smoothed = Angle::Zero();
    circling_info.turn_rate_heading_smoothed = Angle::Zero();
    last_track = basic.track;
    last_heading = basic.attitude.heading;
    return;
  }

  if (dt > 0) {
    circling_info.turn_rate =
      (basic.track - last_track).AsDelta() / dt;
    circling_info.turn_rate_heading =
      (basic.attitude.heading - last_heading).AsDelta() / dt;

    // JMW limit rate to 50 deg per second otherwise a big spike
    // will cause spurious lock on circling for a long time
    Angle turn_rate = Clamp(circling_info.turn_rate,
                            Angle::Degrees(-50), Angle::Degrees(50));

    // Make the turn rate more smooth using the LowPassFilter
    auto smoothed = LowPassFilter(circling_info.turn_rate_smoothed.Native(),
                                  turn_rate.Native(), 0.3);
    circling_info.turn_rate_smoothed = Angle::Native(smoothed);

    // Makes smoothing of heading turn rate
    turn_rate = Clamp(circling_info.turn_rate_heading,
                      Angle::Degrees(-50), Angle::Degrees(50));
    // Make the heading turn rate more smooth using the LowPassFilter
    smoothed = LowPassFilter(circling_info.turn_rate_heading_smoothed.Native(),
                             turn_rate.Native(), 0.3);
    circling_info.turn_rate_heading_smoothed = Angle::Native(smoothed);

    last_track = basic.track;
    last_heading = basic.attitude.heading;
  }
}
Exemple #2
0
int
main(int argc, char *argv[])
{
   ACTempData acTemps[MinPerDay];
   
   /*
    * check for the correct number of command line arguments.  If incorrect
    * provide a simple usage message to the assist the user
    *
    */
	if( argc != 3 ) {
      printf("\nUsage: %s inputFile outputFile \n\n", argv[0]);
      return -1;
	}
	
   if( !ReadTempDataFromFile(acTemps, argv[1]) ) {
      printf("Could not read from input file %s\n", argv[1]);
      return -1;
   }

   RemoveErroneousData(acTemps);
   LowPassFilter(acTemps);
   TrendExtraction(acTemps);
   
   if( !WriteTempDataToFile(acTemps, argv[2]) ) {
      printf("Could not write to output file %s\n", argv[2]);
      return -1;
   }

	return 0;
}
uint8_t ADXL345_Poll()
{
	uint8_t LSB, MSB;
	
	twi(TWI_START);
	TWDR=ADXL345_ADDR|I2C_WRITE;
	twi(TWI_TRANSMIT);
	TWDR=0x32;
	twi(TWI_TRANSMIT);
	twi(TWI_RESTART);
	TWDR=ADXL345_ADDR|I2C_READ;
	twi(TWI_TRANSMIT);
	twi(TWI_RECEIVE_ACK);
	LSB=TWDR;
	twi(TWI_RECEIVE_ACK);
	MSB=TWDR;
	p_k_y.value = k_y.value;
	k_y.value = LowPassFilter((-0.0032) * (float)(MSB << 8 | LSB), 1);
	k_y.value += coeffs[ADXL345_OFFY].value;
	k_y.value *= coeffs[ADXL345_SCY].value;
	
	twi(TWI_RECEIVE_ACK);
	LSB=TWDR;
	twi(TWI_RECEIVE_ACK);
	MSB=TWDR;
	p_k_x.value = k_x.value;
	k_x.value = LowPassFilter((-0.0032) * (float)(MSB << 8 | LSB), 2);
	k_x.value += coeffs[ADXL345_OFFX].value;
	k_x.value *= coeffs[ADXL345_SCX].value;
	
	twi(TWI_RECEIVE_ACK);
	LSB=TWDR;
	twi(TWI_RECEIVE_NACK);
	MSB=TWDR;
	twi(TWI_STOP);
	p_k_z.value = k_z.value;
	k_z.value = LowPassFilter((0.0032) * (float)(MSB << 8 | LSB), 3);
	k_z.value += coeffs[ADXL345_OFFZ].value;
	k_z.value *= coeffs[ADXL345_SCZ].value;
	
	return 0;
}
Exemple #4
0
//
// Sollfarh / Dolphin Speed calculator
//
void SpeedToFly(NMEA_INFO *Basic, DERIVED_INFO *Calculated) {


    if (((AutoMcMode == amcFinalGlide) || (AutoMcMode == amcFinalAndClimb)) 
            && DoOptimizeRoute() && Calculated->NextAltitude > 0.) {

        // Special case for Conical end of Speed section
        int Type = -1;
        double ConeSlope = 0.0;
        LockTaskData();
        if (ValidTaskPoint(ActiveWayPoint)) {
            GetTaskSectorParameter(ActiveWayPoint, &Type, NULL);
            ConeSlope = Task[ActiveWayPoint].PGConeSlope;
        }
        UnlockTaskData();
        if (Type == CONE && ConeSlope > 0.0) {
            double VOpt = GlidePolar::FindSpeedForSlope(ConeSlope);
            double eqMC = GlidePolar::EquMC(VOpt);
            if(eqMC <= MACCREADY ) {
                Calculated->VOpt = VOpt;
                return;
            }
        }
    }

    double HeadWind = 0;
    if (Calculated->FinalGlide && ValidTaskPoint(ActiveWayPoint)) {
        // according to MC theory STF take account of wind only if on final Glide
        // TODO : for the future add config parameter for always use wind.
        if (Calculated->HeadWind != -999) {
            HeadWind = Calculated->HeadWind;
        }
    }

    // this is IAS for best Ground Glide ratio acounting current air mass ( wind / Netto vario )
    double VOptnew = GlidePolar::STF(MACCREADY, Calculated->NettoVario, HeadWind);

    // apply cruises efficiency factor.
    VOptnew *= CRUISE_EFFICIENCY;
    
    if (Calculated->NettoVario > MACCREADY) {
        // this air mass is better than maccready, so don't fly at speed less than minimum sink speed adjusted for load factor
        double n = fabs((Basic->AccelerationAvailable) ? Basic->AccelZ : Calculated->Gload);
        VOptnew = max(VOptnew, GlidePolar::Vminsink() * sqrt(n));
    } else {
        // never fly at speed less than min sink speed
        VOptnew = max(VOptnew, GlidePolar::Vminsink());
    }

    // use low pass filter for avoid big jump of value.
    Calculated->VOpt = LowPassFilter(Calculated->VOpt, VOptnew, 0.6);
}
fixed
UpdateGR(fixed gr, fixed leg_distance, fixed height_above_leg,
         fixed filter_factor)
{
  if (!positive(leg_distance))
    return gr;

  fixed glideangle = height_above_leg / leg_distance;
  if (gr != INVALID_GR)
    glideangle = LowPassFilter(fixed_one / gr, glideangle, filter_factor);

  if (fabs(glideangle) > fixed_one / INVALID_GR)
    gr = LimitGR(fixed_one / glideangle);
  else
    gr = INVALID_GR;

  return gr;
}
fixed
UpdateLD(fixed ld, fixed leg_distance, fixed height_above_leg,
         fixed filter_factor)
{
  if (!positive(leg_distance))
    return ld;

  fixed glideangle = height_above_leg / leg_distance;
  if (ld != fixed(INVALID_GR))
    glideangle = LowPassFilter(fixed_one / ld, glideangle, filter_factor);

  if (fabs(glideangle) > fixed_one / INVALID_GR)
    ld = LimitLD(fixed_one / glideangle);
  else
    ld = fixed(INVALID_GR);

  return ld;
}
Exemple #7
0
double
UpdateLD(double LD, double d, double h, double filter_factor)
{
  double glideangle;
  if (LD != 0) {
    glideangle = 1.0 / LD;
  } else {
    glideangle = 1.0;
  }
  if (d != 0) {
    glideangle = LowPassFilter(1.0 / LD, h / d, filter_factor);
    if (fabs(glideangle) > 1.0 / INVALID_GR) {
      LD = LimitLD(1.0 / glideangle);
    } else {
      LD = INVALID_GR;
    }
  }
  return LD;
}
Exemple #8
0
fixed
UpdateLD(fixed LD, fixed leg_distance, fixed height_above_leg,
         fixed filter_factor)
{
  if (!positive(leg_distance))
    return LD;

  fixed glideangle;
  if (LD != fixed_zero)
    glideangle = fixed_one / LD;
  else
    glideangle = fixed_one;

  glideangle = LowPassFilter(fixed_one / LD, height_above_leg / leg_distance,
                             filter_factor);

  if (fabs(glideangle) > fixed_one / INVALID_GR)
    LD = LimitLD(fixed_one / glideangle);
  else
    LD = fixed(INVALID_GR);

  return LD;
}
inline void
GlideComputerAirData::LastThermalStats(const MoreData &basic,
                                       DerivedInfo &calculated,
                                       bool last_circling)
{
  if (calculated.circling || !last_circling ||
      !positive(calculated.climb_start_time))
    return;

  fixed duration = calculated.cruise_start_time - calculated.climb_start_time;
  if (duration < THERMAL_TIME_MIN)
    return;

  fixed gain =
    calculated.cruise_start_altitude_te - calculated.climb_start_altitude_te;

  if (!positive(gain))
    return;

  bool was_defined = calculated.last_thermal.IsDefined();

  calculated.last_thermal.start_time = calculated.climb_start_time;
  calculated.last_thermal.end_time = calculated.cruise_start_time;
  calculated.last_thermal.gain = gain;
  calculated.last_thermal.duration = duration;
  calculated.last_thermal.CalculateLiftRate();

  if (!was_defined)
    calculated.last_thermal_average_smooth =
        calculated.last_thermal.lift_rate;
  else
    calculated.last_thermal_average_smooth =
        LowPassFilter(calculated.last_thermal_average_smooth,
                      calculated.last_thermal.lift_rate, fixed(0.3));

  ThermalSources(basic, calculated, calculated.thermal_locator);
}
Exemple #10
0
static BOOL PDGFTL1(PDeviceDescriptor_t d, TCHAR *String, NMEA_INFO *pGPS)
{
    /*
    	$PDGFTL1		     field     example
    	QNE 1013.25 altitude		0	2025  meters
    	QNH altitude			1	2000  meters
    	vario cm/s	 		2	 250  2.5m/s
    	netto vario			3	-14   dm/s
    	IAS				4	45    kmh
    	ground efficiency 		5	134   13:4 GR
    	Wind speed			6	28    kmh
    	Wind direction			7	65    degrees
    	Main lithium battery 3,82 v	8	382   0.01v
    	Backup AA battery 1,53 v 	9	153   0.01v

    	100209 Paolo Ventafridda
    	netto and ias optionals

    */

    TCHAR ctemp[80];
    double vtas, vias;
    double altqne, altqnh;
    static bool initqnh=true;



    NMEAParser::ExtractParameter(String,ctemp,0);
    altqne = StrToDouble(ctemp,NULL);
    NMEAParser::ExtractParameter(String,ctemp,1);
    altqnh = StrToDouble(ctemp,NULL);

    // AutoQNH will take care of setting an average QNH if nobody does it for a while
    if (initqnh) {
        // if digifly has qnh set by user qne and qnh are of course different
        if (altqne != altqnh) {
            UpdateQNH(FindQNH(altqne,altqnh));
            StartupStore(_T(". Using Digifly QNH %f%s"),QNH,NEWLINE);
            initqnh=false;
        } else {
            // if locally QNH was set, either by user of by AutoQNH, stop processing QNH from digifly
            if ( (QNH <= 1012) || (QNH>=1014)) initqnh=false;
            // else continue entering initqnh until somebody changes qnh in either digifly or lk8000
        }
    }
    UpdateBaroSource( pGPS,0, d,  AltitudeToQNHAltitude(altqne));


    NMEAParser::ExtractParameter(String,ctemp,2);
#if 1
    pGPS->Vario = StrToDouble(ctemp,NULL)/100;
#else
    double newVario = StrToDouble(ctemp,NULL)/100;
    pGPS->Vario = LowPassFilter(pGPS->Vario,newVario,0.1);
#endif
    pGPS->VarioAvailable = TRUE;


    NMEAParser::ExtractParameter(String,ctemp,3);
    if (ctemp[0] != '\0') {
        pGPS->NettoVario = StrToDouble(ctemp,NULL)/10; // dm/s
        pGPS->NettoVarioAvailable = TRUE;
    } else
        pGPS->NettoVarioAvailable = FALSE;


    NMEAParser::ExtractParameter(String,ctemp,4);
    if (ctemp[0] != '\0') {
        // we store m/s  , so we convert it from kmh
        vias = StrToDouble(ctemp,NULL)/3.6;

        if (vias >1) {
            vtas = vias*AirDensityRatio(altqne);
            pGPS->TrueAirspeed = vtas;
            pGPS->IndicatedAirspeed = vias;
            pGPS->AirspeedAvailable = TRUE;
        }
    } else {
        pGPS->AirspeedAvailable = FALSE;
    }


    NMEAParser::ExtractParameter(String,ctemp,8);
    pGPS->ExtBatt1_Voltage = StrToDouble(ctemp,NULL)/100;
    NMEAParser::ExtractParameter(String,ctemp,9);
    pGPS->ExtBatt2_Voltage = StrToDouble(ctemp,NULL)/100;

    TriggerVarioUpdate();

    return TRUE;
}
Exemple #11
0
void DoAutoMacCready(NMEA_INFO *Basic, DERIVED_INFO *Calculated) {

    if (!Calculated->AutoMacCready) return;

    bool is_final_glide = false;
    bool is_conical_ess = false;

    double ConeSlope = 0.0;

    //  LockFlightData();
    LockTaskData();

    double mc_new = MACCREADY;
    static bool first_mc = true;

    if (AutoMcMode == amcEquivalent) {
        if ((!Calculated->Circling) && (!Calculated->OnGround)) {
            if (Calculated->EqMc >= 0) {
                // MACCREADY = LowPassFilter(MACCREADY,Calculated->EqMc,0.8);
                CheckSetMACCREADY(Calculated->EqMc);
            } else {
                // -1.0 is used as an invalid flag. Normally flying at -1 MC means almost flying
                // at stall speed, which is pretty unusual. Maybe in wave conditions?
                if (Calculated->EqMc >-1) {
                    CheckSetMACCREADY(Calculated->EqMc*-1);
                }
            }
        }
        UnlockTaskData();
        return;
    }

    // otherwise, if AutoMc for finalglide or "both", return if no goto
    if (ValidTaskPoint(ActiveWayPoint)) {
        if (Calculated->FinalGlide && ActiveIsFinalWaypoint()) {
            is_final_glide = true;
        } else {
            first_mc = true;
        }

        if (DoOptimizeRoute() && Calculated->NextAltitude > 0.) {
            // Special case for Conical end of Speed section
            int Type = -1;
            GetTaskSectorParameter(ActiveWayPoint, &Type, NULL);
            ConeSlope = Task[ActiveWayPoint].PGConeSlope;
            if (Type == CONE && ConeSlope > 0.0) {
                is_final_glide = true;
                is_conical_ess = true;
            }
        }
    }

    double av_thermal = -1;
    if (flightstats.ThermalAverage.y_ave > 0) {
        if (Calculated->Circling && (Calculated->AverageThermal > 0)) {
#if BUGSTOP
            LKASSERT((flightstats.ThermalAverage.sum_n + 1) != 0);
#endif
            if (flightstats.ThermalAverage.sum_n == -1) {
                flightstats.ThermalAverage.sum_n = -0.99;
            }
            av_thermal = (flightstats.ThermalAverage.y_ave
                    * flightstats.ThermalAverage.sum_n
                    + Calculated->AverageThermal) /
                    (flightstats.ThermalAverage.sum_n + 1);
        } else {
            av_thermal = flightstats.ThermalAverage.y_ave;
        }
    } else if (Calculated->Circling && (Calculated->AverageThermal > 0)) {
        // insufficient stats, so use this/last thermal's average
        av_thermal = Calculated->AverageThermal;
    }

    if (!ValidTaskPoint(ActiveWayPoint)) {
        if (av_thermal > 0) {
            mc_new = av_thermal;
        } else {
            mc_new = 0;
        }
    } else if (((AutoMcMode == amcFinalGlide) || (AutoMcMode == amcFinalAndClimb)) && is_final_glide) {

        if (Calculated->TaskAltitudeDifference0 > 0) {

            // only change if above final glide with zero Mc
            // otherwise when we are well below, it will wind Mc back to
            // zero

#if BUGSTOP
            LKASSERT((Calculated->WaypointDistance + 1) != 0);
#endif
            if (Calculated->WaypointDistance < 0) Calculated->WaypointDistance = 0; // temporary but ok
            double slope =
                    (Calculated->NavAltitude + Calculated->EnergyHeight
                    - FAIFinishHeight(Basic, Calculated, ActiveWayPoint)) /
                    (Calculated->WaypointDistance + 1);

            double mc_pirker = PirkerAnalysis(Basic, Calculated,
                    Calculated->WaypointBearing,
                    slope);
            mc_pirker = max(0.0, mc_pirker);
            if (first_mc) {
                // don't allow Mc to wind down to zero when first achieving
                // final glide; but do allow it to wind down after that
                if (mc_pirker >= mc_new) {
                    mc_new = mc_pirker;
                    first_mc = false;
                } else if (AutoMcMode == amcFinalAndClimb) {
                    // revert to averager based auto Mc
                    if (av_thermal > 0) {
                        mc_new = av_thermal;
                    }
                }
            } else {
                mc_new = mc_pirker;
            }

            if (is_conical_ess) {
                const double VOpt = GlidePolar::FindSpeedForSlope(ConeSlope);
                const double eqMC = GlidePolar::EquMC(VOpt);
                if(mc_new > eqMC) {
                    mc_new = eqMC;
                }
            }

        } else { // below final glide at zero Mc, never achieved final glide
            if (first_mc && (AutoMcMode == amcFinalAndClimb)) {
                // revert to averager based auto Mc
                if (av_thermal > 0) {
                    mc_new = av_thermal;
                }
            }
        }
    } else if ((AutoMcMode == amcAverageClimb) || ((AutoMcMode == amcFinalAndClimb)&& !is_final_glide)) {
        if (av_thermal > 0) {
            mc_new = av_thermal;
        }
    }

    CheckSetMACCREADY(LowPassFilter(MACCREADY, mc_new, 0.6));

    UnlockTaskData();
    //  UnlockFlightData();

}
Exemple #12
0
//
// Support for new 2011 Digifly TOURTELL protocol 
// (Subset of TL1, pending upgrade in june 2011 for all devices)
//
static BOOL PDGFTTL(PDeviceDescriptor_t d, TCHAR *String, NMEA_INFO *pGPS)
{
/*
	$PDGFTTL		     field     example
	QNE 1013.25 altitude		0	2025  meters
	QNH altitude			1	2000  meters
	vario cm/s	 		2	 250  2.5m/s
	IAS				4	45    kmh
	netto vario			3	-14   dm/s
	
*/

  TCHAR ctemp[80];
  double vtas, vias;
  double altqne, altqnh;
  static bool initqnh=true;

  NMEAParser::ExtractParameter(String,ctemp,0);
  altqne = StrToDouble(ctemp,NULL);
  NMEAParser::ExtractParameter(String,ctemp,1);
  altqnh = StrToDouble(ctemp,NULL);

  // AutoQNH will take care of setting an average QNH if nobody does it for a while
  if (initqnh) {
	// if digifly has qnh set by user qne and qnh are of course different
	if (altqne != altqnh) {
		QNH=FindQNH(altqne,altqnh);
        CAirspaceManager::Instance().QnhChangeNotify(QNH);
		StartupStore(_T(". Using Digifly QNH %f%s"),QNH,NEWLINE);
		initqnh=false;
	} else {
		// if locally QNH was set, either by user of by AutoQNH, stop processing QNH from digifly
		if ( (QNH <= 1012) || (QNH>=1014)) initqnh=false;
		// else continue entering initqnh until somebody changes qnh in either digifly or lk8000
	}
  }
  UpdateBaroSource( pGPS, 0,d,  AltitudeToQNHAltitude(altqne));

  NMEAParser::ExtractParameter(String,ctemp,2);
#if 1
  pGPS->Vario = StrToDouble(ctemp,NULL)/100;
#else
  double newVario = StrToDouble(ctemp,NULL)/100;
  pGPS->Vario = LowPassFilter(pGPS->Vario,newVario,0.1);
#endif
  pGPS->VarioAvailable = TRUE;


  NMEAParser::ExtractParameter(String,ctemp,4);
  if (ctemp[0] != '\0') {
	pGPS->NettoVario = StrToDouble(ctemp,NULL)/10; // dm/s
	pGPS->NettoVarioAvailable = TRUE;
  } else
	pGPS->NettoVarioAvailable = FALSE;


  NMEAParser::ExtractParameter(String,ctemp,4);
  if (ctemp[0] != '\0') {
	// we store m/s  , so we convert it from kmh
	vias = StrToDouble(ctemp,NULL)/3.6;

	if (vias >1) {
		vtas = vias*AirDensityRatio(pGPS->BaroAltitude);
		pGPS->TrueAirspeed = vtas;
		pGPS->IndicatedAirspeed = vias;
		pGPS->AirspeedAvailable = TRUE;
  	}
  } else {
	pGPS->AirspeedAvailable = FALSE;
  }


  TriggerVarioUpdate();

  return TRUE;
}
Exemple #13
0
void Wiimote::GetIRData(u8* const data, bool use_accel)
{
	const bool has_focus = HAS_FOCUS;

	u16 x[4], y[4];
	memset(x, 0xFF, sizeof(x));

	if (has_focus)
	{
		float xx = 10000, yy = 0, zz = 0;
		double nsin,ncos;

		if (use_accel)
		{
			double ax,az,len;
			ax=m_accel.x;
			az=m_accel.z;
			len=sqrt(ax*ax+az*az);
			if (len)
			{
				ax/=len;
				az/=len; //normalizing the vector
				nsin=ax;
				ncos=az;
			}
			else
			{
				nsin=0;
				ncos=1;
			}
		//	PanicAlert("%d %d %d\nx:%f\nz:%f\nsin:%f\ncos:%f",accel->x,accel->y,accel->z,ax,az,sin,cos);
			//PanicAlert("%d %d %d\n%d %d %d\n%d %d %d",accel->x,accel->y,accel->z,calib->zero_g.x,calib->zero_g.y,calib->zero_g.z,
			//	calib->one_g.x,calib->one_g.y,calib->one_g.z);
		}
		else
		{
			nsin=0; //m_tilt stuff here (can't figure it out yet....)
			ncos=1;
		}

		LowPassFilter(ir_sin,nsin,1.0f/60);
		LowPassFilter(ir_cos,ncos,1.0f/60);

		m_ir->GetState(&xx, &yy, &zz, true);
		UDPTLayer::GetIR(m_udp, &xx, &yy, &zz);

		Vertex v[4];

		static const int camWidth=1024;
		static const int camHeight=768;
		static const double bndup=-0.315447;
		static const double bnddown=0.85;
		static const double bndleft=0.443364;
		static const double bndright=-0.443364;
		static const double dist1=100.f/camWidth; //this seems the optimal distance for zelda
		static const double dist2=1.2f*dist1;

		for (auto& vtx : v)
		{
			vtx.x=xx*(bndright-bndleft)/2+(bndleft+bndright)/2;
			if (m_sensor_bar_on_top) vtx.y=yy*(bndup-bnddown)/2+(bndup+bnddown)/2;
			else vtx.y=yy*(bndup-bnddown)/2-(bndup+bnddown)/2;
			vtx.z=0;
		}

		v[0].x-=(zz*0.5+1)*dist1;
		v[1].x+=(zz*0.5+1)*dist1;
		v[2].x-=(zz*0.5+1)*dist2;
		v[3].x+=(zz*0.5+1)*dist2;

#define printmatrix(m) PanicAlert("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n",m[0][0],m[0][1],m[0][2],m[0][3],m[1][0],m[1][1],m[1][2],m[1][3],m[2][0],m[2][1],m[2][2],m[2][3],m[3][0],m[3][1],m[3][2],m[3][3])
		Matrix rot,tot;
		static Matrix scale;
		static bool isscale=false;
		if (!isscale)
		{
			MatrixScale(scale,1,camWidth/camHeight,1);
			//MatrixIdentity(scale);
		}
		MatrixRotationByZ(rot,ir_sin,ir_cos);
		//MatrixIdentity(rot);
		MatrixMultiply(tot,scale,rot);

		for (int i=0; i<4; i++)
		{
			MatrixTransformVertex(tot,v[i]);
			if ((v[i].x<-1)||(v[i].x>1)||(v[i].y<-1)||(v[i].y>1))
				continue;
			x[i]=(u16)round((v[i].x+1)/2*(camWidth-1));
			y[i]=(u16)round((v[i].y+1)/2*(camHeight-1));
		}
	//	PanicAlert("%f %f\n%f %f\n%f %f\n%f %f\n%d %d\n%d %d\n%d %d\n%d %d",
	//		v[0].x,v[0].y,v[1].x,v[1].y,v[2].x,v[2].y,v[3].x,v[3].y,
	//		x[0],y[0],x[1],y[1],x[2],y[2],x[3],y[38]);
	}
	// Fill report with valid data when full handshake was done
	if (m_reg_ir.data[0x30])
	// ir mode
	switch (m_reg_ir.mode)
	{
	// basic
	case 1 :
		{
		memset(data, 0xFF, 10);
		wm_ir_basic* const irdata = (wm_ir_basic*)data;
		for (unsigned int i=0; i<2; ++i)
		{
			if (x[i*2] < 1024 && y[i*2] < 768)
			{
				irdata[i].x1 = u8(x[i*2]);
				irdata[i].x1hi = x[i*2] >> 8;

				irdata[i].y1 = u8(y[i*2]);
				irdata[i].y1hi = y[i*2] >> 8;
			}
			if (x[i*2+1] < 1024 && y[i*2+1] < 768)
			{
				irdata[i].x2 = u8(x[i*2+1]);
				irdata[i].x2hi = x[i*2+1] >> 8;

				irdata[i].y2 = u8(y[i*2+1]);
				irdata[i].y2hi = y[i*2+1] >> 8;
			}
		}
		}
Exemple #14
0
void Wiimote::GetIRData(u8* const data, bool use_accel)
{
  u16 x[4], y[4];
  memset(x, 0xFF, sizeof(x));

  ControlState xx = 10000, yy = 0, zz = 0;
  double nsin, ncos;

  if (use_accel)
  {
    double ax, az, len;
    ax = m_accel.x;
    az = m_accel.z;
    len = sqrt(ax * ax + az * az);
    if (len)
    {
      ax /= len;
      az /= len;  // normalizing the vector
      nsin = ax;
      ncos = az;
    }
    else
    {
      nsin = 0;
      ncos = 1;
    }
  }
  else
  {
    // TODO m_tilt stuff
    nsin = 0;
    ncos = 1;
  }

  LowPassFilter(ir_sin, nsin, 1.0 / 60);
  LowPassFilter(ir_cos, ncos, 1.0 / 60);

  m_ir->GetState(&xx, &yy, &zz, true);

  Vertex v[4];

  static const int camWidth = 1024;
  static const int camHeight = 768;
  static const double bndup = -0.315447;
  static const double bnddown = 0.85;
  static const double bndleft = 0.443364;
  static const double bndright = -0.443364;
  static const double dist1 = 100.0 / camWidth;  // this seems the optimal distance for zelda
  static const double dist2 = 1.2 * dist1;

  for (auto& vtx : v)
  {
    vtx.x = xx * (bndright - bndleft) / 2 + (bndleft + bndright) / 2;
    if (m_sensor_bar_on_top)
      vtx.y = yy * (bndup - bnddown) / 2 + (bndup + bnddown) / 2;
    else
      vtx.y = yy * (bndup - bnddown) / 2 - (bndup + bnddown) / 2;
    vtx.z = 0;
  }

  v[0].x -= (zz * 0.5 + 1) * dist1;
  v[1].x += (zz * 0.5 + 1) * dist1;
  v[2].x -= (zz * 0.5 + 1) * dist2;
  v[3].x += (zz * 0.5 + 1) * dist2;

#define printmatrix(m)                                                                             \
  PanicAlert("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n", m[0][0], m[0][1], m[0][2],    \
             m[0][3], m[1][0], m[1][1], m[1][2], m[1][3], m[2][0], m[2][1], m[2][2], m[2][3],      \
             m[3][0], m[3][1], m[3][2], m[3][3])
  Matrix rot, tot;
  static Matrix scale;
  MatrixScale(scale, 1, camWidth / camHeight, 1);
  MatrixRotationByZ(rot, ir_sin, ir_cos);
  MatrixMultiply(tot, scale, rot);

  for (int i = 0; i < 4; i++)
  {
    MatrixTransformVertex(tot, v[i]);
    if ((v[i].x < -1) || (v[i].x > 1) || (v[i].y < -1) || (v[i].y > 1))
      continue;
    x[i] = (u16)lround((v[i].x + 1) / 2 * (camWidth - 1));
    y[i] = (u16)lround((v[i].y + 1) / 2 * (camHeight - 1));
  }
  // Fill report with valid data when full handshake was done
  if (m_reg_ir.data[0x30])
    // ir mode
    switch (m_reg_ir.mode)
    {
      // basic
    case 1:
    {
      memset(data, 0xFF, 10);
      wm_ir_basic* const irdata = reinterpret_cast<wm_ir_basic*>(data);
      for (unsigned int i = 0; i < 2; ++i)
      {
        if (x[i * 2] < 1024 && y[i * 2] < 768)
        {
          irdata[i].x1 = static_cast<u8>(x[i * 2]);
          irdata[i].x1hi = x[i * 2] >> 8;

          irdata[i].y1 = static_cast<u8>(y[i * 2]);
          irdata[i].y1hi = y[i * 2] >> 8;
        }
        if (x[i * 2 + 1] < 1024 && y[i * 2 + 1] < 768)
        {
          irdata[i].x2 = static_cast<u8>(x[i * 2 + 1]);
          irdata[i].x2hi = x[i * 2 + 1] >> 8;

          irdata[i].y2 = static_cast<u8>(y[i * 2 + 1]);
          irdata[i].y2hi = y[i * 2 + 1] >> 8;
        }
      }
    }
Exemple #15
0
void Turning(NMEA_INFO *Basic, DERIVED_INFO *Calculated)
{
  static double LastTrack = 0;
  static double StartTime  = 0;
  static double StartLong = 0;
  static double StartLat = 0;
  static double StartAlt = 0;
  static double StartEnergyHeight = 0;
  static double LastTime = 0;
  static int MODE = CRUISE;
  static bool LEFT = FALSE;
  double Rate;
  static double LastRate=0;
  double dRate;
  double dT;

  if (!Calculated->Flying) {
	if (MODE!=CRUISE) {
		#if TESTBENCH
		StartupStore(_T(".... Not flying, still circling -> Cruise forced!\n"));
		#endif
		goto _forcereset;
	}
	return;
  }

  // Back in time in IGC replay mode?
  if(Basic->Time <= LastTime) {
    #if TESTBENCH
    StartupStore(_T("...... Turning check is back in time. Now=%f Last=%f: reset %s\n"),Basic->Time, LastTime,WhatTimeIsIt());
    #endif
_forcereset:
    LastTime = Basic->Time; // 101216 PV not sure of this.. 
    LastTrack = 0;
    StartTime  = 0;
    StartLong = 0;
    StartLat = 0;
    StartAlt = 0;
    StartEnergyHeight = 0;
    LastTime = 0;
    LEFT = FALSE;
    if (MODE!=CRUISE) {
	MODE = CRUISE;
        // Finally do the transition to cruise
        Calculated->Circling = false;
        SwitchZoomClimb(Basic, Calculated, false, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CRUISE);
    }
    return;
  }
  dT = Basic->Time - LastTime;
  LastTime = Basic->Time;

  #if BUGSTOP
  LKASSERT(dT!=0);
  #else
  if (dT==0) dT=1;
  #endif

  Rate = AngleLimit180(Basic->TrackBearing-LastTrack)/dT;

  #if DEBUGTURN
  StartupStore(_T("... Rate=%f  in time=%f\n"),Rate,dT);
  #endif

  if (dT<2.0 && dT!=0) {
    // time step ok

    // calculate acceleration
    dRate = (Rate-LastRate)/dT;

    double dtlead=0.3;
    // integrate assuming constant acceleration, for one second
    Calculated->NextTrackBearing = Basic->TrackBearing
      + dtlead*(Rate+0.5*dtlead*dRate);
    // s = u.t+ 0.5*a*t*t

    Calculated->NextTrackBearing = 
      AngleLimit360(Calculated->NextTrackBearing);
    
  } else {
    // time step too big, so just take it at last measurement
    Calculated->NextTrackBearing = Basic->TrackBearing;
  }

  Calculated->TurnRate = Rate;

  // JMW limit rate to 50 deg per second otherwise a big spike
  // will cause spurious lock on circling for a long time
  if (Rate>50) {
    Rate = 50;
  } 
  if (Rate<-50) {
    Rate = -50;
  }

  // average rate, to detect essing
  static double rate_history[60];
  double rate_ave=0;
  for (int i=59; i>0; i--) {
    rate_history[i] = rate_history[i-1];
    rate_ave += rate_history[i];
  }
  rate_history[0] = Rate;
  rate_ave /= 60;
 
  // THIS IS UNUSED in 4.0 
  Calculated->Essing = fabs(rate_ave)*100/MinTurnRate;

  if (MODE==CLIMB||MODE==WAITCRUISE)
	Rate = LowPassFilter(LastRate,Rate,0.9);
  else
	Rate = LowPassFilter(LastRate,Rate,0.3);

  LastRate = Rate;

  if(Rate <0)
    {
      if (LEFT) {
        // OK, already going left
      } else {
        LEFT = true;
      }
      Rate *= -1;
    } else {
    if (!LEFT) {
      // OK, already going right
    } else {
      LEFT = false;
    }
  }

  PercentCircling(Basic, Calculated, Rate);

  LastTrack = Basic->TrackBearing;

  bool forcecruise = false;
  bool forcecircling = false;

  #if 1 // UNUSED, EnableExternalTriggerCruise not configurable, set to false since ever
  if (EnableExternalTriggerCruise ) {
    if (ExternalTriggerCruise && ExternalTriggerCircling) {
      // this should never happen
      ExternalTriggerCircling = false;
    }
    forcecruise = ExternalTriggerCruise;
    forcecircling = ExternalTriggerCircling;
  }
  #endif

  switch(MODE) {
  case CRUISE:

    double cruise_turnthreshold;
    if (ISPARAGLIDER)
	cruise_turnthreshold=5;
    else
	cruise_turnthreshold=4;

    if((Rate >= cruise_turnthreshold)||(forcecircling)) {
      // This is initialising the potential thermalling start
      // We still dont know if we are really circling for thermal
      StartTime = Basic->Time;
      StartLong = Basic->Longitude;
      StartLat  = Basic->Latitude;
      StartAlt  = Calculated->NavAltitude;
      StartEnergyHeight  = Calculated->EnergyHeight;
      #if DEBUGTURN
      StartupStore(_T("... CRUISE -> WAITCLIMB\n"));
      #endif
      MODE = WAITCLIMB;
    }
    if (forcecircling) {
      MODE = WAITCLIMB;
    } else {
      break;
    }
  case WAITCLIMB:
    if (forcecruise) {
      MODE = CRUISE;
      break;
    }

    double waitclimb_turnthreshold;
    double cruiseclimbswitch;
    if (ISPARAGLIDER) {
	waitclimb_turnthreshold=5;
        cruiseclimbswitch=15; // this should be finetuned for PGs
    } else {
	waitclimb_turnthreshold=4;
        cruiseclimbswitch=15; // this is ok for gliders
    }

    if((Rate >= waitclimb_turnthreshold)||(forcecircling)) {
      // WE CANNOT do this, because we also may need Circling mode to detect FF!!
      // if( (Calculated->FreeFlying && ((Basic->Time  - StartTime) > cruiseclimbswitch))|| forcecircling) {
       if( (!ISCAR && !ISGAAIRCRAFT && ((Basic->Time  - StartTime) > cruiseclimbswitch))|| forcecircling) { 

         #ifdef TOW_CRUISE
         // If free flight (FF) hasn�t yet been detected, then we may
         // still be on tow.  The following prevents climb mode from
         // engaging due to normal on-aerotow turns.

         if (!Calculated->FreeFlying && (fabs(Calculated->TurnRate) < 12))
           break;
         #endif

         #if DEBUGTURN
         StartupStore(_T("... WAITCLIMB -> CLIMB\n"));
         #endif
       Calculated->Circling = true;
        // JMW Transition to climb
        MODE = CLIMB;
	// Probably a replay flight, with fast forward with no cruise init
	if (StartTime==0) {
	      StartTime = Basic->Time;
	      StartLong = Basic->Longitude;
	      StartLat  = Basic->Latitude;
	      StartAlt  = Calculated->NavAltitude;
	      StartEnergyHeight  = Calculated->EnergyHeight;
	}
        Calculated->ClimbStartLat = StartLat;
        Calculated->ClimbStartLong = StartLong;
        Calculated->ClimbStartAlt = StartAlt+StartEnergyHeight;
        Calculated->ClimbStartTime = StartTime;
        
        if (flightstats.Altitude_Ceiling.sum_n>0) {
          // only update base if have already climbed, otherwise
          // we will catch the takeoff height as the base.

          flightstats.Altitude_Base.
            least_squares_update(max(0.0, Calculated->ClimbStartTime
                                     - Calculated->TakeOffTime)/3600.0,
                                 StartAlt);
        }
        
        SwitchZoomClimb(Basic, Calculated, true, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CLIMB);
      }
    } else {
      // nope, not turning, so go back to cruise
      #if DEBUGTURN
      StartupStore(_T("... WAITCLIMB -> CRUISE\n"));
      #endif
      MODE = CRUISE;
    }
    break;
  case CLIMB:
    if ( (AutoWindMode == D_AUTOWIND_CIRCLING) || (AutoWindMode==D_AUTOWIND_BOTHCIRCZAG) ) {
      LockFlightData();
      windanalyser->slot_newSample(Basic, Calculated);
      UnlockFlightData();
    }

    double climb_turnthreshold;
    if (ISPARAGLIDER)
	climb_turnthreshold=10;
    else
	climb_turnthreshold=4;
    
    if((Rate < climb_turnthreshold)||(forcecruise)) {
      StartTime = Basic->Time;
      StartLong = Basic->Longitude;
      StartLat  = Basic->Latitude;
      StartAlt  = Calculated->NavAltitude;
      StartEnergyHeight  = Calculated->EnergyHeight;
      // JMW Transition to cruise, due to not properly turning
      MODE = WAITCRUISE;
      #if DEBUGTURN
      StartupStore(_T("... CLIMB -> WAITCRUISE\n"));
      #endif
    }
    if (forcecruise) {
      MODE = WAITCRUISE;
    } else {
      break;
    }
  case WAITCRUISE:

    if (forcecircling) {
      MODE = CLIMB;
      break;
    }

    double waitcruise_turnthreshold;
    double climbcruiseswitch;
    if (ISPARAGLIDER) {
	waitcruise_turnthreshold=10;
        climbcruiseswitch=15;
    } else {
	waitcruise_turnthreshold=4;
        climbcruiseswitch=9; // ok for gliders
    }
    //
    // Exiting climb mode?
    //
    if((Rate < waitcruise_turnthreshold) || forcecruise) {

      if( ((Basic->Time  - StartTime) > climbcruiseswitch) || forcecruise) {

	// We are no more in climb mode

        if (StartTime==0) {
          StartTime = Basic->Time;
          StartLong = Basic->Longitude;
          StartLat  = Basic->Latitude;
          StartAlt  = Calculated->NavAltitude;
          StartEnergyHeight  = Calculated->EnergyHeight;
        }
        Calculated->CruiseStartLat  = StartLat;
        Calculated->CruiseStartLong = StartLong;
        Calculated->CruiseStartAlt  = StartAlt;
        Calculated->CruiseStartTime = StartTime;

	// Here we assign automatically this last thermal to the L> multitarget
	if (Calculated->ThermalGain >100) {

		// Force immediate calculation of average thermal, it would be made
		// during next cycle, but we need it here immediately
		AverageThermal(Basic,Calculated);

		if (EnableThermalLocator) {
			InsertThermalHistory(Calculated->ClimbStartTime, 
				Calculated->ThermalEstimate_Latitude, Calculated->ThermalEstimate_Longitude,
				Calculated->ClimbStartAlt, Calculated->NavAltitude, Calculated->AverageThermal);
		} else {
			InsertThermalHistory(Calculated->ClimbStartTime, 
				Calculated->ClimbStartLat, Calculated->ClimbStartLong, 
				Calculated->ClimbStartAlt, Calculated->NavAltitude, Calculated->AverageThermal);
		}

	}

	InitLDRotary(&rotaryLD);
	InitWindRotary(&rotaryWind);
        
        flightstats.Altitude_Ceiling.
          least_squares_update(max(0.0, Calculated->CruiseStartTime
                                   - Calculated->TakeOffTime)/3600.0,
                               Calculated->CruiseStartAlt);
        
        // Finally do the transition to cruise
        Calculated->Circling = false;
        MODE = CRUISE;
        #if DEBUGTURN
        StartupStore(_T("... WAITCRUISE -> CRUISE\n"));
        #endif
        SwitchZoomClimb(Basic, Calculated, false, LEFT);
        InputEvents::processGlideComputer(GCE_FLIGHTMODE_CRUISE);

      } // climbcruiseswitch time in range

    } else { // Rate>Minturnrate, back to climb, turning again
      #if DEBUGTURN
      StartupStore(_T("... WAITCRUISE -> CLIMB\n"));
      #endif
      MODE = CLIMB;
    }
    break;
  default:
    // error, go to cruise
    MODE = CRUISE;
  }
  // generate new wind vector if altitude changes or a new
  // estimate is available
  if (AutoWindMode>D_AUTOWIND_MANUAL && AutoWindMode <D_AUTOWIND_EXTERNAL) {
    LockFlightData();
    windanalyser->slot_Altitude(Basic, Calculated);
    UnlockFlightData();
  }

  if (EnableThermalLocator) {
    if (Calculated->Circling) {
      thermallocator.AddPoint(Basic->Time, Basic->Longitude, Basic->Latitude,
			      Calculated->NettoVario);
      thermallocator.Update(Basic->Time, Basic->Longitude, Basic->Latitude,
			    Calculated->WindSpeed, Calculated->WindBearing,
			    Basic->TrackBearing,
			    &Calculated->ThermalEstimate_Longitude,
			    &Calculated->ThermalEstimate_Latitude,
			    &Calculated->ThermalEstimate_W,
			    &Calculated->ThermalEstimate_R);
    } else {
      Calculated->ThermalEstimate_W = 0;
      Calculated->ThermalEstimate_R = -1;
      thermallocator.Reset();
    }
  }

  // update atmospheric model
  CuSonde::updateMeasurements(Basic, Calculated);

}
Exemple #16
0
void ITG3200::UpdateBias(float dt)
{
	m_Bias = LowPassFilter(GetBiasedAngVel(), m_Bias, dt, 30.0f);
}
Exemple #17
0
void DoAutoMacCready(NMEA_INFO *Basic, DERIVED_INFO *Calculated)
{

  if (!Calculated->AutoMacCready) return;

  bool is_final_glide = false;
  //  LockFlightData();
  LockTaskData();

  double mc_new = MACCREADY;
  static bool first_mc = true;

  if ( AutoMcMode==amcEquivalent ) {
	if ( (!Calculated->Circling)  && (!Calculated->OnGround)) {
		if (Calculated->EqMc>=0) {
			// MACCREADY = LowPassFilter(MACCREADY,Calculated->EqMc,0.8); 
			MACCREADY = Calculated->EqMc;
		} else {
			// -1.0 is used as an invalid flag. Normally flying at -1 MC means almost flying
			// at stall speed, which is pretty unusual. Maybe in wave conditions?
			if (Calculated->EqMc >-1) {
				MACCREADY=Calculated->EqMc*-1;
			}
		}
	}
	UnlockTaskData();
	return;
  }

  // otherwise, if AutoMc for finalglide or "both", return if no goto
  if (!ValidTaskPoint(ActiveWayPoint)) {
	UnlockTaskData();
	return;
  }

  if (Calculated->FinalGlide && ActiveIsFinalWaypoint()) {  
    is_final_glide = true;
  } else {
    first_mc = true;
  }

  double av_thermal = -1;
  if (flightstats.ThermalAverage.y_ave>0) {
    if (Calculated->Circling && (Calculated->AverageThermal>0)) {
      LKASSERT((flightstats.ThermalAverage.sum_n+1)!=0);
      av_thermal = (flightstats.ThermalAverage.y_ave
		*flightstats.ThermalAverage.sum_n 
		+ Calculated->AverageThermal)/
	(flightstats.ThermalAverage.sum_n+1);
    } else {
      av_thermal = flightstats.ThermalAverage.y_ave;
    }
  } else if (Calculated->Circling && (Calculated->AverageThermal>0)) {
    // insufficient stats, so use this/last thermal's average
    av_thermal = Calculated->AverageThermal;
  }

  if (!ValidTaskPoint(ActiveWayPoint)) {
    if (av_thermal>0) {
      mc_new = av_thermal;
    }
  } else if ( ((AutoMcMode==amcFinalGlide)||(AutoMcMode==amcFinalAndClimb)) && is_final_glide) {

      if (Calculated->TaskAltitudeDifference0>0) {
	
      // only change if above final glide with zero Mc
      // otherwise when we are well below, it will wind Mc back to
      // zero
      
      LKASSERT((Calculated->WaypointDistance+1)!=0);;
      double slope = 
	(Calculated->NavAltitude + Calculated->EnergyHeight
	 - FAIFinishHeight(Basic, Calculated, ActiveWayPoint))/
	(Calculated->WaypointDistance+1);
      
      double mc_pirker = PirkerAnalysis(Basic, Calculated,
					Calculated->WaypointBearing,
					slope);
      mc_pirker = max(0.0, mc_pirker);
      if (first_mc) {
	// don't allow Mc to wind down to zero when first achieving
	// final glide; but do allow it to wind down after that
	if (mc_pirker >= mc_new) {
	  mc_new = mc_pirker;
	  first_mc = false;
	} else if (AutoMcMode==amcFinalAndClimb) {
	  // revert to averager based auto Mc
	  if (av_thermal>0) {
	    mc_new = av_thermal;
	  }
	}
      } else {
	mc_new = mc_pirker;
      }
    } else { // below final glide at zero Mc, never achieved final glide
      if (first_mc && (AutoMcMode==amcFinalAndClimb)) {
	// revert to averager based auto Mc
	if (av_thermal>0) {
	  mc_new = av_thermal;
	}
      }
    }
  } else if ( (AutoMcMode==amcAverageClimb) || ((AutoMcMode==amcFinalAndClimb)&& !is_final_glide) ) {
    if (av_thermal>0) {
      mc_new = av_thermal;
    }
  }

  MACCREADY = LowPassFilter(MACCREADY,mc_new,0.6);

  UnlockTaskData();
  //  UnlockFlightData();

}
Exemple #18
0
void TaskSpeed(NMEA_INFO *Basic, DERIVED_INFO *Calculated, const double this_maccready)
{
    int ifinal;
    static double LastTime = 0;
    static double LastTimeStats = 0;
    double TotalTime=0, TotalDistance=0, Vfinal=0;

    if (!ValidTaskPoint(ActiveWayPoint)) return;
    if (Calculated->ValidFinish) return;
    if (!Calculated->Flying) return;

    // in case we leave early due to error
    Calculated->TaskSpeedAchieved = 0;
    Calculated->TaskSpeed = 0;

    if (ActiveWayPoint<=0) { // no task speed before start
        Calculated->TaskSpeedInstantaneous = 0;
        return;
    }

    //  LockFlightData();
    LockTaskData();

    if (TaskAltitudeRequired(Basic, Calculated, this_maccready, &Vfinal,
                             &TotalTime, &TotalDistance, &ifinal)) {

        double t0 = TotalTime;
        // total time expected for task

        double t1 = Basic->Time-Calculated->TaskStartTime;
        // time elapsed since start

        double d0 = TotalDistance;
        // total task distance

        double d1 = Calculated->TaskDistanceCovered;
        // actual distance covered

        double dr = Calculated->TaskDistanceToGo;
        // distance remaining

        double hf = FAIFinishHeight(Basic, Calculated, -1);

        double h0 = Calculated->TaskAltitudeRequiredFromStart-hf;
        // total height required from start (takes safety arrival alt
        // and finish waypoint altitude into account)

        double h1 = max(0.0, Calculated->NavAltitude-hf);
        // height above target

        double dFinal;
        // final glide distance

        // equivalent speed
        double v2, v1;

        if ((t1<=0) || (d1<=0) || (d0<=0) || (t0<=0) || (h0<=0)) {
            // haven't started yet or not a real task
            Calculated->TaskSpeedInstantaneous = 0;
            //?      Calculated->TaskSpeed = 0;
            goto OnExit;
        }

        // JB's task speed...
        double hx = max(0.0, SpeedHeight(Basic, Calculated));
        double t1mod = t1-hx/MacCreadyOrAvClimbRate(Basic, Calculated, this_maccready);
        // only valid if flown for 5 minutes or more
        if (t1mod>300.0) {
            Calculated->TaskSpeedAchieved = d1/t1mod;
        } else {
            Calculated->TaskSpeedAchieved = d1/t1;
        }
        Calculated->TaskSpeed = Calculated->TaskSpeedAchieved;

        if (Vfinal<=0) {
            // can't reach target at current mc
            goto OnExit;
        }

        // distance that can be usefully final glided from here
        // (assumes average task glide angle of d0/h0)
        // JMW TODO accuracy: make this more accurate by working out final glide
        // through remaining turnpoints.  This will more correctly account
        // for wind.

#if BUGSTOP
        LKASSERT(h0!=0);
#endif
        if (h0==0) h0=1;
        dFinal = min(dr, d0*min(1.0,max(0.0,h1/h0)));

        if (Calculated->ValidFinish) {
            dFinal = 0;
        }

        double dc = max(0.0, dr-dFinal);
        // amount of extra distance to travel in cruise/climb before final glide

        // actual task speed achieved so far
        v1 = d1/t1;

#ifdef OLDTASKSPEED
        // time at end of final glide
        // equivalent time elapsed after final glide
        double t2 = t1+dFinal/Vfinal;

        // equivalent distance travelled after final glide
        // equivalent distance to end of final glide
        double d2 = d1+dFinal;

        // average speed to end of final glide from here
        v2 = d2/t2;
        Calculated->TaskSpeed = max(v1,v2);
#else
        // average speed to end of final glide from here, weighted
        // according to how much extra time would be spent in cruise/climb
        // the closer dc (the difference between remaining distance and
        // final glidable distance) gets to zero, the closer v2 approaches
        // the average speed to end of final glide from here
        // in other words, the more we consider the final glide part to have
        // been earned.

        // this will be bogus at fast starts though...

        LKASSERT((t1+dc/v1+dFinal/Vfinal)!=0);
        LKASSERT((t1+dFinal/Vfinal)!=0);
        if (v1>0) {
            v2 = (d1+dc+dFinal)/(t1+dc/v1+dFinal/Vfinal);
        } else {
            v2 = (d1+dFinal)/(t1+dFinal/Vfinal);
        }
        Calculated->TaskSpeed = v2;
#endif

        if(Basic->Time < LastTime) {
            LastTime = Basic->Time;
        } else if (Basic->Time-LastTime >=1.0) {

            double dt = Basic->Time-LastTime;
            LastTime = Basic->Time;

            // Calculate contribution to average task speed.
            // This is equal to the change in virtual distance
            // divided by the time step

            // This is a novel concept.
            // When climbing at the MC setting, this number should
            // be similar to the estimated task speed.
            // When climbing slowly or when flying off-course,
            // this number will drop.
            // In cruise at the optimum speed in zero lift, this
            // number will be similar to the estimated task speed.

            // A low pass filter is applied so it doesn't jump around
            // too much when circling.

            // If this number is higher than the overall task average speed,
            // it means that the task average speed is increasing.

            // When cruising in sink, this number will decrease.
            // When cruising in lift, this number will increase.

            // Therefore, it shows well whether at any time the glider
            // is wasting time.

            // VNT 090723 NOTICE: all of this is totally crazy. Did anyone ever cared to check
            // what happens with MC=0 ? Did anyone care to tell people how a simple "ETE" or TaskSpeed
            // has been complicated over any limit?
            // TODO: start back from scratch, not possible to trust any number here.

            static double dr_last = 0;

            double mc_safe = max(0.1,this_maccready);
            double Vstar = max(1.0,Calculated->VMacCready);

#if BUGSTOP
            LKASSERT(dt!=0);
#endif
            if (dt==0) dt=1;
            double vthis = (Calculated->LegDistanceCovered-dr_last)/dt;
            vthis /= AirDensityRatio(Calculated->NavAltitude);

            dr_last = Calculated->LegDistanceCovered;
            double ttg = max(1.0, Calculated->LegTimeToGo);
            //      double Vav = d0/max(1.0,t0);
            double Vrem = Calculated->LegDistanceToGo/ttg;
            double Vref = // Vav;
                Vrem;
            double sr = -GlidePolar::SinkRate(Vstar);
            double height_diff = max(0.0, -Calculated->TaskAltitudeDifference);

            if (Calculated->timeCircling>30) {
                mc_safe = max(mc_safe,
                              Calculated->TotalHeightClimb/Calculated->timeCircling);
            }
            // circling percentage during cruise/climb
            double rho_cruise = max(0.0,min(1.0,mc_safe/(sr+mc_safe)));
            double rho_climb = 1.0-rho_cruise;
#if BUGSTOP
            LKASSERT(mc_safe!=0);
#endif
            if (mc_safe==0) mc_safe=0.1;
            double time_climb = height_diff/mc_safe;

            // calculate amount of time in cruise/climb glide
            double rho_c = max(0.0, min(1.0, time_climb/ttg));

            if (Calculated->FinalGlide) {
                if (rho_climb>0) {
                    rho_c = max(0.0, min(1.0, rho_c/rho_climb));
                }
                if (!Calculated->Circling) {
                    if (Calculated->TaskAltitudeDifference>0) {
                        rho_climb *= rho_c;
                        rho_cruise *= rho_c;
                        // Vref = Vrem;
                    }
                }
            }

            LKASSERT(mc_safe!=0);
            double w_comp = min(10.0,max(-10.0,Calculated->Vario/mc_safe));
            double vdiff = vthis/Vstar + w_comp*rho_cruise + rho_climb;

            if (vthis > SAFTEYSPEED*2) {
                vdiff = 1.0;
                // prevent funny numbers when starting mid-track
            }
            //      Calculated->Experimental = vdiff*100.0;

            vdiff *= Vref;

            if (t1<5) {
                Calculated->TaskSpeedInstantaneous = vdiff;
                // initialise
            } else {
                static int lastActiveWayPoint = 0;
                static double tsi_av = 0;
                static int n_av = 0;
                if ((ActiveWayPoint==lastActiveWayPoint)
                        && (Calculated->LegDistanceToGo>1000.0)
                        && (Calculated->LegDistanceCovered>1000.0)) {

                    Calculated->TaskSpeedInstantaneous =
                        LowPassFilter(Calculated->TaskSpeedInstantaneous, vdiff, 0.1);

                    // update stats
                    if(Basic->Time < LastTimeStats) {
                        LastTimeStats = Basic->Time;
                        tsi_av = 0;
                        n_av = 0;
                    } else if (n_av>=60) {
                        tsi_av/= n_av;
                        flightstats.Task_Speed.
                        least_squares_update(
                            max(0.0,
                                Basic->Time-Calculated->TaskStartTime)/3600.0,
                            max(0.0, min(100.0,tsi_av)));
                        LastTimeStats = Basic->Time;
                        tsi_av = 0;
                        n_av = 0;
                    }
                    tsi_av += Calculated->TaskSpeedInstantaneous;
                    n_av ++;

                } else {

                    Calculated->TaskSpeedInstantaneous =
                        LowPassFilter(Calculated->TaskSpeedInstantaneous, vdiff, 0.5);

                    //	  Calculated->TaskSpeedInstantaneous = vdiff;
                    tsi_av = 0;
                    n_av = 0;
                }
                lastActiveWayPoint = ActiveWayPoint;
            }
        }
    }
OnExit:
    UnlockTaskData();

}
void
CirclingComputer::Turning(CirclingInfo &circling_info,
                          const MoreData &basic, const MoreData &last_basic,
                          const DerivedInfo &calculated,
                          const DerivedInfo &last_calculated,
                          const ComputerSettings &settings_computer)
{
    // You can't be circling unless you're flying
    if (!calculated.flight.flying || !basic.HasTimeAdvancedSince(last_basic))
        return;

    // JMW limit rate to 50 deg per second otherwise a big spike
    // will cause spurious lock on circling for a long time
    fixed turn_rate = max(fixed(-50), min(fixed(50), calculated.turn_rate));

    // Make the turn rate more smooth using the LowPassFilter
    turn_rate = LowPassFilter(last_calculated.turn_rate_smoothed,
                              turn_rate, fixed(0.3));
    circling_info.turn_rate_smoothed = turn_rate;
    circling_info.turning = fabs(turn_rate) >= min_turn_rate;

    // Force cruise or climb mode if external device says so
    bool force_cruise = false;
    bool force_circling = false;
    if (settings_computer.external_trigger_cruise_enabled && !basic.gps.replay) {
        switch (basic.switch_state.flight_mode) {
        case SwitchInfo::FlightMode::UNKNOWN:
            force_circling = false;
            force_cruise = false;
            break;

        case SwitchInfo::FlightMode::CIRCLING:
            force_circling = true;
            force_cruise = false;
            break;

        case SwitchInfo::FlightMode::CRUISE:
            force_circling = false;
            force_cruise = true;
            break;
        }
    }

    switch (calculated.turn_mode) {
    case CirclingMode::CRUISE:
        // If (in cruise mode and beginning of circling detected)
        if (circling_info.turning || force_circling) {
            // Remember the start values of the turn
            circling_info.turn_start_time = basic.time;
            circling_info.turn_start_location = basic.location;
            circling_info.turn_start_altitude = basic.nav_altitude;
            circling_info.turn_start_energy_height = basic.energy_height;
            circling_info.turn_mode = CirclingMode::POSSIBLE_CLIMB;
        }
        if (!force_circling)
            break;

    case CirclingMode::POSSIBLE_CLIMB:
        if (force_cruise) {
            circling_info.turn_mode = CirclingMode::CRUISE;
            break;
        }
        if (circling_info.turning || force_circling) {
            if (((basic.time - calculated.turn_start_time) > cruise_climb_switch)
                    || force_circling) {
                // yes, we are certain now that we are circling
                circling_info.circling = true;

                // JMW Transition to climb
                circling_info.turn_mode = CirclingMode::CLIMB;

                // Remember the start values of the climbing period
                circling_info.climb_start_location = circling_info.turn_start_location;
                circling_info.climb_start_altitude = circling_info.turn_start_altitude
                                                     + circling_info.turn_start_energy_height;
                circling_info.climb_start_time = circling_info.turn_start_time;
            }
        } else {
            // nope, not turning, so go back to cruise
            circling_info.turn_mode = CirclingMode::CRUISE;
        }
        break;

    case CirclingMode::CLIMB:
        if (!circling_info.turning || force_cruise) {
            // Remember the end values of the turn
            circling_info.turn_start_time = basic.time;
            circling_info.turn_start_location = basic.location;
            circling_info.turn_start_altitude = basic.nav_altitude;
            circling_info.turn_start_energy_height = basic.energy_height;

            // JMW Transition to cruise, due to not properly turning
            circling_info.turn_mode = CirclingMode::POSSIBLE_CRUISE;
        }
        if (!force_cruise)
            break;

    case CirclingMode::POSSIBLE_CRUISE:
        if (force_circling) {
            circling_info.turn_mode = CirclingMode::CLIMB;
            break;
        }

        if (!circling_info.turning || force_cruise) {
            if (((basic.time - circling_info.turn_start_time) > climb_cruise_switch)
                    || force_cruise) {
                // yes, we are certain now that we are cruising again
                circling_info.circling = false;

                // Transition to cruise
                circling_info.turn_mode = CirclingMode::CRUISE;
                circling_info.cruise_start_location = circling_info.turn_start_location;
                circling_info.cruise_start_altitude = circling_info.turn_start_altitude;
                circling_info.cruise_start_time = circling_info.turn_start_time;
            }
        } else {
            // nope, we are circling again
            // JMW Transition back to climb, because we are turning again
            circling_info.turn_mode = CirclingMode::CLIMB;
        }
        break;
    }
}