-
Notifications
You must be signed in to change notification settings - Fork 2
Fix malformed template, cache CelesTrak TLEs #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import logging | ||
| import re | ||
| from pathlib import Path | ||
| from time import time | ||
|
|
||
| import requests | ||
| from skyfield.api import EarthSatellite | ||
|
|
@@ -12,7 +13,12 @@ | |
|
|
||
| class Satellite(EarthSatellite): # type: ignore[misc] | ||
| def __init__( | ||
| self, sat_id: str, *, tle_cache: TleCache | None = None, local_only: bool = False | ||
| self, | ||
| sat_id: str, | ||
| conf_dir: Path, | ||
| *, | ||
| tle_cache: TleCache | None = None, | ||
| local_only: bool = False, | ||
| ) -> None: | ||
| '''Fetch a TLE and build a satellite. | ||
|
|
||
|
|
@@ -32,6 +38,9 @@ def __init__( | |
| sat_id | ||
| The ID of the satellite, either as an International Designator, a NORAD Satellite | ||
| Catalog number, or Catalog satellite name. | ||
| conf_dir | ||
| Path to the directory to save TLEs from Celestrak in. When in doubt the same directory | ||
| that pass_commander.toml is in is good. | ||
| local_only | ||
| If true, do not use the internet for TLE lookup, only tle_cache or Gpredict. | ||
| tle_cache | ||
|
|
@@ -42,25 +51,37 @@ def __init__( | |
| query = "INTDES" | ||
| elif re.match(r"^\d{1,9}$", sat_id): | ||
| query = "CATNR" | ||
| filename = conf_dir / f'{query}-{sat_id}.txt' | ||
|
|
||
| tle = None | ||
| if tle is None and not local_only: | ||
| # see https://celestrak.org/NORAD/documentation/gp-data-formats.php | ||
| # FIXME: 2 hour cache. This must add the data to the TLE cache. Just use skytraq? | ||
| # see https://celestrak.org/NORAD/documentation/gp-data-formats.php | ||
| # In particular a 2 hour minimum cache time. In practice I think we get one new TLE per day | ||
| # so here we wait 0.5 days. FIXME: Add to TLE cache/save in pass_commander.toml? Depends on | ||
| # pydantic. | ||
| expired = not filename.exists() or (time() - filename.stat().st_mtime > 12 * 60 * 60) | ||
| if not local_only and expired: | ||
| logger.info("fetching TLE from celestrak for %s", sat_id) | ||
| r = requests.get( | ||
| "https://celestrak.org/NORAD/elements/gp.php", | ||
| params={query: sat_id}, | ||
| timeout=10, | ||
| ) | ||
| r.raise_for_status() | ||
| lines = r.text.splitlines() | ||
| filename.write_text(r.text) | ||
|
|
||
| tle = None | ||
| if tle is None and filename.exists(): | ||
| logger.info("using cached TLE from %s", filename) | ||
| lines = filename.read_text().splitlines() | ||
| # Not documented but CelesTrak currently returns this string if the previous query | ||
| # didn't match an object it knows about. We have to cache it anyway to respect their | ||
| # policy. | ||
| if lines[0] == "No GP data found": | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we looking for this specific string? Is the error message, "No GP data found", from a specific place were you can get TLEs?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, when the celestrak request above doesn't find the satellite we're looking for it responds with this string. It's not in the API docs (the link above) but it always appears to respond with it. We save it to a file because any result, successful or not, should follow their 2 hour timeout caching policy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably make note of that, in case they randomly decide to change it. |
||
| logger.info("No results for %s at celestrak", sat_id) | ||
| else: | ||
| tle = lines | ||
|
|
||
| if tle is None and tle_cache and sat_id in tle_cache: | ||
| logger.info("using cached TLE") | ||
| logger.info("using cached TLE from pass_commander.toml") | ||
| tle = tle_cache[sat_id] | ||
|
|
||
| if tle is None and query == "CATNR": | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,9 +165,22 @@ def test_edl(self, tmp_path: Path, good_toml: TOMLDocument, edlport: str | float | |
| Config(path) | ||
|
|
||
| def test_template(self, tmp_path: Path) -> None: | ||
| Config.template(tmp_path / "faketemplate.toml") | ||
| path = tmp_path / "faketemplate.toml" | ||
| Config.template(path) | ||
|
|
||
| # Trying to load the template should fail because template text hasn't been removed | ||
| with pytest.raises(config.TemplateTextError): | ||
| Config(tmp_path / "faketemplate.toml") | ||
| Config(path) | ||
|
|
||
| # But if we remove the template text | ||
| with path.open('r') as f: | ||
| contents = f.read() | ||
| contents = contents.replace('<', '') | ||
| contents = contents.replace('>', '') | ||
erfi-x2 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| with path.open('w') as f: | ||
| f.write(contents) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we generate a template then immediately overwrite the angle brackets while testing the config, wouldn't that just add an unnecessary step. Unless the angle brackets really are necessary to explain the what is expected to be in a field of the config file to the user.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stripping the angle brackets and having it work is a bit of a hack admittedly. We would really like the user to enter their specific values for the fields marked in such a way though, and words inside the brackets tell the user what to do. The main one is the station name, obviously we don't know what the user's station is and for regulatory reasons they really should have some kind of name specific to them. I could probably rethink the whole angle bracket system when the configs are overhauled as part of the pydantic conversion. Maybe just not filling out some mandatory fields and then have pass-commander yell about which fields are missing on startup would be better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing fields and possibly only specific characters you will be expecting would be good as well. |
||
| # then it should load | ||
| Config(path) | ||
|
|
||
| def test_template_exists(self, tmp_path: Path) -> None: | ||
| conf = tmp_path / "config.toml" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.