chirpstack / chirpstack-concentratord

Concentrator HAL daemon for LoRa gateways.
https://www.chirpstack.io/
MIT License
76 stars 54 forks source link

Chirpstack-consertratord-sx1302 backend - Fine Timestamp is missing from uplink event? #65

Closed LouneCode closed 1 year ago

LouneCode commented 1 year ago

Summary

I have investigated the consentratord backend code and found that the Fine timestamp information basis on PPS signal missing from current implementation?

Consertratord-sx1302 implementation has been build on Semtechs's HAL library (libloragw). Sx1302_hal library includes code for Fine timestamp implementation (for hardware with PPS signal). It seems that the Fine Timestamp solution can be added to consertratord-sx1302 backend quite easily.

Similar cases can be found from following issues:

 

What is the use-case?

A Fine Timestamp is used to get the TDoA geolocation of the transmitter (sensor).

Implementation description - Solution proposal

Add Fine Timestamp information to the up-event of the concentratord-sx1302 backend.

 

1) consertratord.toml

consertratord.toml file found in the same folder where chirpstack-concentratord-sx1302 executable is. Following lines to be added into consertratord.toml configuration file for enabling Fine Timestamp functionality:

 

# LoRa gateway configuration.
[gateway]

  # Antenna gain (dB).
  antenna_gain=0

  # Public LoRaWAN network.
  lorawan_public=true

  # Gateway vendor / model.
  #
  # This configures various vendor and model specific settings like the min / max
  # frequency and TX gain table.
  #model="rak_2287_eu868"
  model="semtech_sx1302css868gw1_eu868"

  # Gateway vendor / model flags.
  #
  # Flag can be used to configure additional vendor / model features. The
  # following flags can be used:
  #
  #   Global flags:
  #     GNSS - Enable GNSS / GPS support
  #     USB  - Use USB for concentrator communication (default is SPI)
  model_flags=["USB","GNSS"]

  # Time fallback.
  #
  # In case the gateway does not have a GNSS module or is unable to aquire a
  # GNSS fix, use the system-time for setting the 'time' field on RX.
  time_fallback_enabled=true

  #Fine Timestamp
  #mode: HIGH_CAPACITY or ALL_SF
  [gateway.fine_timestamp]
    enable=true
    mode="ALL_SF"

 

2) chirpstack-concentratord-sx1302/src/handler/uplink.rs

Added frame.ftime and frame.ftime_received fields and needed formating to info!() macro.

                    info!(
                        "Frame received, uplink_id: {}, count_us: {}, freq: {}, bw: {}, mod: {:?}, dr: {:?} fts: {:0>9}, fts_recv: {} ",
                        rx_info.uplink_id,
                        frame.count_us,
                        frame.freq_hz,
                        frame.bandwidth,
                        frame.modulation,
                        frame.datarate,
                        frame.ftime,
                        frame.ftime_received
                    );

Now consertratord-sx1302 log shows Fine Timestamp information "...fts: 143869299, fts_recv: true". The Fts field is a nano secons since last trailing edge of the PPS signal of the GPS module. This nano second information is need for aproximation of the geolocation.

2023-06-11T19:13:54.123Z INFO  [libconcentratord::events] Publishing stats event, rx_received: 3, rx_received_ok: 1, tx_received: 0, tx_emitted: 0
2023-06-11T19:14:13.171Z INFO  [chirpstack_concentratord_sx1302::handler::uplink] Frame received, uplink_id: 1604220501, count_us: 3057298334, freq: 867300000, bw: 125000, mod: LoRa, dr: SF7 fts: 108051544, fts_recv: true
2023-06-11T19:14:24.124Z INFO  [libconcentratord::events] Publishing stats event, rx_received: 3, rx_received_ok: 1, tx_received: 0, tx_emitted: 0
2023-06-11T19:14:43.209Z INFO  [chirpstack_concentratord_sx1302::handler::uplink] Frame received, uplink_id: 2494718995, count_us: 3087334149, freq: 868100000, bw: 125000, mod: LoRa, dr: SF7 fts: 143869299, fts_recv: true
2023-06-11T19:14:54.130Z INFO  [libconcentratord::events] Publishing stats event, rx_received: 1, rx_received_ok: 1, tx_received: 0, tx_emitted: 0
2

 

