Drive the bench CAM beacon round a street loop in St. Georg

The bench transmitter sent a parked car: one fixed position, speed 0,
no heading, a CAM every second. It now simulates a car driving a loop
through six waypoints around Berliner Tor on the real streets, which
makes it a moving target for the app's map and the use case detection
without taking a car out.

The route is generated, not hand-traced. tools/make_route.py asks the
OSRM demo server for a driving route through the waypoints and back to
the first, thins the 371 street points to 103 (none more than 1.5 m off
the line), and writes main/route_points.h. It also saves OSRM's answer
(--offline rebuilds from it) and a map page to check the route before
flashing. Each waypoint is sent with the direction towards the next one:
without it, points on divided roads such as Beim Strohhause snapped to
the opposite carriageway and the loop came out at 8.4 km of U-turns.
With it the loop is 5.2 km, still including two turn-round detours
that OSRM needs to reach the waypoints legally (Borgfelder Strasse /
Anckelmannsplatz, and Nagelsweg / Norderstrasse / Repsoldstrasse).
Route data (c) OpenStreetMap contributors, ODbL.

main/route.c moves the car along the points. It cruises at 50 km/h and
limits each bend to the speed that keeps sideways acceleration at
2 m/s^2, so a junction turn is taken at about 15 km/h and a gentle curve
barely slows it; braking (2 m/s^2) and acceleration (1.5 m/s^2) are
planned across as many points as a bend needs. A simulated lap on the
host is 5.16 km in 7.7 min, averaging 40 km/h.

CAMs now follow the EN 302 637-2 generation rules instead of a fixed
1 Hz: checked every 100 ms, sent on a heading change over 4 degrees, a
move over 4 m, a speed change over 0.5 m/s, or after 1 s - about 3 Hz
at 50 km/h. generationDeltaTime is milliseconds since boot. The
GeoNetworking source position vector now carries the same speed and
heading as the CAM instead of zeros.

NOTES.md gains build and flash steps (including reading a board's app
descriptor first, since both firmwares name their image
obu_firmware.bin) and a section on the simulated drive. The pointer to
docs/04-transmit-setup.md is corrected: that file is not in the repo.

