Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
8a3a07b
Task Manager
wtgee Mar 16, 2022
2d19e16
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Mar 17, 2022
1de869a
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Mar 17, 2022
ed39425
Test skeleton.
wtgee Mar 17, 2022
53a061c
Install `tasks` options.
wtgee Mar 17, 2022
7406373
Updates to starting the celery backends. Still uses config at this po…
wtgee Apr 2, 2022
afd4e9d
Adding `to_dict` methods for the `Observation` and `Field` classes.
wtgee Apr 6, 2022
78d4ab5
Add redis and rabiitmq to install options.
wtgee Apr 6, 2022
8e8dea9
* The `create_location_from_config` returns a `SiteDetails` class rat…
wtgee Apr 6, 2022
de2ef10
* Simplify the cli to start/stop the celery backend.
wtgee Apr 6, 2022
3b84e98
`TaskManager` is a class for starting/stopping the celery backends, m…
wtgee Apr 6, 2022
9dfde87
Start workers with a given `queue` name.
wtgee Apr 11, 2022
6dbc8ae
Simplify the config for celery.
wtgee Apr 11, 2022
a491ffb
Adding basic celery task type for gphoto camera.
wtgee Apr 11, 2022
8e56a89
* Better `status` from the CEM40 mount.
wtgee Apr 11, 2022
7a2368e
Remove rabbitmq dependency.
wtgee Apr 11, 2022
08917da
* Turn off auto-download of IERS. Should still download via cronjob.
wtgee Apr 11, 2022
f30f796
Don't connect to the camera until after set up.
wtgee Apr 11, 2022
5b20fec
* Task camera has a queue name.
wtgee Apr 11, 2022
dc07cfc
Make the IERS download a configurable options.
wtgee Apr 12, 2022
b84f3e5
* Replace `observe` with `take_observation` and add an `pocs.observe`…
wtgee Apr 12, 2022
91f86d4
Minor base camera updates.
wtgee Apr 12, 2022
245c92f
Don't use `release_shutter` for now. Need to figure out the tether.
wtgee Apr 12, 2022
bdcfb8c
Explicit PanDB db_type.
wtgee Apr 12, 2022
ea325be
Change task timeout and typos.
wtgee Apr 12, 2022
600213f
Change timeout back to 10 for tasks.
wtgee Apr 12, 2022
fa5f8ba
Don't send status names.
wtgee Apr 12, 2022
0af8887
Add supervisord config file. Currently starts:
wtgee Apr 18, 2022
9714774
Install script:
wtgee Apr 18, 2022
f768d35
Install script:
wtgee Apr 18, 2022
1000106
Use the `conf_files` dir off of home.
wtgee Apr 18, 2022
2675fac
Adding weather monitor script to supervisord conf file.
wtgee Apr 18, 2022
b69aa91
* Change cem40 mount to have default altitude limit of 30 degrees and…
wtgee Apr 29, 2022
f96a549
Small api fix.
wtgee Apr 29, 2022
4aef956
Fix the timestampe on the mount status.
wtgee May 10, 2022
6ed9a9d
Small logging improvement.
wtgee May 10, 2022
75597c3
* Change method to `observe_target`
wtgee May 11, 2022
0b63a76
The `run` loop contains a default `park_when_done`.
wtgee May 11, 2022
644a817
Adding a cli program for controlling the power.
wtgee May 26, 2022
ca51dc0
Adding sparklines as a dependency.
wtgee May 27, 2022
7684dd4
Merge remote-tracking branch 'upstream/develop' into add-celery
wtgee May 27, 2022
0f09449
Adding test flat field methods.
wtgee May 27, 2022
6761eef
Adding updates for flat field.
wtgee May 27, 2022
85a06ad
Construct a field from AltAx
wtgee May 27, 2022
1dcb679
Weather safety is a bool
wtgee May 27, 2022
190b8d4
Logging format change.
wtgee May 27, 2022
687ba0e
Even better logging format
wtgee May 27, 2022
30e1ab0
Wow, this time logging format just keeps getting better and better!
wtgee May 27, 2022
497e8c1
Using `get_quantity_value` instead of `to_value`
wtgee May 27, 2022
2d1487f
Missed quantity
wtgee May 27, 2022
37e4280
Use the max of the exptime.
wtgee Jun 1, 2022
42fa62d
Try with python 3.7
wtgee Jun 22, 2022
020c6b1
Small upates
wtgee Jun 22, 2022
06b9fd5
Fix cem40 log messages.
wtgee Aug 9, 2022
ebfc0cf
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 17, 2022
bacace9
Python 3.10
wtgee Aug 19, 2022
74e8584
Limit to python 3.10
wtgee Aug 19, 2022
d522727
Fix how test is run.
wtgee Aug 19, 2022
0bdbcc8
Properly properly run it.
wtgee Aug 19, 2022
98b2fb6
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
3c9b3b9
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
0f37400
Fix formatting.
wtgee Aug 19, 2022
ba99a2a
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
ea675fd
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
de1e784
Minor changes.
wtgee Aug 19, 2022
bec0c20
Minor changes
wtgee Aug 19, 2022
bb89ca1
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
06bfa88
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
9d3140e
Minor correction.
wtgee Aug 19, 2022
443e24f
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 19, 2022
5467b85
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 20, 2022
fbddd23
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 20, 2022
ef33355
Fix docstrings.
wtgee Aug 20, 2022
4562d9b
Remove cli test that's unused. Add to later PR>
wtgee Aug 20, 2022
ce624e1
Don't require python 3.10
wtgee Aug 20, 2022
ab65248
Remove state machine state and revert changes.
wtgee Aug 20, 2022
36e66fc
Actually remove the file.
wtgee Aug 20, 2022
6d47044
Don't make state table change in this file.
wtgee Aug 20, 2022
f516a73
Merge branch 'develop' of https://github.com/panoptes/POCS into add-c…
wtgee Aug 22, 2022
3dae082
* Moving to 3.10
wtgee Aug 31, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/pythontest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.8" ]
python-version: [ "3.10" ]
steps:
- name: Checkout code
uses: actions/checkout@v2
Expand All @@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.8", "3.9", "3.10" ]
python-version: [ "3.10" ]
steps:
- name: Checkout code
uses: actions/checkout@v2
Expand All @@ -34,7 +34,7 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Install
run: pip install ".[google,focuser,sensors,testing]"
run: pip install ".[google,focuser,sensors,tasks,testing]"
- name: Test
run: pytest
- name: Upload coverage report to codecov.io
Expand Down
10 changes: 10 additions & 0 deletions conf_files/pocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ directories:
mounts: resources/mounts
fields: conf_files/fields

