You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何利用三边测量法实现iBeacon室内定位?

室内定位应用:修复iBeacon三边测量(Trilateration)坐标计算问题

我正在开发一款房间内的室内定位应用,使用3个iBeacon设备。已获取到这些Beacon的RSSI值,尝试通过公式计算x、y坐标,但结果不准确,数值几乎无变化。请问如何在我的Flutter代码中正确使用Trilateration(三边测量法)?

我的代码:

class TabScanning extends StatefulWidget {
  TabScanning({super.key});

  double x = 0, y = 0;
  @override
  _TabScanningState createState() => _TabScanningState();

}

class _TabScanningState extends State<TabScanning> {
  StreamSubscription<RangingResult>? _streamRanging;
  final _regionBeacons = <Region, List<Beacon>>{};
  final _beacons = <Beacon>[];
  final controller = Get.find<RequirementStateController>();


  double x1 = 0, y1 = 150;
  double x2 = -150, y2 = -150;
  double x3 = 150, y3 = -150;

  double d1 = 0, d2 = 0, d3 = 0;
  int rssi = 0;
  int txPower = -67;
  int N = 2;
  List<double> distance = [0.0, 0.0, 0.0];

  List<Beacon> defaultBeacons = [
    const Beacon(
      proximityUUID: '1AA10000-0A46-215F-E97E-5A966A7DEDC3',
      major: 1,
      minor: 3,
      accuracy: 0.0,
      macAddress: "",
      txPower: 0,
    ),
    const Beacon(
      proximityUUID: '1AA10000-0A46-215F-E97E-5A966A7DEDC3',
      major: 1,
      minor: 4,
      accuracy: 0.0,
      macAddress: "ss",
      txPower: 0,
    ),
    const Beacon(
      proximityUUID: '1AA10000-0A46-215F-E97E-5A966A7DEDC3',
      major: 1,
      minor: 8,
      accuracy: 0.0,
      macAddress: "",
      txPower: 0,
    ),
  ];

  @override
  void initState() {
    super.initState();

    _beacons.addAll(defaultBeacons);

    controller.startStream.listen((flag) {
      if (flag == true) {
        initScanBeacon();
      }
    });

    controller.pauseStream.listen((flag) {
      if (flag == true) {
        //pauseScanBeacon();
      }
    });

  }

  initScanBeacon() async {
    await flutterBeacon.initializeScanning;
    if (!controller.authorizationStatusOk ||
        !controller.locationServiceEnabled ||
        !controller.bluetoothEnabled) {
      print(
          'RETURNED, authorizationStatusOk=${controller.authorizationStatusOk}, '
          'locationServiceEnabled=${controller.locationServiceEnabled}, '
          'bluetoothEnabled=${controller.bluetoothEnabled}');
      return;
    }

    final regions = <Region>[
      Region(
        identifier: 'ibeacon',
        proximityUUID: '1AA10000-0A46-215F-E97E-5A966A7DEDC3',
      ),
    ];

    if (_streamRanging != null) {
      if (_streamRanging!.isPaused) {
        _streamRanging?.resume();
        return;
      }
    }

    _streamRanging =
        flutterBeacon.ranging(regions).listen((RangingResult result) {
      print(result);
      if (mounted) {
        setState(() {
          _regionBeacons[result.region] = result.beacons;


          _beacons.forEach((existingBeacon) {
            result.beacons.forEach((newBeacon) {
              if (existingBeacon.major == newBeacon.major &&
                  existingBeacon.minor == newBeacon.minor) {            
                _beacons[_beacons.indexOf(existingBeacon)] = Beacon(
                  proximityUUID: existingBeacon.proximityUUID,
                  major: existingBeacon.major,
                  minor: existingBeacon.minor,
                  accuracy: newBeacon.accuracy,
                  rssi: newBeacon.rssi,
                  macAddress: newBeacon.macAddress,
                  txPower: newBeacon.txPower,
                );
              }
            });
          });
          _beacons.sort(_compareParameters);


         for (int i = 0; i < _beacons.length; i++) {
            rssi =_beacons[i].rssi;


            if (_beacons[i].minor == 3) {
              dist[0] = pow(10, ((txPower - rssi) / (10 * 2))) as double;
            } else if (_beacons[i].minor == 4) {
              dist[1] = pow(10, ((txPower - rssi) / (10 * 2))) as double;
            } else if (_beacons[i].minor == 8) {
              dist[2] = pow(10, ((txPower - rssi) / (10 * 2))) as double;
            }
          }

            d1 = dist[0];
            d2 = dist[1];
            d3 = dist[2];


            double a = (-2 * x1) + (2 * x2);
            double b = (-2 * y1) + (2 * y2);
            num c = pow(d1, 2) -
                pow(d2, 2) -
                pow(x1, 2) +
                pow(x2, 2) -
                pow(y1, 2) +
                pow(y2, 2);
            double d = (-2 * x2) + (2 * x3);
            double e = (-2 * y2) + (2 * y3);
            num f = pow(d2, 2) -
                pow(d3, 2) -
                pow(x2, 2) +
                pow(x3, 2) -
                pow(y2, 2) +
                pow(y3, 2);

            widget.x = (c * e - f * b);
            widget.x = widget.x / (e * a - b * d);

            widget.y = (c * d - a * f);
            widget.y = widget.y / (b * d - a * e);

        });
      }
    });
  }