Flashed to the COM8 board and checked on its console: it starts driving
on power-up and sends CAMs with changing position, speed and heading.
Not yet received over the air.
This commit is contained in:
Ashin Walpola
2026-09-16 14:21:44 +02:00
parent 01204a2c22
commit 21e01499d8
11 changed files with 6577 additions and 42 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
# wifi_patches.c is intentionally NOT in this list anymore - superseded by
# tx_custom.c (see that file for why). Left on disk, unused, for history.
idf_component_register(
SRCS "main.c" "denm.c" "cam.c" "geonet.c" "dot11p.c" "tx_custom.c"
SRCS "main.c" "denm.c" "cam.c" "geonet.c" "dot11p.c" "tx_custom.c" "route.c"
INCLUDE_DIRS "."
REQUIRES esp_event esp_netif nvs_flash driver esp_phy
REQUIRES esp_event esp_timer esp_netif nvs_flash driver esp_phy
PRIV_REQUIRES esp_wifi
)
+7 -6
View File
@@ -4,6 +4,7 @@
int geonet_wrap_shb(const uint8_t *its_payload, int its_len,
const uint8_t mac[6], uint8_t station_type,
int32_t latitude_tenmicrodeg, int32_t longitude_tenmicrodeg,
uint16_t speed_cm_s, uint16_t heading_ddeg,
uint16_t btp_dest_port,
uint8_t *out, size_t out_len)
{
@@ -68,12 +69,12 @@ int geonet_wrap_shb(const uint8_t *its_payload, int its_len,
uint32_t lon_u = (uint32_t)longitude_tenmicrodeg;
*p++ = (uint8_t)(lon_u >> 24); *p++ = (uint8_t)(lon_u >> 16);
*p++ = (uint8_t)(lon_u >> 8); *p++ = (uint8_t)(lon_u);
// PAI(1 bit) + Speed(15 bits), packed into 2 bytes: 0 = PAI false,
// speed 0 - which is actually correct semantics for a STATIONARY
// vehicle beacon, not just a placeholder.
*p++ = 0x00; *p++ = 0x00;
// Heading (16 bits, 0.1 degree units): 0 = due north / unavailable
*p++ = 0x00; *p++ = 0x00;
// PAI(1 bit) + Speed(15 bits, signed, 0.01 m/s), packed into 2 bytes. PAI stays 0: the
// position has no accuracy estimate behind it.
uint16_t spd = speed_cm_s > 0x7FFF ? 0x7FFF : speed_cm_s;
*p++ = (uint8_t)(spd >> 8); *p++ = (uint8_t)(spd & 0xFF);
// Heading (16 bits, 0.1 degree units, clockwise from north)
*p++ = (uint8_t)(heading_ddeg >> 8); *p++ = (uint8_t)(heading_ddeg & 0xFF);
// Reserved (4 bytes) - clause 9.8.4: the SHB extended header is the 24-byte Source Position
// Vector FOLLOWED BY a 4-byte reserved field (media-dependent data), 28 bytes in total. These
// four bytes were missing, which is why a standards-compliant receiver read our CAM payload's
+5
View File
@@ -31,6 +31,10 @@
// working (same extended header shape as CAM). Fine for a single-vehicle
// beacon; revisit if you need real multi-hop forwarding later.
//
// `speed_cm_s` (0.01 m/s) and `heading_ddeg` (0.1 deg) also go into the Source Long Position
// Vector; pass the same values as the CAM's high-frequency container. Speed is a 15-bit field, so
// values above 32767 are clamped.
//
// `btp_dest_port` is the BTP-B destination port for the service being carried
// (ETSI TS 103 248): 2001 = CAM, 2002 = DENM, 2003 = MAPEM, 2004 = SPATEM, ...
//
@@ -38,6 +42,7 @@
int geonet_wrap_shb(const uint8_t *its_payload, int its_len,
const uint8_t mac[6], uint8_t station_type,
int32_t latitude_tenmicrodeg, int32_t longitude_tenmicrodeg,
uint16_t speed_cm_s, uint16_t heading_ddeg,
uint16_t btp_dest_port,
uint8_t *out, size_t out_len);
+82 -30
View File
@@ -1,7 +1,11 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "esp_wifi.h"
#include "esp_event.h"
@@ -14,13 +18,17 @@
#include "geonet.h"
#include "dot11p.h"
#include "tx_custom.h"
#include "route.h"
#include "route_points.h"
static const char *TAG = "obu-tx";
// CAM beacon: transmit a Cooperative Awareness Message every TX_INTERVAL_MS,
// unconditionally (no hazard-light gating - CAM is a continuous beacon, unlike
// the event-triggered DENM). Matches the working Rust reference
// (esp32-c_its-companion, feat/tx-cam), which beacons CAM on 5900 MHz.
// CAM beacon for a simulated car driving round a block in Hamburg (see route.c). The CAM
// generation rules follow ETSI EN 302 637-2 clause 6.1.3: every CHECK_INTERVAL_MS the car's state
// is compared with the last CAM sent, and a new CAM goes out when the heading changed by more than
// 4 degrees, the position by more than 4 m, the speed by more than 0.5 m/s, or 1 s has passed.
// There is no hazard-light gating - CAM is a continuous beacon, unlike the event-triggered DENM.
// Transmits on 5900 MHz like the working Rust reference (esp32-c_its-companion, feat/tx-cam).
// ISOLATION TEST for whether tx_custom.c is the blocker.
// 1 = transmit via the STANDARD, well-tested esp_wifi_80211_tx() using a
@@ -55,15 +63,19 @@ static const char *TAG = "obu-tx";
#define VEHICLE_LENGTH_DM 40 // VehicleLengthValue, 10cm steps (4.0 m)
#define VEHICLE_WIDTH_DM 18 // VehicleWidth, 10cm steps (1.8 m)
#define BTP_PORT_CAM 2001 // BTP-B destination port for CAM (ETSI TS 103 248)
#define TX_INTERVAL_MS 1000 // CAM beacon period (1 Hz; ITS allows 1-10 Hz)
#define CHECK_INTERVAL_MS 100 // T_CheckCamGen: how often the generation rules are evaluated
#define CAM_MAX_INTERVAL_MS 1000 // T_GenCamMax: a CAM goes out at least this often
#define CAM_HEADING_DDEG 40 // > 4 degrees heading change triggers a CAM
#define CAM_POSITION_M 4.0 // > 4 m position change triggers a CAM
#define CAM_SPEED_CM_S 50 // > 0.5 m/s speed change triggers a CAM
// Bench location, hardcoded since there's no GNSS module wired in yet and
// the unit is genuinely stationary here: 53°33'16.8"N 10°01'20.6"E, in
// 1/10-microdegree units (decimal_degrees * 10,000,000). Replace with real
// GNSS output once you have a fix source; until then this beats 0/0
// ("Null Island"), which is an obvious placeholder-tell on any map.
#define BENCH_LATITUDE_TENMICRODEG 535546667
#define BENCH_LONGITUDE_TENMICRODEG 100223889
// ---- Simulated drive ----
// route_points (main/route_points.h) is the street geometry of a driving loop through six waypoints
// in St. Georg, generated by tools/make_route.py from OpenStreetMap via OSRM. To change the route,
// edit WAYPOINTS in that script and rerun it. No GNSS is wired in; replace with real fixes once
// there is one.
#define CRUISE_MPS (50.0 / 3.6) // 50 km/h, the urban limit
#define MIN_CORNER_MPS (10.0 / 3.6) // slowest the car goes, for hairpins and U-turns
// Single source of truth for the pseudonym/link-layer address: used both as
// the 802.11 source MAC (Addr2) and as GN_ADDR's MID field, since the GN
@@ -78,33 +90,28 @@ static const uint8_t pseudonym_mac[6] = {0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
extern void phy_11p_set(int enable, int unused);
extern void phy_change_channel(int freq_mhz, int bw_mode, int sec_chan_offset, int unused);
static void send_cam(void)
static void send_cam(const route_state_t *car, uint16_t gen_delta)
{
// GenerationDeltaTime is TimestampIts mod 65536 (ms). No RTC/GNSS time here,
// so use a free-running ms counter that advances one beacon-interval per
// send. It wraps at 65536, which is exactly the field's defined behaviour.
static uint16_t gen_delta = 0;
uint8_t frame[300];
cam_fields_t fields = {
.station_id = STATION_ID,
.station_type = STATION_TYPE,
.generation_delta_time = gen_delta,
.latitude_tenmicrodeg = BENCH_LATITUDE_TENMICRODEG,
.longitude_tenmicrodeg = BENCH_LONGITUDE_TENMICRODEG,
.speed_cm_s = 0, // stationary
.heading_ddeg = 3601, // HeadingValue unavailable (no heading source)
.latitude_tenmicrodeg = car->latitude_tenmicrodeg,
.longitude_tenmicrodeg = car->longitude_tenmicrodeg,
.speed_cm_s = car->speed_cm_s,
.heading_ddeg = car->heading_ddeg,
.vehicle_length_dm = VEHICLE_LENGTH_DM,
.vehicle_width_dm = VEHICLE_WIDTH_DM,
};
gen_delta += TX_INTERVAL_MS;
uint8_t cam_payload[96];
int cam_len = cam_encode(&fields, cam_payload, sizeof(cam_payload));
uint8_t gn_payload[160];
int gn_len = geonet_wrap_shb(cam_payload, cam_len, pseudonym_mac, STATION_TYPE,
BENCH_LATITUDE_TENMICRODEG, BENCH_LONGITUDE_TENMICRODEG,
car->latitude_tenmicrodeg, car->longitude_tenmicrodeg,
car->speed_cm_s, car->heading_ddeg,
BTP_PORT_CAM, gn_payload, sizeof(gn_payload));
// qos=false for the standard-TX path (esp_wifi_80211_tx accepts only non-QoS
@@ -127,7 +134,10 @@ static void send_cam(void)
if (err != ESP_OK) {
ESP_LOGW(TAG, "esp_wifi_80211_tx (standard) failed: %d", err);
} else {
ESP_LOGI(TAG, "CAM sent via STANDARD tx (%d bytes) @ %d MHz genDeltaT=%u", frame_len, TX_FREQ_MHZ, gen_delta);
ESP_LOGI(TAG, "CAM sent (%d bytes) @ %d MHz genDeltaT=%u pos=%.7f,%.7f %.1f km/h heading %.1f pt%d",
frame_len, TX_FREQ_MHZ, gen_delta,
car->latitude_tenmicrodeg / 1e7, car->longitude_tenmicrodeg / 1e7,
car->speed_cm_s * 0.036, car->heading_ddeg / 10.0, car->segment + 1);
}
#else
// tx_custom path: submits to the driver's internal HMAC TX path,
@@ -151,12 +161,49 @@ static void send_cam(void)
}
}
static bool cam_due(const route_state_t *car, const route_state_t *last, int64_t since_last_ms)
{
if (since_last_ms >= CAM_MAX_INTERVAL_MS) {
return true;
}
int dh = abs((int)car->heading_ddeg - (int)last->heading_ddeg);
if (dh > 1800) {
dh = 3600 - dh;
}
if (dh > CAM_HEADING_DDEG) {
return true;
}
if (abs((int)car->speed_cm_s - (int)last->speed_cm_s) > CAM_SPEED_CM_S) {
return true;
}
// Flat-earth distance is plenty for a 4 m threshold.
double north_m = (car->latitude_tenmicrodeg - last->latitude_tenmicrodeg) * 0.0111194930;
double east_m = (car->longitude_tenmicrodeg - last->longitude_tenmicrodeg) * 0.0111194930
* cos(car->latitude_tenmicrodeg / 1e7 * M_PI / 180.0);
return north_m * north_m + east_m * east_m > CAM_POSITION_M * CAM_POSITION_M;
}
static void tx_task(void *arg)
{
route_state_t car;
route_state_t last_sent;
int64_t last_sent_ms = 0;
bool sent_any = false;
TickType_t wake = xTaskGetTickCount();
route_step(0.0, &car);
while (1) {
// CAM is a continuous beacon - send every interval, unconditionally.
send_cam();
vTaskDelay(pdMS_TO_TICKS(TX_INTERVAL_MS));
int64_t now_ms = esp_timer_get_time() / 1000;
if (!sent_any || cam_due(&car, &last_sent, now_ms - last_sent_ms)) {
// GenerationDeltaTime is TimestampIts mod 65536 (ms). No real clock here, so use
// milliseconds since boot, which advances at the right rate.
send_cam(&car, (uint16_t)now_ms);
last_sent = car;
last_sent_ms = now_ms;
sent_any = true;
}
vTaskDelayUntil(&wake, pdMS_TO_TICKS(CHECK_INTERVAL_MS));
route_step(CHECK_INTERVAL_MS / 1000.0, &car);
}
}
@@ -255,8 +302,13 @@ void app_main(void)
phy_change_channel(TX_FREQ_MHZ, 1, 0, 0);
ESP_LOGI(TAG, "phy_change_channel returned");
ESP_LOGW(TAG, "OCB @ %d MHz - CAM beacon armed, transmitting every %d ms",
TX_FREQ_MHZ, TX_INTERVAL_MS);
if (route_init(route_points, sizeof(route_points) / sizeof(route_points[0]),
CRUISE_MPS, MIN_CORNER_MPS) != 0) {
ESP_LOGE(TAG, "route_init failed - check route_points");
return;
}
ESP_LOGW(TAG, "OCB @ %d MHz - CAM beacon armed, driving a %d-point street loop",
TX_FREQ_MHZ, (int)(sizeof(route_points) / sizeof(route_points[0])));
xTaskCreate(tx_task, "tx_task", 4096, NULL, 5, NULL);
}
+125
View File
@@ -0,0 +1,125 @@
#include "route.h"
#include <math.h>
#define DEG_TO_RAD (M_PI / 180.0)
#define METRES_PER_DEG 111194.93 // mean Earth radius 6371 km; a block is small enough for a flat projection
#define ACCEL_MPS2 1.5 // pulling away from a corner
#define DECEL_MPS2 2.0 // braking ahead of a corner
#define LATERAL_MPS2 2.0 // sideways acceleration a normal driver takes a bend at
#define STRAIGHT_DEG 3.0 // kinks gentler than this are digitising noise, not bends
#define BEND_SPAN_M 15.0 // longest segment counted towards a bend's radius (see route_init)
static const route_point_t *s_pts;
static int s_n;
static double s_len[ROUTE_MAX_POINTS]; // segment i runs from point i to point (i+1) % n
static double s_bearing_deg[ROUTE_MAX_POINTS];
static double s_corner_mps[ROUTE_MAX_POINTS]; // speed limit at point i, where segment i starts
static double s_cruise_mps;
static int s_seg;
static double s_pos_m; // distance along the current segment
static double segment_speed(int seg, double pos_m)
{
double v = s_cruise_mps;
double pull_away = sqrt(s_corner_mps[seg] * s_corner_mps[seg] + 2.0 * ACCEL_MPS2 * pos_m);
int next = (seg + 1) % s_n;
double braking = sqrt(s_corner_mps[next] * s_corner_mps[next]
+ 2.0 * DECEL_MPS2 * (s_len[seg] - pos_m));
if (pull_away < v) v = pull_away;
if (braking < v) v = braking;
return v;
}
int route_init(const route_point_t *points, int n, double cruise_mps, double min_corner_mps)
{
if (n < 2 || n > ROUTE_MAX_POINTS) {
return -1;
}
s_pts = points;
s_n = n;
s_cruise_mps = cruise_mps;
for (int i = 0; i < n; i++) {
const route_point_t *a = &points[i];
const route_point_t *b = &points[(i + 1) % n];
double mid_lat = (a->latitude_tenmicrodeg + (double)b->latitude_tenmicrodeg) / 2e7;
double north_m = (b->latitude_tenmicrodeg - a->latitude_tenmicrodeg) / 1e7 * METRES_PER_DEG;
double east_m = (b->longitude_tenmicrodeg - a->longitude_tenmicrodeg) / 1e7 * METRES_PER_DEG
* cos(mid_lat * DEG_TO_RAD);
s_len[i] = sqrt(north_m * north_m + east_m * east_m);
if (s_len[i] < 0.01) {
return -1;
}
double bearing = atan2(east_m, north_m) / DEG_TO_RAD;
s_bearing_deg[i] = bearing < 0 ? bearing + 360.0 : bearing;
}
// Speed limit at each point from how tight the bend there is. A polyline bend of angle theta
// between segments of length L approximates an arc of radius L / theta, and a car takes a
// radius R at sqrt(a_lat * R). L is capped at BEND_SPAN_M: at a junction the two streets can be
// hundreds of metres long, but the car still turns within the width of the crossing.
for (int i = 0; i < n; i++) {
double turn = fabs(s_bearing_deg[i] - s_bearing_deg[(i + n - 1) % n]);
if (turn > 180.0) {
turn = 360.0 - turn;
}
double v = cruise_mps;
if (turn > STRAIGHT_DEG) {
double span = s_len[(i + n - 1) % n] < s_len[i] ? s_len[(i + n - 1) % n] : s_len[i];
if (span > BEND_SPAN_M) {
span = BEND_SPAN_M;
}
v = sqrt(LATERAL_MPS2 * span / (turn * DEG_TO_RAD));
}
if (v > cruise_mps) v = cruise_mps;
if (v < min_corner_mps) v = min_corner_mps;
s_corner_mps[i] = v;
}
// A point's limit also has to respect the bends after it (the car must be able to brake for
// them within the segments in between) and before it (it can only have sped up so much since).
// segment_speed only looks at the two ends of a segment, so settle this here. Limits only ever
// go down, so repeating the two passes until nothing changes terminates.
for (int changed = 1; changed;) {
changed = 0;
for (int k = 0; k < 2 * n; k++) {
int i = (2 * n - 1 - k) % n; // backwards: braking
int next = (i + 1) % n;
double v = sqrt(s_corner_mps[next] * s_corner_mps[next] + 2.0 * DECEL_MPS2 * s_len[i]);
if (v < s_corner_mps[i] - 1e-9) { s_corner_mps[i] = v; changed = 1; }
}
for (int k = 0; k < 2 * n; k++) {
int i = k % n; // forwards: accelerating
int next = (i + 1) % n;
double v = sqrt(s_corner_mps[i] * s_corner_mps[i] + 2.0 * ACCEL_MPS2 * s_len[i]);
if (v < s_corner_mps[next] - 1e-9) { s_corner_mps[next] = v; changed = 1; }
}
}
s_seg = 0;
s_pos_m = 0.0;
return 0;
}
void route_step(double dt_s, route_state_t *out)
{
// Advance with the speed at the start of the step; at 100 ms steps the error is well under a metre.
double d = segment_speed(s_seg, s_pos_m) * dt_s;
while (s_pos_m + d >= s_len[s_seg]) {
d -= s_len[s_seg] - s_pos_m;
s_seg = (s_seg + 1) % s_n;
s_pos_m = 0.0;
}
s_pos_m += d;
const route_point_t *a = &s_pts[s_seg];
const route_point_t *b = &s_pts[(s_seg + 1) % s_n];
double f = s_pos_m / s_len[s_seg];
out->latitude_tenmicrodeg = (int32_t)lround(a->latitude_tenmicrodeg
+ f * (b->latitude_tenmicrodeg - a->latitude_tenmicrodeg));
out->longitude_tenmicrodeg = (int32_t)lround(a->longitude_tenmicrodeg
+ f * (b->longitude_tenmicrodeg - a->longitude_tenmicrodeg));
out->speed_cm_s = (uint16_t)lround(segment_speed(s_seg, s_pos_m) * 100.0);
out->heading_ddeg = (uint16_t)(lround(s_bearing_deg[s_seg] * 10.0) % 3600);
out->segment = s_seg;
}
+40
View File
@@ -0,0 +1,40 @@
#ifndef ROUTE_H
#define ROUTE_H
#include <stdint.h>
// Simulated drive around a closed loop of waypoints, so the beacon looks like a
// car going round the block instead of a parked one.
//
// The car follows straight lines between the points and goes from the last one
// back to the first, forever. For street-following, the points are the street
// geometry from tools/make_route.py (main/route_points.h). Speed is a function
// of where the car is on a segment: it cruises, brakes ahead of each bend down to
// the speed that bend allows, and accelerates away after it. Heading is the
// bearing of the current segment.
//
// Uses only standard headers, so it also compiles on the host for testing.
typedef struct {
int32_t latitude_tenmicrodeg; // 1/10 microdegree, same units as the CAM
int32_t longitude_tenmicrodeg;
} route_point_t;
typedef struct {
int32_t latitude_tenmicrodeg;
int32_t longitude_tenmicrodeg;
uint16_t speed_cm_s; // SpeedValue units (0.01 m/s)
uint16_t heading_ddeg; // HeadingValue units (0.1 deg, 0..3599, 0 = north, clockwise)
int segment; // index of the route point the car last passed
} route_state_t;
#define ROUTE_MAX_POINTS 512 // keep in step with MAX_POINTS in tools/make_route.py
// `points` must stay valid for as long as the route is used. Returns 0, or -1
// if n is out of range (2..ROUTE_MAX_POINTS) or a segment has zero length.
// min_corner_mps is the slowest the car ever goes (hairpins, U-turns).
int route_init(const route_point_t *points, int n, double cruise_mps, double min_corner_mps);
// Moves the car on by dt_s seconds and writes its new position into `out`.
void route_step(double dt_s, route_state_t *out);
#endif
+116
View File
@@ -0,0 +1,116 @@
// GENERATED by tools/make_route.py - do not edit by hand; change WAYPOINTS there and rerun.
// Route data (c) OpenStreetMap contributors, ODbL. Routing by OSRM.
//
// Driving loop through 6 waypoints: 5179 m (legs 147 m, 1837 m, 381 m, 819 m, 1234 m, 761 m), 103 points after
// simplifying to 1.5 m. Streets: Beim Strohhause, Berlinertordamm, Berliner Tor, Bei der Hauptfeuerwache, Westphalensweg, Berliner Tor, Berlinertordamm, Borgfelder Straße, Anckelmannstraße, Anckelmannsplatz, Bürgerweide, Wallstraße, Lübeckertordamm, Steindamm, Kreuzweg, Adenauerallee, Nagelsweg, Norderstraße, Repsoldstraße, Kurt-Schumacher-Allee, Kreuzweg, Adenauerallee, Kurt-Schumacher-Allee, Beim Strohhause.
#ifndef ROUTE_POINTS_H
#define ROUTE_POINTS_H
#include "route.h"
static const route_point_t route_points[] = {
{ 535531770, 100220980 },
{ 535534470, 100240600 },
{ 535535790, 100244160 },
{ 535536400, 100244520 },
{ 535537130, 100244160 },
{ 535538480, 100242840 },
{ 535538720, 100242140 },
{ 535540390, 100240200 },
{ 535548880, 100233930 },
{ 535549470, 100235130 },
{ 535554140, 100253280 },
{ 535553570, 100254580 },
{ 535546070, 100251700 },
{ 535543130, 100250020 },
{ 535542570, 100249130 },
{ 535542760, 100247800 },
{ 535539980, 100244600 },
{ 535538720, 100242140 },
{ 535538140, 100241960 },
{ 535535720, 100243380 },
{ 535535300, 100244470 },
{ 535535090, 100246580 },
{ 535536830, 100264460 },
{ 535537600, 100270920 },
{ 535538520, 100275220 },
{ 535541110, 100290830 },
{ 535540170, 100291550 },
{ 535539890, 100294430 },
{ 535539580, 100295330 },
{ 535532290, 100299300 },
{ 535527980, 100302450 },
{ 535525130, 100291880 },
{ 535524220, 100289760 },
{ 535522900, 100287820 },
{ 535522890, 100285300 },
{ 535522610, 100282810 },
{ 535520620, 100276050 },
{ 535519820, 100271130 },
{ 535519670, 100269030 },
{ 535520220, 100267120 },
{ 535520930, 100262840 },
{ 535521850, 100260020 },
{ 535522840, 100258210 },
{ 535527790, 100257880 },
{ 535538650, 100261370 },
{ 535551660, 100266470 },
{ 535555790, 100268750 },
{ 535559620, 100271540 },
{ 535561120, 100272050 },
{ 535562430, 100271250 },
{ 535563830, 100269390 },
{ 535569610, 100260190 },
{ 535579530, 100242810 },
{ 535584090, 100237660 },
{ 535585850, 100234670 },
{ 535584970, 100230060 },
{ 535584000, 100227260 },
{ 535580730, 100222030 },
{ 535579100, 100218590 },
{ 535574500, 100208430 },
{ 535570560, 100199010 },
{ 535568130, 100194820 },
{ 535564910, 100187620 },
{ 535563990, 100184630 },
{ 535562540, 100181160 },
{ 535559780, 100176310 },
{ 535542180, 100136840 },
{ 535539720, 100133430 },
{ 535537350, 100132010 },
{ 535534760, 100131390 },
{ 535523840, 100132690 },
{ 535524980, 100155090 },
{ 535524890, 100156360 },
{ 535524440, 100157650 },
{ 535523030, 100158530 },
{ 535517610, 100158580 },
{ 535504440, 100168810 },
{ 535501360, 100156400 },
{ 535499250, 100144880 },
{ 535498470, 100133880 },
{ 535498630, 100126150 },
{ 535499110, 100121980 },
{ 535504260, 100117230 },
{ 535505720, 100115500 },
{ 535507630, 100112310 },
{ 535508400, 100111560 },
{ 535510000, 100110940 },
{ 535511390, 100119560 },
{ 535512440, 100122280 },
{ 535514510, 100132770 },
{ 535515020, 100134120 },
{ 535516630, 100135630 },
{ 535518260, 100136100 },
{ 535523950, 100134870 },
{ 535524980, 100155090 },
{ 535524890, 100156360 },
{ 535524440, 100157650 },
{ 535523470, 100158460 },
{ 535518560, 100158480 },
{ 535519070, 100162430 },
{ 535522260, 100178080 },
{ 535527880, 100197650 },
{ 535530060, 100209020 },
};
#endif