# Celery task manager for distributed tasks.
celery:
broker_url: redis://localhost:6379
result_backend: redis://localhost:6379
service:
- name: pocs-celery
image: redis
ports:
6379: 6379

db:
name: panoptes
type: file
Expand Down
7 changes: 5 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ classifiers =
License :: OSI Approved :: MIT License
Operating System :: POSIX
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3 :: Only
Topic :: Scientific/Engineering :: Astronomy
Topic :: Scientific/Engineering :: Physics
Expand Down Expand Up @@ -52,7 +52,7 @@ install_requires =
# The usage of test_requires is discouraged, see `Dependency Management` docs
# tests_require = pytest; pytest-cov
# Require a specific Python version, e.g. Python 2.7 or >= 3.4
python_requires = >=3.8
python_requires = >="3.10"

packages = find_namespace:
[options.packages.find]
Expand All @@ -72,6 +72,9 @@ google =
gsutil
protobuf
rsa
tasks =
celery[redis]
docker
testing =
coverage
pycodestyle
Expand Down
Empty file.
57 changes: 57 additions & 0 deletions src/panoptes/pocs/camera/gphoto/celery/controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from typing import List, Union

from panoptes.pocs.camera.gphoto.canon import Camera as CanonCamera
from panoptes.pocs.utils import error
from panoptes.pocs.utils.tasks import TaskManager, RunTaskMixin


class Camera(CanonCamera, RunTaskMixin):
"""A remote gphoto2 camera class that can call local or remote celery tasks."""

def __init__(self, queue: str | None = None, *args, **kwargs):
"""Control a remote gphoto2 camera via a celery task. """
super().__init__(connect=False, *args, **kwargs)

self.celery_app = TaskManager.create_celery_app_from_config()

