|  |  |  | @ -65,6 +65,21 @@ inline bool UbloxMsgParser::valid_so_far() { | 
			
		
	
		
			
				
					|  |  |  |  |   return true; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | inline uint16_t UbloxMsgParser::get_glonass_year(uint8_t N4, uint16_t Nt) { | 
			
		
	
		
			
				
					|  |  |  |  |   // convert time to year (conversion from A3.1.3)
 | 
			
		
	
		
			
				
					|  |  |  |  |   int J = 0; | 
			
		
	
		
			
				
					|  |  |  |  |   if (1 <= Nt && Nt <= 366) { | 
			
		
	
		
			
				
					|  |  |  |  |     J = 1; | 
			
		
	
		
			
				
					|  |  |  |  |   } else if (367 <= Nt && Nt <= 731) { | 
			
		
	
		
			
				
					|  |  |  |  |     J = 2; | 
			
		
	
		
			
				
					|  |  |  |  |   } else if (732 <= Nt && Nt <= 1096) { | 
			
		
	
		
			
				
					|  |  |  |  |     J = 3; | 
			
		
	
		
			
				
					|  |  |  |  |   } else if (1097 <= Nt && Nt <= 1461) { | 
			
		
	
		
			
				
					|  |  |  |  |     J = 4; | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  |   uint16_t year = 1996 + 4*(N4 -1) + (J - 1); | 
			
		
	
		
			
				
					|  |  |  |  |   return year; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | bool UbloxMsgParser::add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { | 
			
		
	
		
			
				
					|  |  |  |  |   int needed = needed_bytes(); | 
			
		
	
	
		
			
				
					|  |  |  | @ -103,9 +118,9 @@ std::pair<std::string, kj::Array<capnp::word>> UbloxMsgParser::gen_msg() { | 
			
		
	
		
			
				
					|  |  |  |  |   switch (ubx_message.msg_type()) { | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0107: | 
			
		
	
		
			
				
					|  |  |  |  |     return {"gpsLocationExternal", gen_nav_pvt(static_cast<ubx_t::nav_pvt_t*>(body))}; | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0213: | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0213: // UBX-RXM-SFRB (Broadcast Navigation Data Subframe)
 | 
			
		
	
		
			
				
					|  |  |  |  |     return {"ubloxGnss", gen_rxm_sfrbx(static_cast<ubx_t::rxm_sfrbx_t*>(body))}; | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0215: | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0215: // UBX-RXM-RAW (Multi-GNSS Raw Measurement Data)
 | 
			
		
	
		
			
				
					|  |  |  |  |     return {"ubloxGnss", gen_rxm_rawx(static_cast<ubx_t::rxm_rawx_t*>(body))}; | 
			
		
	
		
			
				
					|  |  |  |  |   case 0x0a09: | 
			
		
	
		
			
				
					|  |  |  |  |     return {"ubloxGnss", gen_mon_hw(static_cast<ubx_t::mon_hw_t*>(body))}; | 
			
		
	
	
		
			
				
					|  |  |  | @ -246,11 +261,144 @@ kj::Array<capnp::word> UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *m | 
			
		
	
		
			
				
					|  |  |  |  |   return kj::Array<capnp::word>(); | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | kj::Array<capnp::word> UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg) { | 
			
		
	
		
			
				
					|  |  |  |  |   if (msg->sv_id() == 255) { | 
			
		
	
		
			
				
					|  |  |  |  |     // data can be decoded before identifying the SV number, in this case 255
 | 
			
		
	
		
			
				
					|  |  |  |  |     // is returned, which means "unknown"  (ublox p32)
 | 
			
		
	
		
			
				
					|  |  |  |  |     return kj::Array<capnp::word>(); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   auto body = *msg->body(); | 
			
		
	
		
			
				
					|  |  |  |  |   assert(body.size() == 4); | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     std::string string_data; | 
			
		
	
		
			
				
					|  |  |  |  |     string_data.reserve(16); | 
			
		
	
		
			
				
					|  |  |  |  |     for (uint32_t word : body) { | 
			
		
	
		
			
				
					|  |  |  |  |       for (int i = 3; i >= 0; i--) | 
			
		
	
		
			
				
					|  |  |  |  |         string_data.push_back(word >> 8*i); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(string_data); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_string(&stream); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     int string_number = gl_string.string_number(); | 
			
		
	
		
			
				
					|  |  |  |  |     if (string_number > 5 || gl_string.idle_chip()) { | 
			
		
	
		
			
				
					|  |  |  |  |       // dont parse non immediate data, idle_chip == 0
 | 
			
		
	
		
			
				
					|  |  |  |  |       return kj::Array<capnp::word>(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // immediate data is the same within one superframe
 | 
			
		
	
		
			
				
					|  |  |  |  |     if (glonass_superframes[msg->sv_id()] != gl_string.superframe_number()) { | 
			
		
	
		
			
				
					|  |  |  |  |       glonass_strings[msg->sv_id()].clear(); | 
			
		
	
		
			
				
					|  |  |  |  |       glonass_superframes[msg->sv_id()] = gl_string.superframe_number(); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_strings[msg->sv_id()][string_number] = string_data; | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // publish if strings 1-5 have been collected
 | 
			
		
	
		
			
				
					|  |  |  |  |   if (glonass_strings[msg->sv_id()].size() != 5) { | 
			
		
	
		
			
				
					|  |  |  |  |     return kj::Array<capnp::word>(); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   MessageBuilder msg_builder; | 
			
		
	
		
			
				
					|  |  |  |  |   auto eph = msg_builder.initEvent().initUbloxGnss().initGlonassEphemeris(); | 
			
		
	
		
			
				
					|  |  |  |  |   eph.setSvId(msg->sv_id()); | 
			
		
	
		
			
				
					|  |  |  |  |   uint16_t current_day = 0; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // string number 1
 | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(glonass_strings[msg->sv_id()][1]); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_stream(&stream); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t::string_1_t* data = static_cast<glonass_t::string_1_t*>(gl_stream.data()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setP1(data->p1()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setTk(data->t_k()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setXVel(data->x_vel() * pow(2, -20)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setXAccel(data->x_accel() * pow(2, -30)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setX(data->x() * pow(2, -11)); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // string number 2
 | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(glonass_strings[msg->sv_id()][2]); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_stream(&stream); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t::string_2_t* data = static_cast<glonass_t::string_2_t*>(gl_stream.data()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setSvHealth(data->b_n()>>2); // MSB indicates health
 | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setP2(data->p2()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setTb(data->t_b()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setYVel(data->y_vel() * pow(2, -20)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setYAccel(data->y_accel() * pow(2, -30)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setY(data->y() * pow(2, -11)); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // string number 3
 | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(glonass_strings[msg->sv_id()][3]); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_stream(&stream); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t::string_3_t* data = static_cast<glonass_t::string_3_t*>(gl_stream.data()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setP3(data->p3()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setGammaN(data->gamma_n() * pow(2, -40)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setSvHealth(eph.getSvHealth() | data->l_n()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setZVel(data->z_vel() * pow(2, -20)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setZAccel(data->z_accel() * pow(2, -30)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setZ(data->z() * pow(2, -11)); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // string number 4
 | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(glonass_strings[msg->sv_id()][4]); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_stream(&stream); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t::string_4_t* data = static_cast<glonass_t::string_4_t*>(gl_stream.data()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     current_day = data->n_t(); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setTauN(data->tau_n() * pow(2, -30)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setDeltaTauN(data->delta_tau_n() * pow(2, -30)); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setAge(data->e_n()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setP4(data->p4()); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setSvURA(glonass_URA_lookup.at(data->f_t())); | 
			
		
	
		
			
				
					|  |  |  |  |     if (msg->sv_id() != data->n()) { | 
			
		
	
		
			
				
					|  |  |  |  |       LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setSvType(data->m()); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // string number 5
 | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     kaitai::kstream stream(glonass_strings[msg->sv_id()][5]); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t gl_stream(&stream); | 
			
		
	
		
			
				
					|  |  |  |  |     glonass_t::string_5_t* data = static_cast<glonass_t::string_5_t*>(gl_stream.data()); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     // string5 parsing is only needed to get the year, this can be removed and
 | 
			
		
	
		
			
				
					|  |  |  |  |     // the year can be fetched later in laika (note rollovers and leap year)
 | 
			
		
	
		
			
				
					|  |  |  |  |     uint8_t n_4 = data->n_4(); | 
			
		
	
		
			
				
					|  |  |  |  |     uint16_t year = get_glonass_year(n_4, current_day); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     uint16_t last_leap_year = 1996 + 4*(n_4-1); | 
			
		
	
		
			
				
					|  |  |  |  |     uint16_t days_till_this_year = (year - last_leap_year)*365; | 
			
		
	
		
			
				
					|  |  |  |  |     if (days_till_this_year != 0) { | 
			
		
	
		
			
				
					|  |  |  |  |       days_till_this_year++; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setYear(year); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setDayInYear(current_day - days_till_this_year); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setHour((eph.getTk()>>7) & 0x1F); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setMinute((eph.getTk()>>1) & 0x3F); | 
			
		
	
		
			
				
					|  |  |  |  |     eph.setSecond((eph.getTk() & 0x1) * 30); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   glonass_strings[msg->sv_id()].clear(); | 
			
		
	
		
			
				
					|  |  |  |  |   return capnp::messageToFlatArray(msg_builder); | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | kj::Array<capnp::word> UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) { | 
			
		
	
		
			
				
					|  |  |  |  |   switch (msg->gnss_id()) { | 
			
		
	
		
			
				
					|  |  |  |  |     case ubx_t::gnss_type_t::GNSS_TYPE_GPS: | 
			
		
	
		
			
				
					|  |  |  |  |       return parse_gps_ephemeris(msg); | 
			
		
	
		
			
				
					|  |  |  |  |     case ubx_t::gnss_type_t::GNSS_TYPE_GLONASS: | 
			
		
	
		
			
				
					|  |  |  |  |       return parse_glonass_ephemeris(msg); | 
			
		
	
		
			
				
					|  |  |  |  |     default: | 
			
		
	
		
			
				
					|  |  |  |  |       return kj::Array<capnp::word>(); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
	
		
			
				
					|  |  |  | 
 |