previously meant to include this exercise, too
This commit is contained in:
parent
d9c270c28f
commit
8aae6d4003
5 changed files with 475 additions and 0 deletions
218
exercises/22_compass/README.md
Normal file
218
exercises/22_compass/README.md
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
# Exercise 22: Compass / Magnetometer Executive Summary
|
||||||
|
|
||||||
|
This exercise will target the T-Beam Supreme magnetometer and build toward a real-time compass / field monitor. The manufacturer examples reviewed were:
|
||||||
|
|
||||||
|
- `QMC6310_CalibrateExample`
|
||||||
|
- `QMC6310_CompassExample`
|
||||||
|
- `QMC6310_GetDataExample`
|
||||||
|
- `QMC6310_GetPolarExample`
|
||||||
|
|
||||||
|
## What The Sensor Is Useful For
|
||||||
|
|
||||||
|
The QMC6310-class sensor is a 3-axis magnetometer. In practical terms, it is useful for:
|
||||||
|
|
||||||
|
- Magnetic heading estimation: a compass that points toward magnetic north.
|
||||||
|
- Relative orientation work: detecting yaw changes and turn direction.
|
||||||
|
- Magnetic field observation: watching X/Y/Z field components and total field strength change in real time.
|
||||||
|
- Sensor fusion: combining magnetometer data with the IMU to stabilize heading over time.
|
||||||
|
- Hardware assay: identifying board population differences by probing the magnetometer address and response.
|
||||||
|
|
||||||
|
It is not a complete navigation solution by itself. A magnetometer is sensitive to nearby ferrous metal, current-carrying wires, speakers, magnets, and board-level bias. For good heading, it needs calibration and a local magnetic declination correction if the goal is true north rather than magnetic north.
|
||||||
|
|
||||||
|
## What Each Example Actually Does
|
||||||
|
|
||||||
|
### `QMC6310_GetDataExample`
|
||||||
|
|
||||||
|
This is the basic bring-up example. It:
|
||||||
|
|
||||||
|
- Uses `setupBoards()` and the board I2C scan to discover the magnetometer address.
|
||||||
|
- Initializes the sensor.
|
||||||
|
- Configures continuous measurement mode.
|
||||||
|
- Prints compensated values and raw values for X/Y/Z to serial.
|
||||||
|
|
||||||
|
This is the best starting point for an exercise whose first goal is "show me live data."
|
||||||
|
|
||||||
|
### `QMC6310_GetPolarExample`
|
||||||
|
|
||||||
|
This example is the smallest heading-oriented demo. It:
|
||||||
|
|
||||||
|
- Reads magnetometer data.
|
||||||
|
- Converts the reading to a polar heading.
|
||||||
|
- Applies a declination correction.
|
||||||
|
- Prints heading, Gauss, and microtesla values.
|
||||||
|
|
||||||
|
This is useful once raw axis data is already trusted.
|
||||||
|
|
||||||
|
### `QMC6310_CompassExample`
|
||||||
|
|
||||||
|
This is a user-interface example. It:
|
||||||
|
|
||||||
|
- Reads X/Y values.
|
||||||
|
- Computes a heading with a hard-coded declination.
|
||||||
|
- Draws a compass arrow on the OLED.
|
||||||
|
- Also emits debug values on serial.
|
||||||
|
|
||||||
|
This demonstrates a real-time visual display, but it is still only as good as the underlying calibration.
|
||||||
|
|
||||||
|
### `QMC6310_CalibrateExample`
|
||||||
|
|
||||||
|
This example computes hard-iron offsets by watching the min/max raw values while the board is slowly rotated. It then applies:
|
||||||
|
|
||||||
|
- `x_offset = (x_max + x_min) / 2`
|
||||||
|
- `y_offset = (y_max + y_min) / 2`
|
||||||
|
- `z_offset = (z_max + z_min) / 2`
|
||||||
|
|
||||||
|
Those offsets are passed to `qmc.setOffset(...)`, after which subsequent readings are shifted by those values.
|
||||||
|
|
||||||
|
This is a runtime calibration aid, not a persistent factory calibration workflow.
|
||||||
|
|
||||||
|
## Calibration: Does It Need To Happen Every Boot?
|
||||||
|
|
||||||
|
Short answer: no, not if you save the offsets somewhere and re-apply them at boot.
|
||||||
|
|
||||||
|
Important detail from the local driver:
|
||||||
|
|
||||||
|
- `setOffset(...)` only stores offsets in the driver object's RAM.
|
||||||
|
- The offsets are not written into nonvolatile sensor storage.
|
||||||
|
- `begin(...)` calls `initImpl(...)`, and `initImpl(...)` performs `reset()`.
|
||||||
|
|
||||||
|
Implications:
|
||||||
|
|
||||||
|
- A normal reboot loses the offsets unless your firmware stores them elsewhere.
|
||||||
|
- Removing main power loses them.
|
||||||
|
- Keeping a battery attached does not make these software offsets persist in the magnetometer.
|
||||||
|
- If the board environment has not changed, you do not need to physically recalibrate at every boot, but you do need to reload previously measured offsets from persistent storage.
|
||||||
|
|
||||||
|
Recommended practice for this exercise:
|
||||||
|
|
||||||
|
- Add a one-time calibration mode.
|
||||||
|
- Save the resulting offsets in persistent storage on the ESP32.
|
||||||
|
- Re-apply offsets automatically at startup.
|
||||||
|
- Re-run calibration only when the enclosure, mounting, nearby wiring, or magnetic environment changes.
|
||||||
|
|
||||||
|
## Declination Offset
|
||||||
|
|
||||||
|
Declination is separate from magnetometer calibration.
|
||||||
|
|
||||||
|
- Calibration removes local sensor bias and board-level hard-iron offset.
|
||||||
|
- Declination converts magnetic north into true north for a specific latitude, longitude, and date.
|
||||||
|
|
||||||
|
So yes: if you want true heading, the user should specify a coordinate, obtain the current declination for that coordinate, and store that value with provenance.
|
||||||
|
|
||||||
|
For this exercise, the practical design is:
|
||||||
|
|
||||||
|
1. Choose the operating coordinates.
|
||||||
|
2. Obtain declination on the host, not on the ESP32.
|
||||||
|
3. Save the fetched value in a simple text file such as `declination.txt`.
|
||||||
|
4. Load that value into firmware at build time or into ESP32 persistent storage at provisioning time.
|
||||||
|
5. Re-fetch when the unit is moved a meaningful distance or after enough time has passed that the stored date is stale.
|
||||||
|
|
||||||
|
Why host-side instead of device-side:
|
||||||
|
|
||||||
|
- The ESP32 firmware should not depend on live network access to a geomagnetic service.
|
||||||
|
- Declination changes slowly, so caching is appropriate.
|
||||||
|
- A text file with comments gives you traceability for how the value was obtained.
|
||||||
|
|
||||||
|
Recommended file shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# procured: April 16, 2026
|
||||||
|
# for coordinates: 44.93642012667761, -123.02203699545396
|
||||||
|
# source: NOAA/NCEI geomagnetic declination calculator
|
||||||
|
# sign convention: east positive, west negative
|
||||||
|
declination_deg=14.47
|
||||||
|
```
|
||||||
|
|
||||||
|
For your current manually captured value:
|
||||||
|
|
||||||
|
```text
|
||||||
|
2026-04-16 14.47° E ± 0.36° changing by 0.11° W per year
|
||||||
|
```
|
||||||
|
|
||||||
|
the appropriate build-time value is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAG_DECLINATION_DEG = 14.47
|
||||||
|
```
|
||||||
|
|
||||||
|
using the convention:
|
||||||
|
|
||||||
|
- East declination is positive.
|
||||||
|
- West declination is negative.
|
||||||
|
|
||||||
|
That convention should be written directly into the firmware and README so there is no hidden sign ambiguity.
|
||||||
|
|
||||||
|
Operational guidance:
|
||||||
|
|
||||||
|
- For a fixed station, one fetched value is usually sufficient and should simply be refreshed occasionally.
|
||||||
|
- For a mobile unit used across a region, a single fixed declination is still acceptable for an early exercise, but it should be understood as an approximation.
|
||||||
|
- For this project stage, storing one deployment-specific declination value is the right tradeoff.
|
||||||
|
|
||||||
|
## Multi-Unit Assay For Seven Boards
|
||||||
|
|
||||||
|
Lewis He's current note says T-Beam Supreme units may contain `QMC6310N`, `QMC6310U`, or `QMC6309`, each with a different I2C address.
|
||||||
|
|
||||||
|
What the local LilyGo code currently supports:
|
||||||
|
|
||||||
|
- `0x1C` -> treated as `QMC6310U`
|
||||||
|
- `0x3C` -> treated as `QMC6310N` after distinguishing it from the OLED at the same address family
|
||||||
|
|
||||||
|
What is missing locally:
|
||||||
|
|
||||||
|
- I do not see `QMC6309` support anywhere in the local clone.
|
||||||
|
- The referenced GitHub commit is not present in the local repository, so the current local examples cannot yet identify `QMC6309`.
|
||||||
|
|
||||||
|
Practical assay recommendation for units `A` through `G`:
|
||||||
|
|
||||||
|
1. Build a small probe sketch for a selected PlatformIO environment such as `cy`.
|
||||||
|
2. Run the same board scan that `setupBoards()` already performs.
|
||||||
|
3. Print the detected magnetometer I2C address.
|
||||||
|
4. Print `getChipID()`.
|
||||||
|
5. Record the result per unit label: `AMY`, `BOB`, `CY`, `DAN`, `ED`, `FLO`, `GUY`.
|
||||||
|
|
||||||
|
Expected result set:
|
||||||
|
|
||||||
|
- If the unit answers at `0x1C`, classify it as `QMC6310U`.
|
||||||
|
- If the unit answers at `0x3C`, classify it as `QMC6310N`.
|
||||||
|
- If a unit answers at some third address, that is the candidate path for `QMC6309`, but the local code will need to be extended to name it explicitly.
|
||||||
|
|
||||||
|
## Recommended Design Direction For This Exercise
|
||||||
|
|
||||||
|
The clean design is a real-time serial-first field monitor, with OLED support as a secondary display.
|
||||||
|
|
||||||
|
Suggested output in real time:
|
||||||
|
|
||||||
|
- Unit identifier, for example `CY`
|
||||||
|
- Detected magnetometer address
|
||||||
|
- Chip ID
|
||||||
|
- Build UTC tag
|
||||||
|
- Raw X/Y/Z
|
||||||
|
- Offset-corrected X/Y/Z
|
||||||
|
- Declination in degrees and its source date
|
||||||
|
- Heading in degrees
|
||||||
|
- Total field magnitude
|
||||||
|
- Calibration status
|
||||||
|
|
||||||
|
Suggested workflow:
|
||||||
|
|
||||||
|
1. Boot and identify the attached board and detected magnetometer address.
|
||||||
|
2. Load saved calibration offsets if present.
|
||||||
|
3. Stream readings continuously over serial for capture and inspection.
|
||||||
|
4. Optionally mirror heading and a simple arrow on the OLED.
|
||||||
|
5. Provide a serial command or compile-time mode to enter calibration.
|
||||||
|
6. Save newly computed offsets for future boots.
|
||||||
|
7. Apply stored declination before reporting true heading.
|
||||||
|
|
||||||
|
## Bottom Line
|
||||||
|
|
||||||
|
The examples are enough to establish that this sensor is suitable for a live heading and magnetic field display exercise. The only caution is that the example labeled "calibration" is session-local unless we explicitly persist the offsets. The first useful deliverable for this exercise should therefore be a per-unit magnetometer assay plus a serial real-time monitor, followed by persistent calibration storage and then an OLED compass view.
|
||||||
|
|
||||||
|
## Local Helper Scripts
|
||||||
|
|
||||||
|
Exercise 22 now includes:
|
||||||
|
|
||||||
|
- `scripts/set_build_epoch.py` copied from Exercise 18 so the same unique build timestamp mechanism is available here.
|
||||||
|
- `scripts/fetch_declination.pl` to fetch a declination value on the host and write a provenance-rich `declination.txt`.
|
||||||
|
- `platformio.ini` scaffolded with the build-stamp hook and manual declination build defines.
|
||||||
|
|
||||||
|
The NOAA fetch helper remains available, but given the current registration requirement, the recommended workflow is manual lookup plus a checked-in text record such as `declination_manual.txt`.
|
||||||
1
exercises/22_compass/declination_manual.txt
Normal file
1
exercises/22_compass/declination_manual.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
2026-04-16 14.47° E ± 0.36° changing by 0.11° W per year
|
||||||
123
exercises/22_compass/platformio.ini
Normal file
123
exercises/22_compass/platformio.ini
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
; 20260416 ChatGPT
|
||||||
|
; Exercise 22_compass
|
||||||
|
|
||||||
|
[platformio]
|
||||||
|
default_envs = cy
|
||||||
|
|
||||||
|
[env]
|
||||||
|
platform = espressif32
|
||||||
|
framework = arduino
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
board_build.partitions = default_8MB.csv
|
||||||
|
monitor_speed = 115200
|
||||||
|
extra_scripts = pre:scripts/set_build_epoch.py
|
||||||
|
lib_deps =
|
||||||
|
Wire
|
||||||
|
olikraus/U8g2@^2.36.4
|
||||||
|
lewisxhe/XPowersLib@0.3.3
|
||||||
|
|
||||||
|
build_flags =
|
||||||
|
-I ../../shared/boards
|
||||||
|
-I ../../external/microReticulum_Firmware
|
||||||
|
-D BOARD_MODEL=BOARD_TBEAM_S_V1
|
||||||
|
-D OLED_SDA=17
|
||||||
|
-D OLED_SCL=18
|
||||||
|
-D OLED_ADDR=0x3C
|
||||||
|
-D GPS_RX_PIN=9
|
||||||
|
-D GPS_TX_PIN=8
|
||||||
|
-D GPS_WAKEUP_PIN=7
|
||||||
|
-D GPS_1PPS_PIN=6
|
||||||
|
-D GPS_L76K
|
||||||
|
-D NODE_SLOT_COUNT=7
|
||||||
|
-D ARDUINO_USB_MODE=1
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
|
||||||
|
; Declination convention:
|
||||||
|
; east = positive
|
||||||
|
; west = negative
|
||||||
|
; Manual source:
|
||||||
|
; declination_manual.txt
|
||||||
|
; 2026-04-16 14.47° E ±0.36° changing by 0.11° W per year
|
||||||
|
-D MAG_DECLINATION_DEG=14.47f
|
||||||
|
-D MAG_DECLINATION_SOURCE=\"manual_noaa_web_ui\"
|
||||||
|
-D MAG_DECLINATION_DATE=\"2026-04-16\"
|
||||||
|
-D MAG_DECLINATION_LAT=44.93642012667761
|
||||||
|
-D MAG_DECLINATION_LON=-123.02203699545396
|
||||||
|
|
||||||
|
[env:amy]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"AMY\"
|
||||||
|
-D NODE_LABEL=\"Amy\"
|
||||||
|
-D NODE_SHORT=\"A\"
|
||||||
|
-D NODE_SLOT_INDEX=0
|
||||||
|
-D LOG_AP_IP_OCTET=23
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:bob]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"BOB\"
|
||||||
|
-D NODE_LABEL=\"Bob\"
|
||||||
|
-D NODE_SHORT=\"B\"
|
||||||
|
-D NODE_SLOT_INDEX=1
|
||||||
|
-D LOG_AP_IP_OCTET=24
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:cy]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"CY\"
|
||||||
|
-D NODE_LABEL=\"Cy\"
|
||||||
|
-D NODE_SHORT=\"C\"
|
||||||
|
-D NODE_SLOT_INDEX=2
|
||||||
|
-D LOG_AP_IP_OCTET=25
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:dan]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"DAN\"
|
||||||
|
-D NODE_LABEL=\"Dan\"
|
||||||
|
-D NODE_SHORT=\"D\"
|
||||||
|
-D NODE_SLOT_INDEX=3
|
||||||
|
-D LOG_AP_IP_OCTET=26
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:ed]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"ED\"
|
||||||
|
-D NODE_LABEL=\"Ed\"
|
||||||
|
-D NODE_SHORT=\"E\"
|
||||||
|
-D NODE_SLOT_INDEX=4
|
||||||
|
-D LOG_AP_IP_OCTET=27
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:flo]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"FLO\"
|
||||||
|
-D NODE_LABEL=\"Flo\"
|
||||||
|
-D NODE_SHORT=\"F\"
|
||||||
|
-D NODE_SLOT_INDEX=5
|
||||||
|
-D LOG_AP_IP_OCTET=28
|
||||||
|
-D GNSS_CHIP_NAME=\"L76K\"
|
||||||
|
|
||||||
|
[env:guy]
|
||||||
|
extends = env
|
||||||
|
build_flags =
|
||||||
|
${env.build_flags}
|
||||||
|
-D BOARD_ID=\"GUY\"
|
||||||
|
-D NODE_LABEL=\"Guy\"
|
||||||
|
-D NODE_SHORT=\"G\"
|
||||||
|
-D NODE_SLOT_INDEX=6
|
||||||
|
-D LOG_AP_IP_OCTET=29
|
||||||
|
-D GNSS_CHIP_NAME=\"MAX-M10S\"
|
||||||
|
-D GPS_UBLOX
|
||||||
121
exercises/22_compass/scripts/fetch_declination.pl
Normal file
121
exercises/22_compass/scripts/fetch_declination.pl
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use HTTP::Tiny;
|
||||||
|
use POSIX qw(strftime);
|
||||||
|
use Text::ParseWords qw(parse_line);
|
||||||
|
use URI::Escape qw(uri_escape);
|
||||||
|
|
||||||
|
my ($lat, $lon, $outfile, $iso_date) = @ARGV;
|
||||||
|
|
||||||
|
die usage() unless defined $lat && defined $lon;
|
||||||
|
|
||||||
|
$outfile ||= "declination.txt";
|
||||||
|
$iso_date ||= strftime("%Y-%m-%d", gmtime());
|
||||||
|
|
||||||
|
die "Latitude must be numeric\n" unless $lat =~ /\A-?\d+(?:\.\d+)?\z/;
|
||||||
|
die "Longitude must be numeric\n" unless $lon =~ /\A-?\d+(?:\.\d+)?\z/;
|
||||||
|
die "Latitude out of range\n" unless $lat >= -90 && $lat <= 90;
|
||||||
|
die "Longitude out of range\n" unless $lon >= -180 && $lon <= 180;
|
||||||
|
die "Date must be YYYY-MM-DD\n" unless $iso_date =~ /\A(\d{4})-(\d{2})-(\d{2})\z/;
|
||||||
|
|
||||||
|
my ($year, $month, $day) = ($1, $2, $3);
|
||||||
|
|
||||||
|
my $base_url = "https://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination";
|
||||||
|
my %query = (
|
||||||
|
lat1 => $lat,
|
||||||
|
lon1 => $lon,
|
||||||
|
model => "WMM",
|
||||||
|
startYear => $year,
|
||||||
|
startMonth => $month + 0,
|
||||||
|
startDay => $day + 0,
|
||||||
|
resultFormat => "csv",
|
||||||
|
);
|
||||||
|
|
||||||
|
my $url = $base_url . "?" . join("&",
|
||||||
|
map { uri_escape($_) . "=" . uri_escape($query{$_}) } sort keys %query
|
||||||
|
);
|
||||||
|
|
||||||
|
my $http = HTTP::Tiny->new(
|
||||||
|
agent => "microReticulum-ex22-declination-fetch/1.0",
|
||||||
|
timeout => 30,
|
||||||
|
verify_SSL => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
my $res = $http->get($url);
|
||||||
|
die "HTTP request failed: $res->{status} $res->{reason}\n" unless $res->{success};
|
||||||
|
|
||||||
|
my $body = $res->{content};
|
||||||
|
my $declination = extract_declination_from_csv($body);
|
||||||
|
$declination = extract_declination_from_text($body) unless defined $declination;
|
||||||
|
|
||||||
|
die "Unable to parse declination from NOAA response\n" unless defined $declination;
|
||||||
|
|
||||||
|
my $procured = format_procured_date($year, $month, $day);
|
||||||
|
open my $fh, ">", $outfile or die "Cannot write $outfile: $!\n";
|
||||||
|
print {$fh} "# procured: $procured\n";
|
||||||
|
print {$fh} "# for coordinates: $lat, $lon\n";
|
||||||
|
print {$fh} "# source: NOAA/NCEI geomagnetic declination calculator\n";
|
||||||
|
print {$fh} "# date: $iso_date\n";
|
||||||
|
print {$fh} "# sign convention: east positive, west negative\n";
|
||||||
|
printf {$fh} "declination_deg=%.6f\n", $declination;
|
||||||
|
close $fh or die "Cannot close $outfile: $!\n";
|
||||||
|
|
||||||
|
print "Wrote $outfile\n";
|
||||||
|
printf "declination_deg=%.6f\n", $declination;
|
||||||
|
|
||||||
|
sub extract_declination_from_csv {
|
||||||
|
my ($text) = @_;
|
||||||
|
my @lines = grep { /\S/ } split /\r?\n/, $text;
|
||||||
|
return undef unless @lines >= 2;
|
||||||
|
|
||||||
|
my @header = parse_line(",", 0, $lines[0] // "");
|
||||||
|
my $decl_idx;
|
||||||
|
for my $i (0 .. $#header) {
|
||||||
|
next unless defined $header[$i];
|
||||||
|
if ($header[$i] =~ /\Adeclination\z/i) {
|
||||||
|
$decl_idx = $i;
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undef unless defined $decl_idx;
|
||||||
|
|
||||||
|
for my $line (@lines[1 .. $#lines]) {
|
||||||
|
my @fields = parse_line(",", 0, $line);
|
||||||
|
next unless defined $fields[$decl_idx];
|
||||||
|
my $value = $fields[$decl_idx];
|
||||||
|
$value =~ s/^\s+|\s+$//g;
|
||||||
|
$value =~ s/[^\d+.\-]//g;
|
||||||
|
return $value + 0 if $value =~ /\A[+-]?\d+(?:\.\d+)?\z/;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub extract_declination_from_text {
|
||||||
|
my ($text) = @_;
|
||||||
|
return $1 + 0 if $text =~ /declination[^-+0-9]*([+-]?\d+(?:\.\d+)?)/i;
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub format_procured_date {
|
||||||
|
my ($y, $m, $d) = @_;
|
||||||
|
my @month_names = qw(
|
||||||
|
January February March April May June
|
||||||
|
July August September October November December
|
||||||
|
);
|
||||||
|
my $name = $month_names[$m - 1] // die "Invalid month\n";
|
||||||
|
return sprintf("%s %d, %04d", $name, $d, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub usage {
|
||||||
|
return <<"USAGE";
|
||||||
|
Usage:
|
||||||
|
fetch_declination.pl <latitude> <longitude> [outfile] [YYYY-MM-DD]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
fetch_declination.pl 44.93642012667761 -123.02203699545396 declination.txt 2026-04-16
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
12
exercises/22_compass/scripts/set_build_epoch.py
Normal file
12
exercises/22_compass/scripts/set_build_epoch.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import time
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
epoch = int(time.time())
|
||||||
|
utc_tag = time.strftime("%Y%m%d_%H%M%S_z", time.gmtime(epoch))
|
||||||
|
|
||||||
|
env.Append(
|
||||||
|
CPPDEFINES=[
|
||||||
|
("FW_BUILD_EPOCH", str(epoch)),
|
||||||
|
("FW_BUILD_UTC", '"%s"' % utc_tag),
|
||||||
|
]
|
||||||
|
)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue