Skip to content

Commit 96c7db0

Browse files
committed
Added file listing and download commands
1 parent 6952838 commit 96c7db0

File tree

3 files changed

+355
-0
lines changed

3 files changed

+355
-0
lines changed

src/instrumentman/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from . import setmeasurement
1212
from . import protocoltest
1313
from . import inclination
14+
from . import filetransfer
1415

1516

1617
@extra_group("iman", params=None) # type: ignore[misc]
@@ -51,6 +52,16 @@ def cli_test() -> None:
5152
"""Test protocol responsiveness."""
5253

5354

55+
@cli.group("list") # type: ignore[misc]
56+
def cli_list() -> None:
57+
"""List various data stored on the instrument."""
58+
59+
60+
@cli.group("download") # type: ignore[misc]
61+
def cli_download() -> None:
62+
"""Download data from the instrument."""
63+
64+
5465
cli.add_command(morse.cli)
5566
cli.add_command(terminal.cli)
5667
cli_measure.add_command(setmeasurement.cli_measure)
@@ -64,3 +75,5 @@ def cli_test() -> None:
6475
cli_merge.add_command(inclination.cli_merge)
6576
cli_validate.add_command(setmeasurement.cli_validate)
6677
cli_import.add_command(setup.cli_import)
78+
cli_list.add_command(filetransfer.cli_list)
79+
cli_download.add_command(filetransfer.cli_download)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from typing import Any
2+
3+
from click_extra import (
4+
extra_command,
5+
option,
6+
argument,
7+
IntRange,
8+
Choice,
9+
File
10+
)
11+
12+
from ..utils import (
13+
com_option_group,
14+
com_port_argument
15+
)
16+
17+
18+
@extra_command(
19+
"files",
20+
params=None,
21+
context_settings={"auto_envvar_prefix": None}
22+
) # type: ignore[misc]
23+
@com_port_argument()
24+
@argument(
25+
"directory",
26+
help="directory to list files in (path should end with '/')",
27+
type=str,
28+
default="/"
29+
)
30+
@com_option_group()
31+
@option(
32+
"-d",
33+
"--device",
34+
help="memory device",
35+
type=Choice(
36+
(
37+
"internal",
38+
"cf",
39+
"sd",
40+
"usb",
41+
"ram"
42+
),
43+
case_sensitive=False
44+
),
45+
default="internal"
46+
)
47+
@option(
48+
"-f",
49+
"--filetype",
50+
help="file type",
51+
type=Choice(
52+
(
53+
"image",
54+
"database",
55+
"overview-jpg",
56+
"overview-bmp",
57+
"telescope-jpg",
58+
"telescope-bmp",
59+
"scan",
60+
"unknown",
61+
"last"
62+
),
63+
case_sensitive=False
64+
),
65+
default="unknown"
66+
)
67+
def cli_list(**kwargs: Any) -> None:
68+
"""List files on an instrument."""
69+
from .app import main_list
70+
71+
main_list(**kwargs)
72+
73+
74+
@extra_command(
75+
"file",
76+
params=None,
77+
context_settings={"auto_envvar_prefix": None}
78+
) # type: ignore[misc]
79+
@com_port_argument()
80+
@argument(
81+
"filename",
82+
help=(
83+
"file to download (including path with '/' separators if filetype "
84+
"option is not specified)"
85+
),
86+
type=str
87+
)
88+
@argument(
89+
"output",
90+
help="file to save downloaded data to",
91+
type=File("wb", lazy=False)
92+
)
93+
@com_option_group()
94+
@option(
95+
"-d",
96+
"--device",
97+
help="memory device",
98+
type=Choice(
99+
(
100+
"internal",
101+
"cf",
102+
"sd",
103+
"usb",
104+
"ram"
105+
),
106+
case_sensitive=False
107+
),
108+
default="internal"
109+
)
110+
@option(
111+
"-f",
112+
"--filetype",
113+
help="file type",
114+
type=Choice(
115+
(
116+
"image",
117+
"database",
118+
"overview-jpg",
119+
"overview-bmp",
120+
"telescope-jpg",
121+
"telescope-bmp",
122+
"scan",
123+
"unknown",
124+
"last"
125+
),
126+
case_sensitive=False
127+
),
128+
default="unknown"
129+
)
130+
@option(
131+
"-c",
132+
"--chunk",
133+
help="chunk size (max 450 for normal and 1800 for VivaTPS large download)",
134+
type=IntRange(1, 1800),
135+
default=450
136+
)
137+
@option(
138+
"--large",
139+
help="use large download commands (only available from VivaTPS)",
140+
is_flag=True
141+
)
142+
def cli_download(**kwargs: Any) -> None:
143+
"""Download a file from the instrument."""
144+
from .app import main_download
145+
146+
main_download(**kwargs)
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
from io import BufferedWriter
2+
3+
from click_extra import echo, progressbar
4+
from geocompy.communication import open_serial
5+
from geocompy.geo import GeoCom
6+
from geocompy.geo.gctypes import GeoComCode
7+
from geocompy.geo.gcdata import File, Device
8+
9+
from ..utils import echo_red, echo_green, echo_yellow
10+
11+
12+
_FILE = {
13+
"image": File.IMAGE,
14+
"database": File.DATABASE,
15+
"overview-jpg": File.IMAGES_OVERVIEW_JPG,
16+
"overview-bmp": File.IMAGES_OVERVIEW_BMP,
17+
"telescope-jpg": File.IMAGES_TELESCOPIC_JPG,
18+
"telescope-bmp": File.IMAGES_TELESCOPIC_BMP,
19+
"scan": File.SCANS,
20+
"unknown": File.UNKNOWN,
21+
"last": File.LAST
22+
}
23+
24+
25+
_DEVICE = {
26+
"internal": Device.INTERNAL,
27+
"cf": Device.CFCARD,
28+
"sd": Device.SDCARD,
29+
"usb": Device.USB,
30+
"ram": Device.RAM
31+
}
32+
33+
34+
def run_listing(
35+
tps: GeoCom,
36+
dev: str,
37+
directory: str,
38+
filetype: str
39+
) -> None:
40+
resp_setup = tps.ftr.setup_listing(
41+
_DEVICE[dev],
42+
_FILE[filetype],
43+
directory
44+
)
45+
if resp_setup.error != GeoComCode.OK:
46+
echo_red(f"Could not set up file listing ({resp_setup.error.name})")
47+
return
48+
49+
resp_list = tps.ftr.list()
50+
if resp_list.error != GeoComCode.OK or resp_list.params is None:
51+
echo_red(f"Could not start listing ({resp_list.error.name})")
52+
return
53+
54+
last, name, size, lastmodified = resp_list.params
55+
if name == "":
56+
echo_yellow("Directory is empty or path does not exist")
57+
return
58+
59+
count = 1
60+
echo(f"{'file name':<55.55s}{'bytes':>10.10s}{'last modified':>25.25s}")
61+
echo(f"{'---------':<55.55s}{'-----':>10.10s}{'-------------':>25.25s}")
62+
fmt = "{name:<55.55s}{size:>10s}{date:>25.25s}"
63+
echo(
64+
fmt.format_map(
65+
{
66+
"name": name,
67+
"size": str(size),
68+
"date": (
69+
lastmodified.isoformat(sep=" ")
70+
if lastmodified is not None
71+
else ""
72+
)
73+
}
74+
)
75+
)
76+
while not last:
77+
resp_list = tps.ftr.list(True)
78+
if resp_list.error != GeoComCode.OK or resp_list.params is None:
79+
echo_red(
80+
f"An error occured during listing ({resp_list.error.name})"
81+
)
82+
return
83+
84+
last, name, size, lastmodified = resp_list.params
85+
echo(
86+
fmt.format_map(
87+
{
88+
"name": name,
89+
"size": str(size),
90+
"date": (
91+
lastmodified.isoformat(sep=" ")
92+
if lastmodified is not None
93+
else ""
94+
)
95+
}
96+
)
97+
)
98+
count += 1
99+
100+
echo("-" * 90)
101+
echo(f"total: {count} files")
102+
103+
104+
def run_download(
105+
tps: GeoCom,
106+
filename: str,
107+
file: BufferedWriter,
108+
device: str = "internal",
109+
filetype: str = "unknown",
110+
chunk: int = 450,
111+
large: bool = False
112+
) -> None:
113+
setup = tps.ftr.setup_download
114+
download = tps.ftr.download
115+
if large:
116+
setup = tps.ftr.setup_large_download
117+
download = tps.ftr.download_large
118+
119+
resp_setup = setup(
120+
filename,
121+
chunk,
122+
_DEVICE[device],
123+
_FILE[filetype]
124+
)
125+
if resp_setup.error != GeoComCode.OK or resp_setup.params is None:
126+
echo_red(f"Could not set up file download ({resp_setup.error.name})")
127+
return
128+
129+
block_count = resp_setup.params
130+
with progressbar(
131+
range(block_count),
132+
label="Downloading"
133+
) as bar:
134+
for i in bar:
135+
resp_pull = download(i + 1)
136+
if resp_pull.error != GeoComCode.OK or resp_pull.params is None:
137+
echo_red(
138+
"An error occured during download "
139+
f"({resp_setup.error.name})"
140+
)
141+
return
142+
143+
echo(bytes.fromhex(resp_pull.params), file, False)
144+
145+
echo_green("Download complete")
146+
147+
148+
def main_download(
149+
port: str,
150+
filename: str,
151+
output: BufferedWriter,
152+
baud: int = 9600,
153+
timeout: int = 15,
154+
retry: int = 1,
155+
sync_after_timeout: bool = False,
156+
device: str = "internal",
157+
filetype: str = "unknown",
158+
chunk: int = 450,
159+
large: bool = False
160+
) -> None:
161+
with open_serial(
162+
port=port,
163+
speed=baud,
164+
timeout=timeout,
165+
retry=retry,
166+
sync_after_timeout=sync_after_timeout
167+
) as com:
168+
tps = GeoCom(com)
169+
try:
170+
run_download(tps, filename, output, device, filetype, chunk, large)
171+
finally:
172+
tps.ftr.abort_download()
173+
174+
175+
def main_list(
176+
port: str,
177+
directory: str = "/",
178+
baud: int = 9600,
179+
timeout: int = 15,
180+
retry: int = 1,
181+
sync_after_timeout: bool = False,
182+
device: str = "internal",
183+
filetype: str = "unknown"
184+
) -> None:
185+
with open_serial(
186+
port=port,
187+
speed=baud,
188+
timeout=timeout,
189+
retry=retry,
190+
sync_after_timeout=sync_after_timeout
191+
) as com:
192+
tps = GeoCom(com)
193+
try:
194+
run_listing(tps, device, directory, filetype)
195+
finally:
196+
tps.ftr.abort_list()

0 commit comments

Comments
 (0)