self.task = None
self.queue = queue or self.name
self.connect()
self.logger.debug(f'Canon DSLR GPhoto2 camera celery task manager with queue={self.queue}')

@property
def is_exposing(self):
return self.task and self.task.state == 'EXPOSING'

def command(self, cmd, queue=None, **kwargs):
"""Run a remote celery task attached to a camera. """

if self.is_exposing:
raise error.CameraBusy()

queue = queue or self.queue

arguments = ' '.join(cmd)

self.logger.debug(f'Running remote gphoto2 task with {arguments=} to {queue=}')
self.task = self.call_task('camera.command', args=[arguments], queue=queue)

def get_command_result(self, timeout: float = 10) -> Union[List[str], None]:
"""Get the output from the remote camera task, blocking up to timeout."""
cmd_result = self.task.get(timeout=timeout)

self.logger.debug(f'Full results from command {cmd_result!r}')

# Clear task.
self.task = None

# Return just the actual output. TODO error checking?
return cmd_result['output']

def _create_fits_header(self, seconds, dark=None, metadata=None) -> dict:
fits_header = super(Camera, self)._create_fits_header(seconds, dark=dark, metadata=metadata)
return {k.lower(): v for k, v in dict(fits_header).items()}

def _start_exposure(self, seconds=None, *args, **kwargs):
# TODO more here
self.task = self.call_task('camera.release_shutter', args=[seconds], queue=self.queue)
44 changes: 44 additions & 0 deletions src/panoptes/pocs/camera/gphoto/celery/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import typing
from enum import IntEnum
from typing import Dict, Optional

import pigpio
from pydantic import BaseSettings, BaseModel, Field

from worker import gpio


class State(IntEnum):
LOW = 0
HIGH = 1


class Settings(BaseSettings):
camera_name: str
camera_port: str
camera_pin: int
broker_url: str = 'amqp://guest:guest@localhost:5672//'
result_backend: str = 'rpc://'

class Config:
env_prefix = 'pocs_'


class Camera(BaseModel):
"""A camera with a shutter release connected to a gpio pin."""
name: str
port: str
pin: int
is_tethered: bool = False

def setup_pin(self):
"""Sets the mode for the GPIO pin."""
# Get GPIO pin and set OUTPUT mode.
print(f'Setting {self.pin=} as OUTPUT for {self.name}')
gpio.set_mode(self.pin, pigpio.OUTPUT)


class AppSettings(BaseModel):
celery: Dict = Field(default_factory=dict)
camera: Camera
process: Optional[typing.Any] = None
158 changes: 158 additions & 0 deletions src/panoptes/pocs/camera/gphoto/celery/worker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import re
import shutil
import subprocess
from contextlib import suppress
from typing import Optional, List, Union

import pigpio
from celery import Celery
from panoptes.utils.time import current_time, CountdownTimer

from panoptes.pocs.camera.gphoto.celery.settings import State, Settings, Camera, AppSettings

# Create settings from env vars.
settings = Settings()

# Build app settings.
app_settings = AppSettings(
camera=Camera(name=settings.camera_name,
port=settings.camera_port,
pin=settings.camera_pin),
celery=dict(broker_url=settings.broker_url,
result_backend=settings.result_backend),
)

# Start celery.
app = Celery()
app.config_from_object(app_settings.celery)

# Setup GPIO pins.
gpio = pigpio.pi()
app_settings.camera.setup_pin()

camera_match_re = re.compile(r'([\w\d\s_.]{30})\s(usb:\d{3},\d{3})')
file_save_re = re.compile(r'Saving file as (.*)')


@app.task(name='camera.release_shutter', bind=True)
def release_shutter(self, exptime: float):
"""Trigger the shutter release for given exposure time via the GPIO pin."""
# Create a timer.
timer = CountdownTimer(exptime, name=f'Pin{app_settings.camera.pin}Expose')

# Open shutter.
self.update_state(state='START_EXPOSING', start_time=current_time(flatten=True))
gpio.write(app_settings.camera.pin, State.HIGH)

# Wait for exptime, send state updates.
while timer.expired() is False:
self.update_state(state='EXPOSING', meta=dict(secs=f'{exptime - timer.time_left():.02f}', ))
timer.sleep(max_sleep=max(1., exptime / 8)) # Divide wait time into eighths.