3) chirpstack-concentratord-sx1302/src/wrapper/mod.rs

pub fn uplink_to_proto() returns Result. fine_time_since_gps_epoch structe is added to gw::UplinkFrame. packet.ftime field holds precise receiving time information if Fine Timestamp property is enabled on consertratord-sx1302.

            fine_time_since_gps_epoch: match packet.ftime_received {
                true => Some(prost_types::Duration {
                            seconds: 0i64,
                            nanos: packet.ftime as i32
                        }),
                false => None
            },

And here is whole changed uplink_to_proto() function.


pub fn uplink_to_proto(
    gateway_id: &[u8],
    packet: &hal::RxPacket,
    time_fallback: bool,
) -> Result<gw::UplinkFrame> {
    let mut rng = rand::thread_rng();
    let uplink_id: u32 = rng.gen();

    Ok(gw::UplinkFrame {
        phy_payload: packet.payload[..packet.size as usize].to_vec(),
        tx_info: Some(gw::UplinkTxInfo {
            frequency: packet.freq_hz,
            modulation: Some(gw::Modulation {
                parameters: match packet.modulation {
                    hal::Modulation::LoRa => {
                        Some(gw::modulation::Parameters::Lora(gw::LoraModulationInfo {
                            bandwidth: packet.bandwidth,
                            spreading_factor: match packet.datarate {
                                hal::DataRate::SF5 => 5,
                                hal::DataRate::SF6 => 6,
                                hal::DataRate::SF7 => 7,
                                hal::DataRate::SF8 => 8,
                                hal::DataRate::SF9 => 9,
                                hal::DataRate::SF10 => 10,
                                hal::DataRate::SF11 => 11,
                                hal::DataRate::SF12 => 12,
                                _ => return Err(anyhow!("unexpected spreading-factor")),
                            },
                            code_rate: match packet.coderate {
                                hal::CodeRate::LoRa4_5 => gw::CodeRate::Cr45,
                                hal::CodeRate::LoRa4_6 => gw::CodeRate::Cr46,
                                hal::CodeRate::LoRa4_7 => gw::CodeRate::Cr47,
                                hal::CodeRate::LoRa4_8 => gw::CodeRate::Cr48,
                                hal::CodeRate::Undefined => gw::CodeRate::CrUndefined,
                            }
                            .into(),
                            ..Default::default()
                        }))
                    }
                    hal::Modulation::FSK => {
                        Some(gw::modulation::Parameters::Fsk(gw::FskModulationInfo {
                            datarate: match packet.datarate {
                                hal::DataRate::FSK(v) => v * 1000,
                                _ => return Err(anyhow!("unexpected datarate")),
                            },
                            ..Default::default()
                        }))
                    }
                    hal::Modulation::Undefined => None,
                },
            }),
        }),
        rx_info: Some(gw::UplinkRxInfo {
            uplink_id,
            context: packet.count_us.to_be_bytes().to_vec(),
            gateway_id: hex::encode(gateway_id),
            rssi: packet.rssis as i32,
            snr: packet.snr,
            channel: packet.if_chain as u32,
            rf_chain: packet.rf_chain as u32,
            time: match gps::cnt2time(packet.count_us) {
                Ok(v) => {
                    let v = v.duration_since(UNIX_EPOCH).unwrap();
                    Some(prost_types::Timestamp {
                        seconds: v.as_secs() as i64,
                        nanos: v.subsec_nanos() as i32,
                    })
                }
                Err(err) => {
                    debug!(
                        "Could not get GPS time, uplink_id: {}, error: {}",
                        uplink_id, err
                    );

                    if time_fallback {
                        Some(prost_types::Timestamp::from(SystemTime::now()))
                    } else {
                        None
                    }
                }
            },
            time_since_gps_epoch: match gps::cnt2epoch(packet.count_us) {
                Ok(v) => Some(prost_types::Duration {
                    seconds: v.as_secs() as i64,
                    nanos: v.subsec_nanos() as i32,
                }),
                Err(err) => {
                    debug!(
                        "Could not get GPS epoch, uplink_id: {}, error: {}",
                        uplink_id, err
                    );
                    None
                }
            },           
            fine_time_since_gps_epoch: match packet.ftime_received {
                true => Some(prost_types::Duration {
                            seconds: 0i64,
                            nanos: packet.ftime as i32
                        }),
                false => None
            },
            crc_status: match packet.status {
                hal::CRC::CRCOk => gw::CrcStatus::CrcOk,
                hal::CRC::BadCRC => gw::CrcStatus::BadCrc,
                hal::CRC::NoCRC | hal::CRC::Undefined => gw::CrcStatus::NoCrc,
            }
            .into(),
            ..Default::default()
        }),
        ..Default::default()
    })
}

 