  pauseScanBeacon() async {
    //_streamRanging?.pause();
    if (_beacons.isNotEmpty) {
      setState(() {
        //_beacons.clear();
      });
    }
  }

  int _compareParameters(Beacon a, Beacon b) {
    int compare = a.proximityUUID.compareTo(b.proximityUUID);
    if (compare == 0) {compare = a.major.compareTo(b.major);}
    if (compare == 0) {compare = a.minor.compareTo(b.minor);}
    return compare;
  }
}

我在GitHub上找到上述计算方式,但计算出的x、y数值几乎无变化,比如x=0.012……、y=-38.15……。


问题修复方案

一、修正距离计算逻辑

你的代码存在变量名错误(定义distance数组却用dist)、传播损耗系数固定为2(室内环境建议3~4)、硬编码txPower(应该用每个Beacon实际返回的txPower)三个核心问题,修改后的距离计算代码:

// 替换原有的距离计算循环
for (int i = 0; i < _beacons.length; i++) {
  final beacon = _beacons[i];
  // 优先使用Beacon自身的txPower,无效时用默认值
  final currentTxPower = beacon.txPower != 0 ? beacon.txPower : txPower;
  final currentRssi = beacon.rssi;

  // 过滤极端无效RSSI值
  if (currentRssi < -100 || currentRssi > -30) {
    continue;
  }

  // 正确的RSSI转距离公式:d = 10^((TxPower - RSSI)/(10*N))
  // N为环境损耗系数,室内取3.5左右,可根据实际场景调整
  final calculatedDistance = pow(10, ((currentTxPower - currentRssi) / (10 * 3.5))) as double;

  if (beacon.minor == 3) {
    distance[0] = calculatedDistance;
  } else if (beacon.minor == 4) {
    distance[1] = calculatedDistance;
  } else if (beacon.minor == 8) {
    distance[2] = calculatedDistance;
  }
}

二、修复三边测量数学公式

原代码的线性方程组推导错误,正确的三边测量公式基于两点间距离公式展开,修改后的坐标计算代码:

d1 = distance[0];
d2 = distance[1];
d3 = distance[2];

// 跳过无效距离值(初始0或异常值)
if (d1 <= 0 || d2 <= 0 || d3 <= 0) {
  return;
}

// 正确的系数计算
double a = 2 * (x2 - x1);
double b = 2 * (y2 - y1);
double c = pow(d1, 2) - pow(d2, 2) - pow(x1, 2) + pow(x2, 2) - pow(y1, 2) + pow(y2, 2);
double dCoeff = 2 * (x3 - x2);
double e = 2 * (y3 - y2);
double f = pow(d2, 2) - pow(d3, 2) - pow(x2, 2) + pow(x3, 2) - pow(y2, 2) + pow(y3, 2);

// 避免除以0
double denominator = a * e - b * dCoeff;
if (denominator.abs() < 0.0001) {
  return;
}

// 求解坐标
widget.x = (c * e - f * b) / denominator;
widget.y = (a * f - c * dCoeff) / denominator;

三、提升数据稳定性

  1. 滑动平均平滑距离:减少RSSI波动带来的距离跳变,添加全局变量存储历史距离:
// 类内定义
Map<int, List<double>> distanceHistory = {3: [], 4: [], 8: []};
final maxHistoryCount = 5;

// 在计算出calculatedDistance后替换赋值逻辑
distanceHistory[beacon.minor]!.add(calculatedDistance);
if (distanceHistory[beacon.minor]!.length > maxHistoryCount) {
  distanceHistory[beacon.minor]!.removeAt(0);
}
final avgDistance = distanceHistory[beacon.minor]!.reduce((a, b) => a + b) / distanceHistory[beacon.minor]!.length;
if (beacon.minor == 3) distance[0] = avgDistance;
  1. 优化Beacon数据更新:替换原有的双重循环,用Map提升匹配效率:
// 替换原有的_beacons更新逻辑
final newBeaconMap = {for (var b in result.beacons) '${b.major}-${b.minor}': b};
for (int i = 0; i < _beacons.length; i++) {
  final key = '${_beacons[i].major}-${_beacons[i].minor}';
  if (newBeaconMap.containsKey(key)) {
    _beacons[i] = newBeaconMap[key]!;
  }
}

四、验证物理坐标

确保代码中x1/y1、x2/y2、x3/y3与Beacon实际部署的物理坐标一致(单位统一,比如厘米或米),坐标错误会直接导致计算结果偏离。

内容的提问来源于stack exchange,提问作者Neiss

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 12:27:01