# Close shutter.
gpio.write(app_settings.camera.pin, State.LOW)
self.update_state(state='STOP_EXPOSING', stop_time=current_time(flatten=True))


@app.task(name='camera.start_tether', bind=True)
def start_gphoto2_tether(self, filename_pattern: str):
"""Start a tether for gphoto2 auto-download."""
if app_settings.camera.is_tethered:
print(f'{app_settings.camera} is already tethered')
return
else:
print(f'Starting gphoto2 tether for {app_settings.camera.port=} using {filename_pattern=}')
app_settings.camera.is_tethered = True

command = ['--filename', filename_pattern, '--capture-tethered']
full_command = _build_gphoto2_command(command)

# Start tether process.
app_settings.process = subprocess.Popen(full_command,
stderr=subprocess.STDOUT,
stdout=subprocess.PIPE)
print(f'gphoto2 tether started for {app_settings.camera} on {app_settings.process.pid=}')


@app.task(name='camera.stop_tether')
def stop_gphoto2_tether():
"""Tells camera to stop gphoto2 tether."""
print(f'Stopping gphoto2 tether for {app_settings.camera}')
# Communicate and kill immediately.
try:
outs, errs = app_settings.process.communicate(timeout=1)
except subprocess.TimeoutExpired:
app_settings.process.kill()
outs, errs = app_settings.process.communicate()

app_settings.camera.is_tethered = False

return dict(outs=outs.decode('utf-8'), errs=errs.decode('utf-8'))


@app.task(name='camera.file_download', bind=True)
def gphoto_file_download(self,
filename_pattern: str,
only_new: bool = True
):
"""Downloads (newer) files from the camera on the given port using the filename pattern."""
print(f'Starting gphoto2 download for {app_settings.camera} using {filename_pattern=}')
command = ['--filename', filename_pattern, '--get-all-files', '--recurse']
if only_new:
command.append('--new')

results = gphoto2_command(command, timeout=600)
filenames = list()
for line in results['output']:
file_match = file_save_re.match(line)
if file_match is not None:
fn = file_match.group(1).strip()
print(f'Found match {fn}')
filenames.append(fn)
self.update_state(state='DOWNLOADING', meta=dict(directory=fn))

return filenames


@app.task(name='camera.delete_files', bind=True)
def gphoto_file_delete(self):
"""Removes all files from the camera on the given port."""
print(f'Deleting all files for {app_settings.camera}')
gphoto2_command('--delete-all-files --recurse')


@app.task(name='camera.command', bind=True)
def gphoto_task(self, command: Union[List[str], str]):
"""Perform arbitrary gphoto2 command.."""
print(f'Calling {command=} on {app_settings.camera}')
return gphoto2_command(command)


def gphoto2_command(command: Union[List[str], str], timeout: Optional[float] = 300) -> dict:
"""Perform a gphoto2 command."""
full_command = _build_gphoto2_command(command)
print(f'Running gphoto2 {full_command=}')

completed_proc = subprocess.run(full_command, capture_output=True, timeout=timeout)

# Populate return items.
command_output = dict(
success=completed_proc.returncode >= 0,
returncode=completed_proc.returncode,
output=completed_proc.stdout.decode('utf-8').split('\n'),
error=completed_proc.stderr.decode('utf-8').split('\n')
)

return command_output


def _build_gphoto2_command(command: Union[List[str], str]):
full_command = [shutil.which('gphoto2'), '--port', app_settings.camera.port]

# Turn command into a list if not one already.
with suppress(AttributeError):
command = command.split(' ')

full_command.extend(command)

return full_command
2 changes: 2 additions & 0 deletions src/panoptes/pocs/utils/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from panoptes.pocs.utils.cli import config
from panoptes.pocs.utils.cli import sensor
from panoptes.pocs.utils.cli import image
from panoptes.pocs.utils.cli import tasks
from panoptes.pocs.utils.cli import power

app = typer.Typer()
Expand All @@ -12,6 +13,7 @@
app.add_typer(sensor.app, name="sensor", help='Interact with system sensors.')
app.add_typer(power.app, name="power", help='Interact with power relays.')
app.add_typer(image.app, name="image", help='Interact with images.')
app.add_typer(tasks.app, name="tasks", help='Interact with the TaskManager.')


@app.callback()
Expand Down
Loading