I wrote a simple Rust zeromq client program for printing up-events of the consertratord-sx1302 backend. Now Fine Timestamp information found in fine_time_since_gps_epoch: Some(Duration { seconds: 0, nanos: 48290698 }) Structure as follows. In this example second part is allway zere. Only nano second part matters for the geolocation calculations.


Topic: up
phy_payload: [64, 97, 158, 199, 1, 128, 245, 148, 3, 132, 29, 75, 233, 12, 129, 60, 199, 85, 34, 198, 216, 151, 189]
phy_payload: UplinkRxInfo { gateway_id: "0016c001ff1f11ddd", uplink_id: 1836980030, time: Some(Timestamp { seconds: 1686508758, nanos: 116930490 }), time_since_gps_epoch: None, fine_time_since_gps_epoch: Some(Duration { seconds: 0, nanos: 48290698 }), rssi: -99, snr: 12.25, channel: 3, rf_chain: 0, board: 0, antenna: 0, location: None, context: [57, 90, 153, 82], metadata: {}, crc_status: CrcOk }
Topic: up
phy_payload: [64, 97, 158, 199, 1, 128, 246, 148, 3, 86, 239, 135, 219, 156, 240, 225, 92, 184, 205, 19, 254, 61, 94]
phy_payload: UplinkRxInfo { gateway_id: "0016c001ff1f1ddd", uplink_id: 2518488021, time: Some(Timestamp { seconds: 1686508788, nanos: 161273198 }), time_since_gps_epoch: None, fine_time_since_gps_epoch: Some(Duration { seconds: 0, nanos: 91829739 }), rssi: -42, snr: 11.25, channel: 1, rf_chain: 1, board: 0, antenna: 0, location: None, context: [59, 37, 6, 226], metadata: {}, crc_status: CrcOk }
Topic: up
phy_payload: [64, 97, 158, 199, 1, 128, 247, 148, 3, 167, 7, 251, 70, 240, 160, 220, 53, 167, 110, 184, 203, 240, 19]
phy_payload: UplinkRxInfo { gateway_id: "0016c001ff1f1ddd", uplink_id: 1110056337, time: Some(Timestamp { seconds: 1686508818, nanos: 194757382 }), time_since_gps_epoch: None, fine_time_since_gps_epoch: Some(Duration { seconds: 0, nanos: 134425134 }), rssi: -42, snr: 9.5, channel: 2, rf_chain: 1, board: 0, antenna: 0, location: None, context: [60, 239, 112, 194], metadata: {}, crc_status: CrcOk }

Can you implement this by yourself and make a pull request?

Yes ! can. I have a smoke tested this solution proposal and it seems to work . But first let's wait for the review and comments of the idea....

brocaar commented 1 year ago

Hi @LouneCode please do create a pull-request for this :+1:

I think we can use the seconds part of the time_since_gps_epoch for the fine_time_since_gps_epoch + the packet.ftime as nanos, then we have the full duration since GPS epoch.

LouneCode commented 1 year ago

OK, I'll try make a pull-request with the suggested improvements.

brocaar commented 1 year ago

Thanks! :)