react-nativeexposensorsfloating-accuracy

How to Improve the Accuracy of an App Altimeter for Small Height Variations?


I'm developing an altimeter to measure small height variations (0 to 1.5 m) using the smartphone barometer. However, I'm facing accuracy issues due to the sensor's sensitivity to device temperature and pressure fluctuations.

Current Approach

Currently, I calculate height based on the difference in pressure between an initial reading and the ongoing readings. I use a constant pressure-to-height ratio, experimentally determined to be around 8.7 m/hPa, to estimate the height change.

To minimize noise, I apply a moving average filter to both the initial and ongoing pressure readings. I also set a dead zone threshold to ignore minor fluctuations in pressure that don't significantly affect the height measurement.

Problems

  1. Thermal Variation: The smartphone barometer can be affected by the device's temperature, leading to pressure variations.
  2. Measurement Noise: Small natural fluctuations in pressure can cause inconsistent readings.
  3. Delay and Latency: The applied filters introduce some delay in the response, which can affect real-time measurements.

Question


Solution

  • You can try something like this, it's not as precise as it could be, but it's a start.

    export default function App() {
      const [pressureReadings, setPressureReadings] = useState([]);
      const [initialPressureReadings, setInitialPressureReadings] = useState([]);
      const [initialPressure, setInitialPressure] = useState(null);
      const [relativeHeight, setRelativeHeight] = useState(0);
      const [isCollecting, setIsCollecting] = useState(false);
      const [subscription, setSubscription] = useState(null);
    
      const PRESSURE_TO_HEIGHT_RATIO = 9;
      const SAMPLE_SIZE = 6;
      const INITIAL_SAMPLE_SIZE = 12;
      const FILTER_SIZE = 6;
      const HEIGHT_STEP = 0.3; // Precisão de 0,4 m
      const HEIGHT_TARGET = 1.2; // Altura alvo
      const HEIGHT_TOLERANCE = 0.05; // Margem de erro
      const DEAD_ZONE = 0.1; // Ignorar pequenas variações
    
      // Função de filtro de média móvel
      const applyMovingAverageFilter = (values, size) => {
        if (values.length < size) return values;
        const filtered = [];
        for (let i = 0; i <= values.length - size; i++) {
          const slice = values.slice(i, i + size);
          const average = slice.reduce((sum, v) => sum + v, 0) / size;
          filtered.push(average);
        }
        return filtered;
      };
    
      useEffect(() => {
        if (!cameraPermission?.granted) {
          requestCameraPermission();
        }
      }, []);
    
      useEffect(() => {
        (async () => {
          const { status } = await MediaLibrary.requestPermissionsAsync();
          requestMediaLibraryPermission(status === "granted");
        })();
      }, []);
    
      useEffect(() => {
        if (isCollecting) {
          const newSubscription = Barometer.addListener((data) => {
            setPressureReadings((prev) => {
              const updatedReadings = [...prev, data.pressure];
    
              if (initialPressure === null) {
                setInitialPressureReadings((prevInit) => {
                  const updatedInitReadings = [...prevInit, data.pressure];
                  if (updatedInitReadings.length === INITIAL_SAMPLE_SIZE) {
                    const avgInitialPressure =
                      updatedInitReadings.reduce((sum, p) => sum + p, 0) /
                      INITIAL_SAMPLE_SIZE;
                    setInitialPressure(avgInitialPressure);
                    setMessage("Levante o celular!");
                  }
                  return updatedInitReadings.length > INITIAL_SAMPLE_SIZE
                    ? updatedInitReadings.slice(1)
                    : updatedInitReadings;
                });
              }
              return updatedReadings.length > SAMPLE_SIZE
                ? updatedReadings.slice(1)
                : updatedReadings;
            });
          });
    
          setSubscription(newSubscription);
          return () => {
            if (newSubscription) newSubscription.remove();
          };
        }
      }, [isCollecting]);
    
      useEffect(() => {
        if (initialPressure !== null && pressureReadings.length === SAMPLE_SIZE) {
          // Aplicando filtro de média móvel
          const filteredReadings = applyMovingAverageFilter(
            pressureReadings,
            FILTER_SIZE
          );
          const avgPressure =
            filteredReadings.reduce((sum, p) => sum + p, 0) /
            filteredReadings.length;
          let pressureDifference = initialPressure - avgPressure;
    
          // Calculando altura com precisão de 0,4m
          let calculatedHeight =
            Math.round((pressureDifference * PRESSURE_TO_HEIGHT_RATIO) / HEIGHT_STEP) *
            HEIGHT_STEP;
    
          // Aplicação da Dead Zone (ignorar pequenas variações)
          if (Math.abs(calculatedHeight - relativeHeight) > DEAD_ZONE) {
            setRelativeHeight(calculatedHeight);
          }
    
        }
      }, [pressureReadings]);