import math from triangulation import ( PropagationModel, ReceiverSignal, SPEED_OF_LIGHT_M_S, build_result_payload, rssi_to_distance_m, solve_from_signal_amplitudes, solve_three_sphere_intersection, Sphere, ) def _distance_to_rssi(distance_m: float, frequency_hz: float, model: PropagationModel) -> float: fspl_ref_db = 20.0 * math.log10( 4.0 * math.pi * model.reference_distance_m * frequency_hz / SPEED_OF_LIGHT_M_S ) rx_power_at_ref_dbm = ( model.tx_power_dbm + model.tx_gain_dbi + model.rx_gain_dbi - fspl_ref_db ) return rx_power_at_ref_dbm - 10.0 * model.path_loss_exponent * math.log10( distance_m / model.reference_distance_m ) def test_exact_three_sphere_intersection(): true_point = (2.0, 3.0, 4.0) spheres = [ Sphere(center=(0.0, 0.0, 0.0), radius=math.dist(true_point, (0.0, 0.0, 0.0))), Sphere(center=(10.0, 0.0, 0.0), radius=math.dist(true_point, (10.0, 0.0, 0.0))), Sphere(center=(0.0, 8.0, 0.0), radius=math.dist(true_point, (0.0, 8.0, 0.0))), ] result = solve_three_sphere_intersection(spheres=spheres, z_preference="positive") assert result.exact assert math.isclose(result.point[0], true_point[0], abs_tol=1e-9, rel_tol=0.0) assert math.isclose(result.point[1], true_point[1], abs_tol=1e-9, rel_tol=0.0) assert math.isclose(result.point[2], true_point[2], abs_tol=1e-9, rel_tol=0.0) def test_frequency_affects_distance_conversion(): model = PropagationModel(tx_power_dbm=20.0) amplitude = -60.0 low_freq_distance = rssi_to_distance_m(amplitude, 433e6, model) high_freq_distance = rssi_to_distance_m(amplitude, 2.4e9, model) assert high_freq_distance < low_freq_distance def test_pipeline_from_signal_to_payload(): model = PropagationModel(tx_power_dbm=18.0, path_loss_exponent=2.0) true_point = (3.0, 2.0, 1.5) receiver_centers = [ ("r0", (0.0, 0.0, 0.0)), ("r1", (8.0, 0.0, 0.0)), ("r2", (0.0, 7.0, 0.0)), ] freqs = [915e6, 920e6, 930e6] signals = [] for (receiver_id, center), freq in zip(receiver_centers, freqs): distance = math.dist(true_point, center) amplitude = _distance_to_rssi(distance, freq, model) signals.append( ReceiverSignal( receiver_id=receiver_id, center=center, amplitude_dbm=amplitude, frequency_hz=freq, ) ) result, spheres = solve_from_signal_amplitudes( signals=signals, model=model, z_preference="positive" ) payload = build_result_payload(signals, spheres, result, model) assert result.exact assert math.isclose(result.point[0], true_point[0], abs_tol=1e-7, rel_tol=0.0) assert math.isclose(result.point[1], true_point[1], abs_tol=1e-7, rel_tol=0.0) assert math.isclose(result.point[2], true_point[2], abs_tol=1e-7, rel_tol=0.0) assert "position" in payload assert len(payload["receivers"]) == 3