diff --git a/.dockerignore b/.dockerignore
index b296ccfa9..91be5f110 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,12 +1,9 @@
-docs/*
.idea
.venv
venv
# Pass the git folder to the local build context.
# This is only needed for local builds because of setuptools_scm.
-# The cloudbuild version ignores this folder (and creates a smaller image)
-# because it installs from pypi rather than local.
!.git
.github
diff --git a/.github/codecov.yml b/.github/codecov.yml
index 00297b4ae..d8a44d702 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -1,19 +1,19 @@
ignore:
- - "pocs/camera/canon_gphoto2.py"
- - "pocs/camera/sbig.py"
- - "pocs/camera/sbigudrv.py"
- - "pocs/camera/fli.py"
- - "pocs/camera/libfli.py"
- - "pocs/camera/libfliconstants.py"
- - "pocs/camera/zwo.py"
- - "pocs/camera/libasi.py"
- - "pocs/tests/bisque/*"
- - "pocs/dome/bisque.py"
- - "pocs/mount/bisque.py"
- - "pocs/mount/serial.py"
- - "pocs/mount/ioptron.py"
- - "pocs/focuser/birger.py"
- - "pocs/focuser/focuslynx.py"
- - "pocs/filterwheel/libefw.py"
- - "pocs/filterwheel/sbig.py"
- - "pocs/filterwheel/zwo.py"
+ - "src/panoptes/pocs/camera/gphoto/canon.py"
+ - "src/panoptes/pocs/camera/sbig.py"
+ - "src/panoptes/pocs/camera/sbigudrv.py"
+ - "src/panoptes/pocs/camera/fli.py"
+ - "src/panoptes/pocs/camera/libfli.py"
+ - "src/panoptes/pocs/camera/libfliconstants.py"
+ - "src/panoptes/pocs/camera/zwo.py"
+ - "src/panoptes/pocs/camera/libasi.py"
+ - "src/panoptes/pocs/tests/bisque/*"
+ - "src/panoptes/pocs/dome/bisque.py"
+ - "src/panoptes/pocs/mount/bisque.py"
+ - "src/panoptes/pocs/mount/serial.py"
+ - "src/panoptes/pocs/mount/ioptron.py"
+ - "src/panoptes/pocs/focuser/birger.py"
+ - "src/panoptes/pocs/focuser/focuslynx.py"
+ - "src/panoptes/pocs/filterwheel/libefw.py"
+ - "src/panoptes/pocs/filterwheel/sbig.py"
+ - "src/panoptes/pocs/filterwheel/zwo.py"
diff --git a/.github/workflows/pythontest.yaml b/.github/workflows/pythontest.yaml
index ef806a429..2e455e4ae 100644
--- a/.github/workflows/pythontest.yaml
+++ b/.github/workflows/pythontest.yaml
@@ -1,12 +1,12 @@
name: Test Python
-on: [push, pull_request]
+on: [ push, pull_request ]
jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.8]
+ python-version: [ 3.7 ]
steps:
- name: Checkout code
uses: actions/checkout@v2
@@ -25,35 +25,28 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.8]
+ python-version: [ 3.7 ]
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Build panoptes-pocs image
run: |
- PANOPTES_POCS=. INCLUDE_UTILS=false scripts/setup-local-environment.sh
+ docker-compose -f tests/docker-compose.yaml --env-file tests/env build
- name: Test with pytest in panoptes-pocs container
run: |
- mkdir -p coverage_dir && chmod 777 coverage_dir
- ci_env=`bash <(curl -s https://codecov.io/env)`
- docker run -i \
- $ci_env \
- -e REPORT_FILE="/tmp/coverage/coverage.xml" \
- --network "host" \
- -v $PWD/coverage_dir:/var/panoptes/logs \
- -v $PWD/coverage_dir:/tmp/coverage \
- panoptes-pocs:develop \
- "/var/panoptes/POCS/scripts/testing/run-tests.sh"
+ mkdir -p logs && chmod -R 777 logs
+ mkdir -p build && chmod -R 777 build
+ docker-compose -f tests/docker-compose.yaml --env-file tests/env run pocs pytest
- name: Upload coverage report to codecov.io
uses: codecov/codecov-action@v1
if: success()
with:
name: codecov-upload
- file: coverage_dir/coverage.xml
+ file: build/coverage.xml
fail_ci_if_error: true
- name: Create log file artifact
uses: actions/upload-artifact@v1
if: always()
with:
name: log-files
- path: coverage_dir/panoptes-testing.log
+ path: logs/panoptes-testing.log
diff --git a/.gitignore b/.gitignore
index 7dd8d4802..e3d5fac4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,5 @@ MANIFEST
# Per-project virtualenvs
.venv*/
**/.ipynb_checkpoints/**
+
+logs
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c852d5dbe..a9d1b8320 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,10 +1,115 @@
-CHANGELOG
=========
+Changelog
+=========
+
+[0.7.7] - 2021-01-19
+--------------------
+
+Added
+~~~~~
+
+* Conda environment file. (@wtgee #1066)
+* Add the `gsutil` to `google` install options. Required for uploading data. (@wtgee #1036, #1037)
+* Ability to specify autofocus plots in config file. (@wtgee #1029)
+* A "developer" version of the ``panoptes-pocs`` docker image is cloudbuilt automatically on merge with ``develop``. (@wtgee #1010)
+* Better error checking in cameras, including ability to store error. (@AnthonyHorton #1007)
+* Added ``error.InvalidConfig`` exception. (@wtgee #1007)
+* Config options to control camera processing options and allow for `defaults` in the config that applies to all cameras: (@wtgee #1007)
+
+ * ``cameras.defaults.compress_fits`` if FITS files should be fpacked. Default True.
+ * ``cameras.defaults.record_observations`` if observation metadata should be recorded. Default True.
+ * ``cameras.defaults.make_pretty_images`` to make jpgs from images. Default True.
+
+Breaking
+~~~~~~~~
+
+* The ``model`` parameter for the camera and subcomponents needs a fully resolved namespace for either the module or class. (@wtgee #1007)
+* The ``take_exposure`` method returns an event to indicate that exposure is in progress, **not** to indicate when exposure has completed. The event is stored in the camera object and accessible via ``camera.is_exposing``. (@wtgee #1007)
+* Removed camera temperature stability checking for now. (@wtgee #1007)
+* Moved the ``AbstractGphotoCamera`` class into it's own namespace and file. (@wtgee #1007)
+* Python moved back to 3.7. (#1021)
+
+
+Bug fixes
+~~~~~~~~~
+
+* Fix incorrect import of autofocus plots. (@wtgee #1034)
+* DSLR simulator cameras properly override the cooling defaults. (@wtgee #1001)
+* Stability checks for cooled cameras so they are only marked ``ready`` when cooled condition has stabilized. (@danjampro #990)
+* Properly closed the autofocus matplotlib figures. (@wtgee #1029)
+* Prevent thumbnails from being larger than image. (@wtgee #1029)
+
+Changed
+~~~~~~~
-All notable changes to this project will be documented in this file.
+* Clean up dependencies and offer extras install options. (@wtgee #1066)
+
+ * Split some hardware options, such as ``focuser``, which has extra dependencies.
+
+* Consolidate config files into ``conf_files`` dir. This includes targets and state machine files. (@wtgee #1066)
+* Change ``thumbnail_size`` to ``cutout_size`` consistently. (@wtgee #1040.)
+* Camera observation updates:
+
+ * TheSkyX utilities added (from ``panoptes-utils``). (@wtgee #1066)
+ * headers param fixed so truly optional. The POINTING keyword is checked in the metadata, not original headers. Closes #1002. (@wtgee #1009)
+ * Passing approved headers will actually write them to file. (@wtgee #1009)
+ * ``blocking=False`` param added. If True, will wait on observation_event. (@wtgee #1009)
+ * Renamed metadata variables to be consistent. (@wtgee #1009)
+ * ``_process_fits`` is responsible for writing the headers rather than calling out to panoptes-utils. Allows for easier overrides. (@wtgee #1009)
+ * dslr simulator readout time improved. (@wtgee #1009)
+ * ``process_exposure`` doesn't require the exposure_event to be passed because that is the cameras is_exposing property. (@wtgee #1009)
+ * The autofocus plotting has been moved to an external file. (@wtgee #1029)
+
+
+* Changelog cleanup. (@wtgee #1008)
+* ``panoptes-utils`` updates:
+
+ * Updated ``panoptes-utils`` to ``v0.2.30``. (@wtgee #1066)
+ * Updated ``panoptes-utils`` to ``v0.2.29``. (@wtgee #1021)
+ * Updated ``panoptes-utils`` to ``v0.2.28``. (@wtgee #1007)
+ * Updated ``panoptes-utils`` to ``v0.2.27`` to support the envvars for starting config server. (@wtgee #1001)
+
+* Move the ``wait-for-it.sh`` script into ``scripts``. (@wtgee #1001)
+* Camera:
+
+ * Changed how subcomponents for camera are created. (@wtgee #1007)
+ * Camera and subcomponent stringification changed for clarity. (@wtgee #1007)
+ * Can reassign SDK camera if same UID is presented with flag to ``create_cameras_from_config``. (@wtgee #1007)
+ * Add support for taking "dark" frames for cameras with mechanical shutters or opaque filters in the filterwheel. (@AnthonyHorton #989)
+ * ``_poll_exposure`` was needlessly being called in a ``threading.Timer`` rather than a simple ``threading.Event``. (@wtgee @1007)
+ * Slight improvements to the timeout and readout for exposures with the simulators. (@wtgee #1007)
+
+* Docker:
+
+ * Default ``$PANUSER`` is now ``pocs-user`` instead of ``panoptes``. (@wtgee #1066)
+ * Docker images default to ``latest`` instead of ``develop``. (@wtgee #1066)
+ * Removed ``developer`` docker image. (@wtgee #1066)
+ * Updated to match ``panoptes-utils`` Docker updates: removal of ``source-extractor`` and more. (@wtgee #1008)
+ * ``gphoto2`` comes from apt. (@wtgee #1007)
+ * Local setup script doesn't build ``panoptes-utils`` but assumes done otherwise or uses ``gcr.io``. (@wtgee #1007)
+
+* Testing:
+
+ * Added ``tests/env`` file for setting up testing. (@wtgee #1066)
+ * Config server is started as part of pytest again (reverting below). (@wtgee #1066)
+ * Testing is run from a locally built Docker image for both local and CI testing. (@wtgee #1001)
+ * Config file for testing is moved to ``$PANDIR/tests/testing.yaml``. (@wtgee #1001)
+ * Config server for testing is started external to ``pytest``, which is currently lowering coverage. (@wtgee #1001)
+ * Coverage reports are generated inside the Docker container. (@wtgee #1001)
+ * Default log level set to TRACE. (@wtgee #1007)
+ * Less hard-coding of fixtures and answers, more config server. (@wtgee #1007)
+ * Renamed the cameras in testing fixtures. (@wtgee #1007)
+ * Cooled cameras have temperature stability check in conftest. (@wtgee #1007)
+
+
+Removed
+~~~~~~~
+
+* Removed testing and local setup scripts. (@wtgee #1066)
+* Removed manuals from ``resources`` directory. (@wtgee #1066)
+* Removed all arduino files, to be replaced by Firmata. See instructions on gitbook docs. (@wtgee #1035)
+* Remove ``create_camera_simulator`` helper function. (@wtgee #1007)
-The format is based on `Keep a Changelog `__, and this project
-adheres to `Semantic Versioning `__.
[0.7.6] - 2020-08-21
--------------------
@@ -14,18 +119,18 @@ Changed
* Dependency updates:
- * `panoptes-utils` to `0.2.26`. (#995)
- * `panoptes-utils` to `0.2.21`. (#979)
- * `panoptes-utils` to `0.2.20`. (#974)
+ * ``panoptes-utils`` to ``0.2.26``. (#995)
+ * ``panoptes-utils`` to ``0.2.21``. (#979)
+ * ``panoptes-utils`` to ``0.2.20``. (#974)
* Install script. (#974)
* Env var file is sourced for zshrc and bashrc.
* Fix the clone of the repos in install script. (#978)
* Adding a date version to script. (#979)
- * `docker-compose` version bumped to `1.26.2`. (#979)
+ * ``docker-compose`` version bumped to ``1.26.2``. (#979)
* Better testing for ssh access. (#984)
- * Using `linuxserver.io docker-compose `_ so we also have `arm` version without work. (#986)
+ * Using `linuxserver.io docker-compose `_ so we also have ``arm`` version without work. (#986)
* Fixing conditional so script can proceed without restart. (#986)
* Generalizing install script in sections. (#986)
@@ -37,7 +142,7 @@ Changed
* Docker image updates (#972)
- * Updated `install-pocs.sh` script.
+ * Updated ``install-pocs.sh`` script.
* ``latest`` installs the ``panoptes-pocs`` module from pip
* ``develop`` installs via ``pip install -e[google.testing]`` and is used for running the CI tests.
* ``developer-env`` installs locally but with all options, i.e. ``pip install -e[google,testing,plotting,developer]``. Also builds ``jupyterlab`` and other developer tools. Starts a ``jupyterlab`` instance by default.
@@ -52,29 +157,29 @@ Changed
* Testing (#974)
* Removing all the dynamic config server info, making things a lot simpler.
- * `docker-compose` files for running tests.
+ * ``docker-compose`` files for running tests.
* Misc documentation updates.
* Code coverage no longer ignores test.
- * Testing is run via `panoptes-develop test`.
+ * Testing is run via ``panoptes-develop test``.
* Log files are rotated during each run.
* POCS (#974)
- * POCS instance cannot `initialize` unless it's `observatory.can_observe`.
- * Set `simulator` config item at start of `POCS` init method if `simulators` (note plural) is passed.
- * Simplification of the `run` method and the various predicates used to control it. Now just use the computed `keep_running`.
- * Adding some action flags to the `pocs.yaml` file.
- * Remove `POCS.check_environment` class method.
- * Add a `console_log_level` and `stderr_log_level`. The former is written to the log file in `$PANLOG` and is meant to be tailed in the console. The `stderr_log_level` is what would be displayed, e.g. in a jupyter notebook. (#977)
+ * POCS instance cannot ``initialize`` unless it's ``observatory.can_observe``.
+ * Set ``simulator`` config item at start of ``POCS`` init method if ``simulators`` (note plural) is passed.
+ * Simplification of the ``run`` method and the various predicates used to control it. Now just use the computed ``keep_running``.
+ * Adding some action flags to the ``pocs.yaml`` file.
+ * Remove ``POCS.check_environment`` class method.
+ * Add a ``console_log_level`` and ``stderr_log_level``. The former is written to the log file in ``$PANLOG`` and is meant to be tailed in the console. The ``stderr_log_level`` is what would be displayed, e.g. in a jupyter notebook. (#977)
* Mount simulator better name and stringify. (#977)
- * Global db object for `PanBase` (#977)
+ * Global db object for ``PanBase`` (#977)
* Allow for custom folder for metadata. (#979)
- * Default changed to `metadata`.
+ * Default changed to ``metadata``.
* Camera simulator cleanup. (#974)
* Scheduler (#974)
- * The `fields_file` is read when scheduler is created.
+ * The ``fields_file`` is read when scheduler is created.
[0.7.4] - 2020-05-31
--------------------
@@ -164,13 +269,13 @@ Changed
* Renamed codecov configuration file to be compliant.
* Switch to pyscaffold for package maintenance.
* "Waiting" method changes:
- * `sleep` has been renamed to `wait`.
+ * ``sleep`` has been renamed to ``wait``.
* All `status()` methods have been converted to properties that return a useful dict.
* Making proper abstractmethods.
* Documentation updates where found.
* Many log and f-string fixes.
-* `pocs.config_port` property available publicly.
-* horizon check for state happens directly in `run`.
+* ``pocs.config_port`` property available publicly.
+* horizon check for state happens directly in ``run``.
Removed
~~~~~~~
@@ -335,7 +440,7 @@ Changed
* State machine location more flexible
`209 `__,
`219 `__
-* Testing improvments
+* Testing improvements
`249 `__.
* Updates to many wiki pages.
* Misc bug fixes and improvements.
@@ -378,4 +483,3 @@ Added
* Automated testing with travis-ci.org
* Code coverage via codecov.io
* Basic install scripts
-
diff --git a/README.md b/README.md
index d79ac3841..be88185dc 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@ Welcome to POCS documentation!
==============================
-
+
@@ -10,40 +10,31 @@ Welcome to POCS documentation!
# Project PANOPTES
-[PANOPTES](https://www.projectpanoptes.org) is an open source citizen science project
-designed to find [transiting exoplanets](https://spaceplace.nasa.gov/transits/en/) with
-digital cameras. The goal of PANOPTES is to establish a global network of of robotic
-cameras run by amateur astronomers and schools (or anyone!) in order to monitor,
-as continuously as possible, a very large number of stars. For more general information
-about the project, including the science case and resources for interested individuals, see the
+[PANOPTES](https://www.projectpanoptes.org) is an open source citizen science project
+designed to find [transiting exoplanets](https://spaceplace.nasa.gov/transits/en/) with
+digital cameras. The goal of PANOPTES is to establish a global network of of robotic
+cameras run by amateur astronomers and schools (or anyone!) in order to monitor,
+as continuously as possible, a very large number of stars. For more general information
+about the project, including the science case and resources for interested individuals, see the
[project overview](https://projectpanoptes.org/articles/).
# POCS
-POCS (PANOPTES Observatory Control System) is the main software driver for a
-PANOPTES unit, responsible for high-level control of the unit.
+POCS (PANOPTES Observatory Control System) is the main software driver for a
+PANOPTES unit, responsible for high-level control of the unit.
For more information, see the full documentation at: https://pocs.readthedocs.io.
-[`panoptes-utils`](https://www.github.com/panoptes/panoptes-utils) is a related repository and POCS
-relies on most of the tools within `panoptes-utils`.
-
## Install
### POCS Environment
-If you are running a PANOPTES unit then you will most likely want an entire PANOPTES environment.
+If you are running a PANOPTES unit then you will most likely want an entire PANOPTES environment, which includes things like plate-solvers (to tell you what stars you are looking at) and other necessary tools for operation.
-There is a bash shell script that will install an entire working POCS system on your computer. Some
+There is a bash shell script that will install an entire working POCS system on your computer. Some
folks even report that it works on a Mac.
-The script will ask if you want to install in "developer" mode or not. If so, you should fork this repo, [panoptes-utils](https://github.com/panoptes/panoptes-utils), and [panoptes-tutorials](https://github.com/panoptes/panoptes-tutorials),
-and then give your github username when prompted.
-
-The non-developer mode of the script is intended for PANOPTES units.
-
-
-To install POCS via the script, open a terminal and enter:
+To install POCS via the script, open a terminal and enter (you will be prompted for your `sudo` password):
```bash
curl -fsSL https://install.projectpanoptes.org > install-pocs.sh
@@ -72,15 +63,76 @@ If you want the extra features, such as Google Cloud Platform connectivity, then
use the extras options:
```bash
-pip install "panoptes-pocs[google,testing]"
+pip install "panoptes-pocs[google,focuser,testing]"
+```
+
+#### Running POCS
+
+`POCS` requires three things to properly run:
+
+1. Environment variables that tell `POCS` the location of the main PANOPTES directory (`$PANDIR`).
+1. A [`panoptes-utils`](https://github.com/panoptes/panoptes-utils.git) `config-server` running to provide dynamic configuration.
+2. An `Observatory` instance that has details about the location of a POCS unit (real or simulated), which hardware is available, etc.
+
+A minimal working example with a simulated `Observatory` would be:
+
+```python
+import os
+from panoptes.utils.config.server import config_server
+from panoptes.pocs.observatory import Observatory
+from panoptes.pocs.core import POCS
+
+os.environ['PANDIR'] = '/var/panoptes'
+conf_server = config_server('conf_files/pocs.yaml')
+I 01-20 01:01:10.886 Starting panoptes-config-server with config_file='conf_files/pocs.yaml'
+S 01-20 01:01:10.926 Config server Loaded 17 top-level items
+I 01-20 01:01:10.928 Config items saved to flask config-server
+I 01-20 01:01:10.934 Starting panoptes config server with localhost:6563
+
+observatory = Observatory()
+I 01-20 01:01:16.157 Creating PanDB panoptes
+I 01-20 01:01:16.158 Initializing observatory
+I 01-20 01:01:16.158 Setting up location
+S 01-20 01:01:17.070 Observatory initialized
+
+pocs = POCS(observatory, simulators=['all'])
+I 01-20 01:01:20.408 Initializing PANOPTES unit - Generic PANOPTES Unit - Mauna Loa Observatory
+I 01-20 01:01:20.419 Making a POCS state machine from panoptes
+I 01-20 01:01:20.420 Loading state table: panoptes
+S 01-20 01:01:20.485 Unit says: Hi there!
+W 01-20 01:01:20.494 Scheduler not present
+W 01-20 01:01:20.495 Cameras not present
+W 01-20 01:01:20.496 Mount not present
+I 01-20 01:01:20.497 Scheduler not present, cannot get current observation.
+
+pocs.initialize()
+W 01-20 01:01:28.386 Scheduler not present
+W 01-20 01:01:28.388 Cameras not present
+W 01-20 01:01:28.389 Mount not present
+S 01-20 01:01:28.390 Unit says: Looks like we're missing some required hardware.
+Out[10]: False
```
-
-See the full documentation at: https://pocs.readthedocs.io
-### For helping develop POCS software
+For a more realistic usage, see the full documentation at: [https://pocs.readthedocs.io](https://pocs.readthedocs.io).
+
+For actually deploying a PANOPTES unit, refer to the [Operating Guider](https://projectpanoptes.gitbook.io/pocs-user-guide/operation/operating-guides).
+
+#### Developing POCS
See [Coding in PANOPTES](https://github.com/panoptes/POCS/wiki/Coding-in-PANOPTES)
+#### [Testing]
+
+To test the software, you can build a local [Docker](https://docs.docker.com/) image using [docker-compose](https://docs.docker.com/compose/install/).
+
+First clone the repository, then run the following from the project's root directory:
+
+```bash
+docker-compose -f tests/docker-compose.yaml --env-file tests/env build
+
+docker-compose -f tests/docker-compose.yaml --env-file tests/env run pocs pytest
+```
+
Links
-----
@@ -88,3 +140,5 @@ Links
- Forum: https://forum.projectpanoptes.org
- Documentation: https://pocs.readthedocs.io
- Source Code: https://github.com/panoptes/POCS
+
+[Testing]: #testing
diff --git a/bin/wait-for-it.sh b/bin/wait-for-it.sh
deleted file mode 100755
index 761bc5cee..000000000
--- a/bin/wait-for-it.sh
+++ /dev/null
@@ -1,179 +0,0 @@
-#!/usr/bin/env bash
-# https://github.com/vishnubob/wait-for-it
-# Use this script to test if a given TCP host/port are available
-
-WAITFORIT_cmdname=${0##*/}
-
-echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi }
-
-usage()
-{
- cat << USAGE >&2
-Usage:
- $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args]
- -h HOST | --host=HOST Host or IP under test
- -p PORT | --port=PORT TCP port under test
- Alternatively, you specify the host and port as host:port
- -s | --strict Only execute subcommand if the test succeeds
- -q | --quiet Don't output any status messages
- -t TIMEOUT | --timeout=TIMEOUT
- Timeout in seconds, zero for no timeout
- -- COMMAND ARGS Execute command with args after the test finishes
-USAGE
- exit 1
-}
-
-wait_for()
-{
- if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
- echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
- else
- echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout"
- fi
- WAITFORIT_start_ts=$(date +%s)
- while :
- do
- if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then
- nc -z $WAITFORIT_HOST $WAITFORIT_PORT
- WAITFORIT_result=$?
- else
- (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1
- WAITFORIT_result=$?
- fi
- if [[ $WAITFORIT_result -eq 0 ]]; then
- WAITFORIT_end_ts=$(date +%s)
- echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds"
- break
- fi
- sleep 1
- done
- return $WAITFORIT_result
-}
-
-wait_for_wrapper()
-{
- # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692
- if [[ $WAITFORIT_QUIET -eq 1 ]]; then
- timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
- else
- timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT &
- fi
- WAITFORIT_PID=$!
- trap "kill -INT -$WAITFORIT_PID" INT
- wait $WAITFORIT_PID
- WAITFORIT_RESULT=$?
- if [[ $WAITFORIT_RESULT -ne 0 ]]; then
- echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT"
- fi
- return $WAITFORIT_RESULT
-}
-
-# process arguments
-while [[ $# -gt 0 ]]
-do
- case "$1" in
- *:* )
- WAITFORIT_hostport=(${1//:/ })
- WAITFORIT_HOST=${WAITFORIT_hostport[0]}
- WAITFORIT_PORT=${WAITFORIT_hostport[1]}
- shift 1
- ;;
- --child)
- WAITFORIT_CHILD=1
- shift 1
- ;;
- -q | --quiet)
- WAITFORIT_QUIET=1
- shift 1
- ;;
- -s | --strict)
- WAITFORIT_STRICT=1
- shift 1
- ;;
- -h)
- WAITFORIT_HOST="$2"
- if [[ $WAITFORIT_HOST == "" ]]; then break; fi
- shift 2
- ;;
- --host=*)
- WAITFORIT_HOST="${1#*=}"
- shift 1
- ;;
- -p)
- WAITFORIT_PORT="$2"
- if [[ $WAITFORIT_PORT == "" ]]; then break; fi
- shift 2
- ;;
- --port=*)
- WAITFORIT_PORT="${1#*=}"
- shift 1
- ;;
- -t)
- WAITFORIT_TIMEOUT="$2"
- if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi
- shift 2
- ;;
- --timeout=*)
- WAITFORIT_TIMEOUT="${1#*=}"
- shift 1
- ;;
- --)
- shift
- WAITFORIT_CLI=("$@")
- break
- ;;
- --help)
- usage
- ;;
- *)
- echoerr "Unknown argument: $1"
- usage
- ;;
- esac
-done
-
-if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then
- echoerr "Error: you need to provide a host and port to test."
- usage
-fi
-
-WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15}
-WAITFORIT_STRICT=${WAITFORIT_STRICT:-0}
-WAITFORIT_CHILD=${WAITFORIT_CHILD:-0}
-WAITFORIT_QUIET=${WAITFORIT_QUIET:-0}
-
-# check to see if timeout is from busybox?
-WAITFORIT_TIMEOUT_PATH=$(type -p timeout)
-WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH)
-if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then
- WAITFORIT_ISBUSY=1
- WAITFORIT_BUSYTIMEFLAG="-t"
-
-else
- WAITFORIT_ISBUSY=0
- WAITFORIT_BUSYTIMEFLAG=""
-fi
-
-if [[ $WAITFORIT_CHILD -gt 0 ]]; then
- wait_for
- WAITFORIT_RESULT=$?
- exit $WAITFORIT_RESULT
-else
- if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then
- wait_for_wrapper
- WAITFORIT_RESULT=$?
- else
- wait_for
- WAITFORIT_RESULT=$?
- fi
-fi
-
-if [[ $WAITFORIT_CLI != "" ]]; then
- if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then
- echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess"
- exit $WAITFORIT_RESULT
- fi
- exec "${WAITFORIT_CLI[@]}"
-else
- exit $WAITFORIT_RESULT
-fi
diff --git a/conf_files/pocs.yaml b/conf_files/pocs.yaml
index e062a5ec4..350e54e71 100644
--- a/conf_files/pocs.yaml
+++ b/conf_files/pocs.yaml
@@ -22,7 +22,7 @@ location:
flat_horizon: -6 deg # Flats when sun between this and focus horizon.
focus_horizon: -12 deg # Dark enough to focus on stars.
observe_horizon: -18 deg # Sun below this limit to observe.
- obsctructions: []
+ obstructions: [ ]
timezone: US/Hawaii
gmt_offset: -600 # Offset in minutes from GMT during.
@@ -43,7 +43,7 @@ wait_delay: 180 # time in seconds before checking safety/etc while waiting.
max_transition_attempts: 5 # number of transitions attempts.
status_check_interval: 60 # periodic status check.
-state_machine: simple_state_table
+state_machine: panoptes
scheduler:
type: dispatch
@@ -69,21 +69,45 @@ pointing:
max_iterations: 5
cameras:
- auto_detect: True
- primary: 14d3bd
+ defaults:
+ primary: None
+ auto_detect: False
+ file_extension: fits
+ compress_fits: False
+ make_pretty_images: False
+ keep_jpgs: False
+ readout_time: 0.5 # seconds
+ timeout: 10 # seconds
+ filter_type: RGGB
+ cooling:
+ enabled: False
+ temperature:
+ target: 0 # celsius
+ tolerance: 0.1 # celsius
+ stable_time: 60 # seconds
+ check_interval: 5 # seconds
+ timeout: 300 # seconds
+ filterwheel:
+ model: panoptes.pocs.filterwheel.simulator.FilterWheel
+ filter_names: [ ]
+ move_time: 0.1 # seconds
+ timeout: 0.5 # seconds
+ focuser:
+ enabled: False
+ autofocus_seconds: 0.1 # seconds
+ autofocus_size: 500 # seconds
+ autofocus_keep_files: False
+ autofocus_make_plots: False
devices:
- - model: canon_gphoto2
- - model: canon_gphoto2
+ - model: panoptes.pocs.camera.gphoto.canon.Camera
+ name: dslr.00
+ file_extension: cr2
+ - model: panoptes.pocs.camera.gphoto.canon.Camera
+ name: dslr.01
+ file_extension: cr2
######################### Environmental Sensors ################################
-# Configure the environmental sensors that are attached.
-#
-# Use `auto_detect: True` for most options. Or use a manual configuration:
-#
-# camera_board:
-# serial_port: /dev/ttyACM0
-# control_board:
-# serial_port: /dev/ttyACM1
+# Configuration for the power distribution board and other environmental sensors.
################################################################################
environment:
auto_detect: False
@@ -112,6 +136,9 @@ environment:
################################################################################
observations:
make_timelapse: True
+ compress_fits: True
+ record_observations: True
+ make_pretty_images: True
keep_jpgs: True
######################## Google Network ########################################
@@ -140,4 +167,4 @@ pocs:
INTERRUPTED: false
KEEP_RUNNING: true
DO_STATES: true
- RUN_ONCE: false
\ No newline at end of file
+ RUN_ONCE: false
diff --git a/resources/state_table/simple_state_table.yaml b/conf_files/state_table/panoptes.yaml
similarity index 100%
rename from resources/state_table/simple_state_table.yaml
rename to conf_files/state_table/panoptes.yaml
diff --git a/resources/targets/simple.yaml b/conf_files/targets/simple.yaml
similarity index 89%
rename from resources/targets/simple.yaml
rename to conf_files/targets/simple.yaml
index 8481252e9..ed8c00d4d 100644
--- a/resources/targets/simple.yaml
+++ b/conf_files/targets/simple.yaml
@@ -13,7 +13,7 @@
-
name: HD 23630
position: 03h47m29.077s +24d06m18.49s
- priority: 100
+ priority: 100
-
name: HD 189733
position: 20h00m43.713s +22d42m39.07s
@@ -26,7 +26,7 @@
-
name: Wasp 140
position: 04h01m32.54s -20d27m03.9s
- priority: 100
+ priority: 100
-
name: Wasp 104
position: 10h42m24.61s +07d26m06.3s
@@ -54,7 +54,7 @@
-
name: Wasp 43
position: 10h19m38.008s -09d48m22.59s
- priority: 100
+ priority: 100
-
name: Wasp 36
position: 08h46m19.30s -08d01m36.7s
@@ -62,11 +62,11 @@
-
name: Wasp 11
position: 03h09m28.54s +30d40m26.0s
- priority: 100
+ priority: 100
-
name: Wasp 35
position: 05h04m19.56s -06d13m47.2s
- priority: 100
+ priority: 100
-
name: HAT-P-20
position: 07h27m39.89s +24d20m14.7s
@@ -78,7 +78,7 @@
-
name: Qatar-2
position: 13h50m37.409s -06d48m14.4101s
- priority: 100
+ priority: 100
-
name: Tres 3
position: 17h52m07.02s +37d32m46.2012s
@@ -102,15 +102,15 @@
-
name: HAT-P-30
position: 08h15m47.98s +05d50m12.3s
- priority: 100
+ priority: 100
-
name: HAT-P-36
position: 12h33m03.909s +44d54m55.18s
- priority: 100
+ priority: 100
-
name: HAT-P-37
position: 18h57m11.058s +51d16m08.8601s
- priority: 100
+ priority: 100
-
name: M42
position: 05h35m17.2992s -05d23m27.996s
@@ -124,12 +124,12 @@
-
name: M45
position: 03h47m24s +24d07m01.20s
- priority: 50
+ priority: 50
-
name: M5
position: 15h18m33.2201s +02d04m51.7008s
priority: 50
-
name: CoRoT-18
- position: 06h32m41.38s -00d01m53.s
+ position: 06h32m41.38s -00d01m53.s
priority: 100
diff --git a/resources/targets/simulator.yaml b/conf_files/targets/simulator.yaml
similarity index 100%
rename from resources/targets/simulator.yaml
rename to conf_files/targets/simulator.yaml
diff --git a/resources/targets/tess_sectors_north.yaml b/conf_files/targets/tess_sectors_north.yaml
similarity index 100%
rename from resources/targets/tess_sectors_north.yaml
rename to conf_files/targets/tess_sectors_north.yaml
diff --git a/resources/targets/tess_sectors_south.yaml b/conf_files/targets/tess_sectors_south.yaml
similarity index 100%
rename from resources/targets/tess_sectors_south.yaml
rename to conf_files/targets/tess_sectors_south.yaml
diff --git a/conftest.py b/conftest.py
index 73df248e6..04f3f7f2c 100644
--- a/conftest.py
+++ b/conftest.py
@@ -1,49 +1,70 @@
import logging
import os
+import shutil
import stat
-import pytest
-import time
import tempfile
-import shutil
from contextlib import suppress
-from _pytest.logging import caplog as _caplog
+import pytest
+from _pytest.logging import caplog as _caplog # noqa
from panoptes.pocs import hardware
-from panoptes.utils.database import PanDB
-from panoptes.utils.config.client import get_config
+from panoptes.pocs.utils.logger import get_logger
+from panoptes.pocs.utils.logger import PanLogger
from panoptes.utils.config.client import set_config
from panoptes.utils.config.server import config_server
-from panoptes.pocs.utils.logger import get_logger, PanLogger
-
# TODO download IERS files.
_all_databases = ['file', 'memory']
+TESTING_LOG_LEVEL = 'TRACE'
LOGGER_INFO = PanLogger()
-logger = get_logger()
+logger = get_logger(console_log_file=TESTING_LOG_LEVEL)
logger.enable('panoptes')
-logger.level("testing", no=15, icon="🤖", color="")
-log_file_path = os.path.join(
- os.getenv('PANLOG', '/var/panoptes/logs'),
- 'panoptes-testing.log'
-)
-startup_message = ' STARTING NEW PYTEST RUN '
+# Add a level above TRACE and below DEBUG
+logger.level("testing", no=15, icon="🤖", color="")
+log_fmt = "{level:.1s} " \
+ "{time:MM-DD HH:mm:ss.SSS!UTC}>" \
+ " ({time:HH:mm:ss zz})> " \
+ "| {name} {function}:{line} | " \
+ "{message}"
+
+log_file_path = os.path.expandvars('${PANLOG}/panoptes-testing.log')
+startup_message = f' STARTING NEW PYTEST RUN - LOGS: {log_file_path} '
logger.add(log_file_path,
enqueue=True, # multiprocessing
+ format=log_fmt,
colorize=True,
+ # TODO decide on these options
backtrace=True,
diagnose=True,
catch=True,
# Start new log file for each testing run.
rotation=lambda msg, _: startup_message in msg,
- level='TRACE')
+ level=TESTING_LOG_LEVEL)
+
logger.log('testing', '*' * 25 + startup_message + '*' * 25)
+
# Make the log file world readable.
os.chmod(log_file_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
+def pytest_configure(config):
+ """Set up the testing."""
+ logger.info('Setting up the config server.')
+ config_file = 'tests/testing.yaml'
+
+ host = 'localhost'
+ port = '8765'
+
+ os.environ['PANOPTES_CONFIG_HOST'] = host
+ os.environ['PANOPTES_CONFIG_PORT'] = port
+
+ config_server(config_file, host=host, port=port, load_local=False, save_local=False)
+ logger.success('Config server set up')
+
+
def pytest_addoption(parser):
hw_names = ",".join(hardware.get_all_names()) + ' (or all for all hardware)'
db_names = ",".join(_all_databases) + ' (or all for all databases)'
@@ -65,6 +86,11 @@ def pytest_addoption(parser):
help=f"Test databases in the list. List items can include: {db_names}. Note that "
f"travis-ci will test all of "
f"them by default.")
+ group.addoption(
+ "--theskyx",
+ action='store_true',
+ default=False,
+ help=f"Test TheSkyX commands, default False -- CURRENTLY NOT WORKING!")
def pytest_collection_modifyitems(config, items):
@@ -88,7 +114,7 @@ def pytest_collection_modifyitems(config, items):
# with_hardware is a list of hardware names for which we have that hardware attached.
with_hardware = hardware.get_simulator_names(simulator=config.getoption('--with-hardware'))
- for name in without_hardware:
+ for name in without_hardware: # noqa
# User does not want to run tests that interact with hardware called name,
# whether it is marked as with_name or without_name.
if name in with_hardware:
@@ -139,52 +165,14 @@ def pytest_runtest_logfinish(nodeid, location):
logger.log('testing', '##########' * 8)
-def pytest_runtest_logreport(report):
- """Adds the failure info that pytest prints to stdout into the log."""
- if report.skipped or report.outcome != 'failed':
- return
- with suppress(Exception):
- logger.log('testing', '')
- logger.log('testing',
- f' TEST {report.nodeid} FAILED during {report.when} {report.longreprtext} ')
- if report.capstdout:
- logger.log('testing',
- f'============ Captured stdout during {report.when} {report.capstdout} '
- f'============')
- if report.capstderr:
- logger.log('testing',
- f'============ Captured stdout during {report.when} {report.capstderr} '
- f'============')
-
-
@pytest.fixture(scope='session')
-def config_path():
- return os.path.expandvars('${POCS}/tests/pocs_testing.yaml')
-
+def config_host():
+ return os.getenv('PANOPTES_CONFIG_HOST', 'localhost')
-@pytest.fixture(scope='module', autouse=True)
-def static_config_server(config_path, images_dir, db_name):
- logger.log('testing', f'Starting static_config_server for testing session')
- proc = config_server(
- config_path,
- ignore_local=True,
- auto_save=False
- )
-
- logger.log('testing', f'static_config_server started with {proc.pid=}')
-
- # Give server time to start
- while get_config('name') is None: # pragma: no cover
- logger.log('testing', f'Waiting for static_config_server {proc.pid=}, sleeping 1 second.')
- time.sleep(1)
-
- set_config('directories.images', images_dir)
-
- logger.log('testing', f'Startup config_server name=[{get_config("name")}]')
- yield
- logger.log('testing', f'Killing static_config_server started with PID={proc.pid}')
- proc.terminate()
+@pytest.fixture(scope='session')
+def config_port():
+ return os.getenv('PANOPTES_CONFIG_PORT', 6563)
@pytest.fixture
@@ -193,41 +181,17 @@ def temp_file(tmp_path):
d.mkdir(exist_ok=True)
f = d / 'temp'
yield f
- f.unlink(missing_ok=True)
-
-
-@pytest.fixture(scope='session')
-def db_name():
- return 'panoptes_testing'
+ with suppress(FileNotFoundError):
+ f.unlink()
@pytest.fixture(scope='session')
def images_dir(tmpdir_factory):
directory = tmpdir_factory.mktemp('images')
+ set_config('directories.images', str(directory))
return str(directory)
-@pytest.fixture(scope='function', params=_all_databases)
-def db_type(request, db_name):
- db_list = request.config.option.test_databases
- if request.param not in db_list and 'all' not in db_list:
- pytest.skip(f"Skipping {request.param} DB, set --test-all-databases=True")
-
- PanDB.permanently_erase_database(request.param, db_name, really='Yes', dangerous='Totally')
- return request.param
-
-
-@pytest.fixture(scope='function')
-def db(db_type, db_name):
- return PanDB(db_type=db_type, db_name=db_name, connect=True)
-
-
-@pytest.fixture(scope='function')
-def memory_db(db_name):
- PanDB.permanently_erase_database('memory', db_name, really='Yes', dangerous='Totally')
- return PanDB(db_type='memory', db_name=db_name)
-
-
@pytest.fixture(scope='session')
def data_dir():
return os.path.expandvars('${POCS}/tests/data')
@@ -270,7 +234,7 @@ def noheader_fits_file(data_dir):
@pytest.fixture(scope='function')
-def cr2_file(data_dir):
+def cr2_file(data_dir): # noqa
cr2_path = os.path.join(data_dir, 'canon.cr2')
if not os.path.exists(cr2_path):
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 110ddb99e..1958edfc7 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,63 +1,68 @@
-ARG image_url=gcr.io/panoptes-exp/panoptes-utils:latest
-FROM ${image_url} AS pocs-base
+FROM ubuntu:latest
-LABEL description="Installs the panoptes-pocs module from GitHub. \
-Used as a production image, i.e. for running on PANOPTES units."
+LABEL description="Installs the panoptes-pocs module from GitHub."
LABEL maintainers="developers@projectpanoptes.org"
LABEL repo="github.com/panoptes/POCS"
-ARG panuser=panoptes
-ARG userid=1000
-ARG pan_dir=/var/panoptes
-ARG pocs_dir="${pan_dir}/POCS"
-ARG conda_env_name="panoptes"
-
-ARG arduino_url="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh"
-ARG gphoto2_url="https://raw.githubusercontent.com/gonzalo/gphoto2-updater/master/gphoto2-updater.sh"
-ARG pip_extras="[testing,google]"
-
ENV DEBIAN_FRONTEND=noninteractive
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
-ENV PANDIR $pan_dir
-ENV PANLOG "$pan_dir/logs"
+ARG panuser=pocs-user
+ARG userid=1000
+ARG pan_dir=/var/panoptes
+
ENV PANUSER $panuser
-ENV POCS $pocs_dir
+ENV USERID $userid
+ENV PANDIR $pan_dir
# Install system dependencies.
-USER root
RUN apt-get update && apt-get install --no-install-recommends --yes \
- gcc \
- gphoto2 \
- ncurses-dev \
- readline-common \
- udev
-
-# Install program dependencies.
-USER ${PANUSER}
-WORKDIR ${POCS}
-RUN mkdir -p "${PANDIR}/scripts" && \
- cd "${PANDIR}/scripts" && \
- # Install gphoto2 auto-updater
- wget $gphoto2_url -O gphoto2-updater.sh && \
- chmod +x gphoto2-updater.sh && \
- # Don't actually update gphoto2 as of right now.
- # sudo /bin/bash gphoto2-updater.sh --stable && \
- # Install arduino-cli.
- wget -q "${arduino_url}" -O install-arduino-cli.sh && \
- sudo BINDIR="/usr/local/bin" /bin/sh install-arduino-cli.sh && \
- sudo chown -R "${PANUSER}":"${PANUSER}" "${PANDIR}"
-
-# Install the module.
-USER ${PANUSER}
-# Can't seem to get around the hard-coding here.
-COPY --chown=panoptes:panoptes . .
-RUN "${PANDIR}/conda/envs/${conda_env_name}/bin/pip" install -e ".${pip_extras}" && \
+ bzip2 ca-certificates \
+ wget gcc git pkg-config sudo gosu less udev \
+ astrometry.net astrometry-data-tycho2-10-19 dcraw exiftool \
+ libcfitsio-dev libcfitsio-bin \
+ libfreetype6-dev libpng-dev libjpeg-dev libffi-dev \
+ gphoto2 && \
+ useradd -u ${USERID} -o -c "PANOPTES POCS" \
+ -p panoptes -m -G plugdev,dialout,users,sudo ${PANUSER} && \
+ # Allow sudo without password.
+ echo "%sudo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+# Miniconda
+WORKDIR /tmp
+RUN wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-$(uname -m).sh" \
+ -O install-miniforge.sh && \
+ /bin/sh /tmp/install-miniforge.sh -b -f -p /conda && \
+ # Initialize conda for the shells.
+ /conda/bin/conda init bash
+
+COPY environment.yaml .
+RUN /conda/bin/conda env update -n base -f environment.yaml && \
+ # Create PANOPTES directories.
+ mkdir -p ${PANDIR} && \
+ mkdir -p ${PANDIR}/logs && \
+ mkdir -p ${PANDIR}/images
+
+ARG pocs_dir="${PANDIR}/POCS"
+ENV POCS $pocs_dir
+ENV PATH "/home/${PANUSER}/.local/bin:$PATH"
+
+ARG pip_extras="[google,testing]"
+
+USER "${userid}"
+WORKDIR "${POCS}"
+COPY --chown="${userid}:${userid}" . .
+RUN echo "Installing editable module with ${pip_extras}" && \
+ sudo chown -R ${PANUSER}:${PANUSER} ${PANDIR} && \
+ /conda/bin/pip install -e ".${pip_extras}" && \
+ # Remove git folder
+ rm -rf "${POCS}/.git" && \
# Cleanup
sudo apt-get autoremove --purge --yes && \
sudo apt-get autoclean --yes && \
sudo apt-get --yes clean && \
- sudo rm -rf /var/lib/apt/lists/* && \
- "${PANDIR}/conda/bin/conda" clean -tipy
+ sudo rm -rf /var/lib/apt/lists/*
USER root
+ENTRYPOINT ["/bin/sh", "/var/panoptes/POCS/scripts/entrypoint.sh"]
+CMD [ "/bin/bash" ]
diff --git a/docker/README.md b/docker/README.md
index dce637c34..afc4a3cef 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -4,35 +4,3 @@ Docker Images
POCS is available as a docker image hosted on Google Cloud Registry (GCR):
Image name: `gcr.io/panoptes-exp/panoptes-pocs:latest`
-
-### `develop` image
-
-To build the images locally:
-
-```bash
-scripts/setup-local-environment.sh
-```
-
-This will build all required images locally and is suitable for testing and development.
-
-Then, to run the test suite locally:
-
-```bash
-scripts/testing/test-software.sh
-````
-
-### `developer` image
-
-The `developer` image is meant to be be used by developers or anyone wishing to
-explore the code. It is the same as the local `develop`, but also installs additional
-plotting libraries and the `jupyter` environment.
-
-The image should be built locally using the `docker/setup-local-environment.sh`
-script (see above).
-
-The `bin/panoptes-develop up` can then be used to start a docker container
-instance that will launch `jupyter lab` from `$PANDIR` automatically.
-
-```bash
-bin/panoptes-develop up
-```
diff --git a/docker/cloudbuild.yaml b/docker/cloudbuild.yaml
index 90c69bfcc..39721473c 100644
--- a/docker/cloudbuild.yaml
+++ b/docker/cloudbuild.yaml
@@ -5,7 +5,6 @@ timeout: 18000s # 5 hours
substitutions:
_PLATFORMS: linux/amd64,linux/arm64
- _BASE_IMAGE: panoptes-utils:latest
_IMAGE_NAME: panoptes-pocs
_REPO_URL: https://github.com/panoptes/POCS
_TAG: latest
@@ -14,15 +13,15 @@ steps:
# Fetch the repo from github
- name: gcr.io/cloud-builders/git
id: "clone-repo"
- args: ["clone", "${_REPO_URL}"]
- waitFor: ["-"]
+ args: [ "clone", "${_REPO_URL}" ]
+ waitFor: [ "-" ]
# Pull the cached image.
- name: 'gcr.io/cloud-builders/docker'
id: "pull-cached-image"
entrypoint: 'bash'
- args: ['-c', 'docker pull gcr.io/${PROJECT_ID}/${_IMAGE_NAME}:${_TAG} || exit 0']
- waitFor: ["-"]
+ args: [ '-c', 'docker pull gcr.io/${PROJECT_ID}/${_IMAGE_NAME}:${_TAG} || exit 0' ]
+ waitFor: [ "-" ]
# Set up multiarch support
- name: "gcr.io/cloud-builders/docker"
@@ -34,7 +33,7 @@ steps:
- "--privileged"
- "--rm"
- "docker/binfmt:a7996909642ee92942dcd6cff44b9b95f08dad64"
- waitFor: ["-"]
+ waitFor: [ "-" ]
# Build builder
- name: "gcr.io/cloud-builders/docker"
@@ -46,9 +45,9 @@ steps:
- "create"
- "--use"
- "--driver=docker-container"
- waitFor: ["setup-buildx"]
+ waitFor: [ "setup-buildx" ]
- # Build with cloned panoptes-utils as source directory
+ # Build with cloned panoptes-utils as source directory.
- name: "gcr.io/cloud-builders/docker"
id: "build-images"
env:
@@ -59,8 +58,7 @@ steps:
- "--push"
- "--platform=${_PLATFORMS}"
- "-f=docker/Dockerfile"
- - "--build-arg=image_url=gcr.io/${PROJECT_ID}/${_BASE_IMAGE}"
- "--tag=gcr.io/${PROJECT_ID}/${_IMAGE_NAME}:${_TAG}"
- "--cache-from=gcr.io/${PROJECT_ID}/${_IMAGE_NAME}:${_TAG}"
- "POCS"
- waitFor: ["build-builder", "clone-repo"]
+ waitFor: [ "build-builder", "clone-repo" ]
diff --git a/docker/developer/Dockerfile b/docker/developer/Dockerfile
deleted file mode 100644
index 4e0fcd78e..000000000
--- a/docker/developer/Dockerfile
+++ /dev/null
@@ -1,59 +0,0 @@
-ARG BASE_IMAGE=panoptes-pocs:develop
-FROM ${BASE_IMAGE}
-
-LABEL description="Installs a number of developer tools. Runs jupyter lab instance. \
-This assumes the `panoptes-pocs:develop` has already been built locally."
-LABEL maintainers="developers@projectpanoptes.org"
-LABEL repo="github.com/panoptes/POCS"
-
-ARG panuser=panoptes
-ARG userid=1000
-ARG pan_dir=/var/panoptes
-ARG pocs_dir="${pan_dir}/POCS"
-ARG conda_env_name="panoptes"
-
-ENV DEBIAN_FRONTEND=noninteractive
-ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
-ENV SHELL /bin/zsh
-
-ENV USERID $userid
-ENV PANDIR $pan_dir
-ENV PANLOG "$pan_dir/logs"
-ENV PANUSER $panuser
-ENV POCS $pocs_dir
-
-RUN apt-get update && \
- # Make a developer's life easier.
- apt-get install --yes --no-install-recommends \
- bzip2 ca-certificates nano neovim \
- ncdu htop
-
-USER $PANUSER
-RUN echo "Installing developer tools" && \
- "${PANDIR}/conda/bin/conda" install --name "${conda_env_name}" \
- altair \
- bokeh \
- httpie \
- jupyterlab \
- jq \
- holoviews \
- hvplot \
- nodejs \
- seaborn && \
- # Set some jupyterlab defaults.
- mkdir -p /home/panoptes/.jupyter && \
- /usr/bin/env zsh -c "${PANDIR}/conda/envs/${conda_env_name}/bin/jupyter-lab --no-browser --generate-config" && \
- # Jupyterlab extensions.
- echo "c.JupyterApp.answer_yes = True" >> \
- "/home/panoptes/.jupyter/jupyter_notebook_config.py" && \
- echo "c.JupyterApp.open_browser = False" >> \
- "/home/panoptes/.jupyter/jupyter_notebook_config.py" && \
- echo "c.JupyterApp.notebook_dir = '${PANDIR}'" >> \
- "/home/panoptes/.jupyter/jupyter_notebook_config.py" && \
- # Cleanup
- sudo apt-get autoremove --purge --yes && \
- sudo apt-get autoclean --yes && \
- sudo rm -rf /var/lib/apt/lists/*
-
-USER root
-WORKDIR ${PANDIR}
\ No newline at end of file
diff --git a/docker/developer/docker-compose.yaml b/docker/developer/docker-compose.yaml
deleted file mode 100644
index c96ea10d6..000000000
--- a/docker/developer/docker-compose.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-version: '3.7'
-services:
- config-server:
- image: "panoptes-utils:develop"
- init: true
- container_name: config-server
- privileged: true
- network_mode: host
- restart: on-failure
- volumes:
- - pocsdir:/var/panoptes/POCS
- command: ["panoptes-config-server run /var/panoptes/POCS/conf_files/pocs.yaml"]
- developer:
- image: "${IMAGE:-panoptes-pocs}:${TAG:-developer}"
- init: true
- container_name: "${CONTAINER_NAME:-pocs-developer}"
- privileged: true
- network_mode: host
- depends_on:
- - "config-server"
- volumes:
- - pocsdir:/var/panoptes/POCS
- command: ["jupyter lab", "--ip=0.0.0.0"]
-volumes:
- pocsdir:
- driver: local
- driver_opts:
- type: none
- device: /var/panoptes/POCS
- o: bind
-
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 5cb77c273..d70c81d43 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -1,47 +1,58 @@
version: '3.7'
services:
- peas-shell:
- image: gcr.io/panoptes-exp/panoptes-pocs:latest
+ config-server:
+ image: panoptes-pocs:latest
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
init: true
- container_name: peas-shell
- hostname: peas-shell
- privileged: true
+ tty: true
+ container_name: config-server
+ hostname: config-server
network_mode: host
- env_file: $PANDIR/env
+ environment:
+ # These need to be defined in the shell on the host.
+ PANDIR:
+ PANLOG:
+ PANOPTES_CONFIG_HOST:
+ PANOPTES_CONFIG_PORT:
+ PANOPTES_CONFIG_FILE:
+ restart: on-failure
volumes:
- - pandir:/var/panoptes
- # No-op to keep machine running, use $POCS/bin/peas-shell to access
- command:
- - "$PANDIR/panoptes-utils/bin/wait-for-it.sh"
- - "localhost:6563"
- - "--"
- - "tail"
- - "-f"
- - "/dev/null"
- pocs-shell:
- image: gcr.io/panoptes-exp/panoptes-pocs:latest
+ - pocsdir:/var/panoptes/POCS
+ - confdir:/var/panoptes/config_files
+ command: [ "panoptes-config-server --verbose run" ]
+ pocs:
+ image: panoptes-pocs:latest
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
init: true
- container_name: pocs-shell
- hostname: pocs-shell
+ container_name: pocs
+ hostname: pocs
privileged: true
network_mode: host
- env_file: $PANDIR/env
- depends_on:
- - "peas-shell"
+ environment:
+ # These need to be defined in the shell on the host.
+ PANDIR:
+ PANLOG:
+ PANOPTES_CONFIG_HOST:
+ PANOPTES_CONFIG_PORT:
+ PANOPTES_CONFIG_FILE:
volumes:
- - pandir:/var/panoptes
+ - pocsdir:/var/panoptes/POCS
# No-op to keep machine running, use $POCS/bin/pocs-shell to access
- command:
- - "$PANDIR/panoptes-utils/bin/wait-for-it.sh"
- - "localhost:6563"
- - "--"
- - "tail"
- - "-f"
- - "/dev/null"
+ command: [ "wait-for-it ${PANOPTES_CONFIG_PORT}:${PANOPTES_CONFIG_PORT} -- echo POCS yo!" ]
volumes:
- pandir:
- driver: local
- driver_opts:
- type: none
- device: /var/panoptes
- o: bind
+ pocsdir:
+ driver: local
+ driver_opts:
+ type: none
+ device: /var/panoptes/POCS
+ o: bind
+ confdir:
+ driver: local
+ driver_opts:
+ type: none
+ device: /var/panoptes/config_files
+ o: bind
diff --git a/env b/env
new file mode 100644
index 000000000..fb58cef63
--- /dev/null
+++ b/env
@@ -0,0 +1,13 @@
+# Envfile for POCS operation.
+#
+# Note this doesn't support full interpolation or quotes.
+# See https://docs.docker.com/compose/compose-file/#env_file
+#
+# THESE ARE VALUES INSIDE A RUNNING DOCKER CONTAINER.
+#
+PANDIR=/var/panoptes
+POCS=/var/panoptes/POCS
+PANLOG=/var/panoptes/logs
+PANOPTES_CONFIG_FILE=/var/panoptes/POCS/conf_files/pocs.yaml
+PANOPTES_CONFIG_HOST=0.0.0.0
+PANOPTES_CONFIG_PORT=6563
diff --git a/environment.yaml b/environment.yaml
new file mode 100644
index 000000000..c8daadb96
--- /dev/null
+++ b/environment.yaml
@@ -0,0 +1,10 @@
+channels:
+ - https://conda.anaconda.org/conda-forge
+dependencies:
+ - matplotlib-base
+ - numpy
+ - readline # POCS shell
+ - scipy
+ - pip
+ - pip:
+ - panoptes-utils[config,images,social] >= 0.2.30
diff --git a/resources/arduino_files/camera_board/Adafruit_MMA8451.cpp b/resources/arduino_files/camera_board/Adafruit_MMA8451.cpp
deleted file mode 120000
index 2e51ad652..000000000
--- a/resources/arduino_files/camera_board/Adafruit_MMA8451.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/Adafruit_MMA8451/Adafruit_MMA8451.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/Adafruit_MMA8451.h b/resources/arduino_files/camera_board/Adafruit_MMA8451.h
deleted file mode 120000
index c62ece80b..000000000
--- a/resources/arduino_files/camera_board/Adafruit_MMA8451.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/Adafruit_MMA8451/Adafruit_MMA8451.h
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/Adafruit_Sensor.cpp b/resources/arduino_files/camera_board/Adafruit_Sensor.cpp
deleted file mode 120000
index 09a3ddbfe..000000000
--- a/resources/arduino_files/camera_board/Adafruit_Sensor.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/Adafruit_Sensor/Adafruit_Sensor.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/Adafruit_Sensor.h b/resources/arduino_files/camera_board/Adafruit_Sensor.h
deleted file mode 120000
index 180c14555..000000000
--- a/resources/arduino_files/camera_board/Adafruit_Sensor.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/Adafruit_Sensor/Adafruit_Sensor.h
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/DHT.cpp b/resources/arduino_files/camera_board/DHT.cpp
deleted file mode 120000
index fec37f7c9..000000000
--- a/resources/arduino_files/camera_board/DHT.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/DHT.h b/resources/arduino_files/camera_board/DHT.h
deleted file mode 120000
index 024c035f2..000000000
--- a/resources/arduino_files/camera_board/DHT.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.h
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/PinUtils.cpp b/resources/arduino_files/camera_board/PinUtils.cpp
deleted file mode 120000
index 509845a44..000000000
--- a/resources/arduino_files/camera_board/PinUtils.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/PinUtils.h b/resources/arduino_files/camera_board/PinUtils.h
deleted file mode 120000
index 89359569c..000000000
--- a/resources/arduino_files/camera_board/PinUtils.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.h
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/camera_board.ino b/resources/arduino_files/camera_board/camera_board.ino
deleted file mode 100644
index 6d186193f..000000000
--- a/resources/arduino_files/camera_board/camera_board.ino
+++ /dev/null
@@ -1,221 +0,0 @@
-#include
-#include
-
-#include "Adafruit_MMA8451.h"
-#include "Adafruit_Sensor.h"
-#include "dht_handler.h"
-#include "PinUtils.h"
-
-const int DHT_PIN = 9; // DHT Temp & Humidity Pin
-const int CAM_0_RELAY = 5;
-const int CAM_1_RELAY = 6;
-const int RESET_PIN = 12;
-
-// Type of Digital Humidity and Temperature (DHT) Sensor
-#define DHTTYPE DHT22 // DHT 22 (AM2302)
-
-// IO Handlers.
-
-class AccelerometerHandler {
- public:
- void init() {
- ready_ = false;
- has_reading_ = false;
- ready();
- }
- void collect() {
- if (ready()) {
- accelerometer_.getEvent(&event_);
- orientation_ = accelerometer_.getOrientation(); // Orientation
- has_reading_ = true;
- }
- }
- void report() {
- if (has_reading_) {
- Serial.print(", \"accelerometer\":{\"x\":");
- Serial.print(event_.acceleration.x);
- Serial.print(", \"y\":");
- Serial.print(event_.acceleration.y);
- Serial.print(", \"z\":");
- Serial.print(event_.acceleration.z);
- Serial.print(", \"o\": "); Serial.print(orientation_);
- Serial.print("}");
- }
- }
- bool ready() {
- if (!ready_) {
- ready_ = accelerometer_.begin();
- if (!ready_) {
- Serial.println("Accelerometer not ready, or not present.");
- } else {
- // Check Accelerometer range
- // accelerometer.setRange(MMA8451_RANGE_2_G);
- // Serial.print("Accelerometer Range = "); Serial.print(2 << accelerometer.getRange());
- // Serial.println("G");
- }
- }
- return ready_;
- }
-
- private:
- Adafruit_MMA8451 accelerometer_;
- sensors_event_t event_;
- uint8_t orientation_;
- bool ready_;
- bool has_reading_;
-} acc_handler;
-
-// DHT22: Relative Humidity & Temperature Sensor.
-DHTHandler dht_handler(DHT_PIN, DHTTYPE);
-
-unsigned long end_setup_millis;
-unsigned long next_report_millis;
-int report_num = 0;
-
-class LedHandler {
- public:
- void init() {
- pinMode(LED_BUILTIN, OUTPUT);
- digitalWrite(LED_BUILTIN, false);
-
- // Provide a visible signal that setup has been entered.
- if (Serial) {
- // 2 seconds of fast blinks.
- for (int i = 0; i < 40; ++i) {
- delay(50);
- toggle_led();
- }
- Serial.println("LED blink complete");
- } else {
- // 2 seconds of slow blinks.
- for (int i = 0; i < 10; ++i) {
- delay(200);
- toggle_led();
- }
- }
- }
-
- void update() {
- unsigned long now = millis();
- if (next_change_ms_ <= now) {
- toggle_led();
- next_change_ms_ += (Serial ? 1000 : 100);
- if (next_change_ms_ <= now) {
- next_change_ms_ = now;
- }
- }
- }
-
- private:
- unsigned long next_change_ms_ = 0;
-} led_handler;
-
-void setup(void) {
- Serial.begin(9600);
- Serial.flush();
-
- led_handler.init();
-
- // Setup Camera relays
- pinMode(CAM_0_RELAY, OUTPUT);
- pinMode(CAM_1_RELAY, OUTPUT);
-
- // Turn on Camera relays
- turn_pin_on(CAM_0_RELAY);
- turn_pin_on(CAM_1_RELAY);
-
- acc_handler.init();
- dht_handler.Init();
-
- Serial.println("EXIT setup()");
- next_report_millis = end_setup_millis = millis();
-}
-
-void loop() {
- led_handler.update();
- if (Serial) {
- main_loop();
- }
-}
-
-static int inputs = 0;
-
-void main_loop() {
- unsigned long now = millis();
- if (next_report_millis <= now) {
- // Schedule the next report for `interval' milliseconds from the last report,
- // unless we've fallen behind.
- constexpr int interval = 1000;
- next_report_millis += interval;
- if (next_report_millis <= now) {
- next_report_millis = now + interval;
- }
-
- // Collect the data. Since some of these operations take a while, keep updating the
- // LED as appropriate. Could probably be done with an interrupt handler instead.
- report_num++;
- acc_handler.collect();
- led_handler.update();
- dht_handler.Collect();
- led_handler.update();
- bool cam0 = digitalRead(CAM_0_RELAY);
- bool cam1 = digitalRead(CAM_1_RELAY);
-
- // Format/output the results.
- Serial.print("{\"name\":\"camera_board\", \"count\":");
- led_handler.update();
- // TODO(jamessynge): Deal with wrap around here.
- Serial.print(millis() - end_setup_millis);
- led_handler.update();
- Serial.print(", \"num\":");
- led_handler.update();
- Serial.print(report_num);
- led_handler.update();
- Serial.print(", \"inputs_received\":");
- led_handler.update();
- Serial.print(inputs);
- led_handler.update();
- Serial.print(", \"camera_00\":");
- led_handler.update();
- Serial.print(cam0);
- led_handler.update();
- Serial.print(", \"camera_01\":");
- led_handler.update();
- Serial.print(cam1);
- led_handler.update();
- acc_handler.report();
- led_handler.update();
- dht_handler.Report();
- led_handler.update();
- Serial.println("}");
- led_handler.update();
- Serial.flush();
- led_handler.update();
- }
-
- // Read any serial input
- // - Input will be two integers (with anything in between them), the
- // first specifying the pin and the second the status
- // to change to (1/0). Cameras and debug led are
- // supported.
- // Example serial input:
- // 5,1 # Turn camera 0 on
- // 6,0 # Turn camera 1 off
- // 13,0 # Turn led off
- while (Serial.available() > 0) {
- inputs++;
- int pin_num = Serial.parseInt();
- int pin_status = Serial.parseInt();
-
- switch (pin_num) {
- case CAM_0_RELAY:
- case CAM_1_RELAY:
- if (pin_status == 1) {
- turn_pin_on(pin_num);
- } else if (pin_status == 0) {
- turn_pin_off(pin_num);
- }
- break;
- }
- }
-}
diff --git a/resources/arduino_files/camera_board/dht_handler.cpp b/resources/arduino_files/camera_board/dht_handler.cpp
deleted file mode 120000
index b867ff99a..000000000
--- a/resources/arduino_files/camera_board/dht_handler.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/camera_board/dht_handler.h b/resources/arduino_files/camera_board/dht_handler.h
deleted file mode 120000
index e40a38241..000000000
--- a/resources/arduino_files/camera_board/dht_handler.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.h
\ No newline at end of file
diff --git a/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.cpp b/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.cpp
deleted file mode 100644
index 587b696b1..000000000
--- a/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.cpp
+++ /dev/null
@@ -1,258 +0,0 @@
-/**************************************************************************/
-/*!
- @file Adafruit_MMA8451.h
- @author K. Townsend (Adafruit Industries)
- @license BSD (see license.txt)
-
- This is a library for the Adafruit MMA8451 Accel breakout board
- ----> https://www.adafruit.com/products/2019
-
- Adafruit invests time and resources providing this open source code,
- please support Adafruit and open-source hardware by purchasing
- products from Adafruit!
-
- @section HISTORY
-
- v1.0 - First release
-*/
-/**************************************************************************/
-
-#if ARDUINO >= 100
- #include "Arduino.h"
-#else
- #include "WProgram.h"
-#endif
-
-#include
-#include "Adafruit_MMA8451.h"
-
-/**************************************************************************/
-/*!
- @brief Abstract away platform differences in Arduino wire library
-*/
-/**************************************************************************/
-static inline uint8_t i2cread(void) {
- #if ARDUINO >= 100
- return Wire.read();
- #else
- return Wire.receive();
- #endif
-}
-
-static inline void i2cwrite(uint8_t x) {
- #if ARDUINO >= 100
- Wire.write((uint8_t)x);
- #else
- Wire.send(x);
- #endif
-}
-
-
-/**************************************************************************/
-/*!
- @brief Writes 8-bits to the specified destination register
-*/
-/**************************************************************************/
-void Adafruit_MMA8451::writeRegister8(uint8_t reg, uint8_t value) {
- Wire.beginTransmission(_i2caddr);
- i2cwrite((uint8_t)reg);
- i2cwrite((uint8_t)(value));
- Wire.endTransmission();
-}
-
-/**************************************************************************/
-/*!
- @brief Reads 8-bits from the specified register
-*/
-/**************************************************************************/
-uint8_t Adafruit_MMA8451::readRegister8(uint8_t reg) {
- Wire.beginTransmission(_i2caddr);
- i2cwrite(reg);
- Wire.endTransmission(false); // MMA8451 + friends uses repeated start!!
-
- Wire.requestFrom(_i2caddr, 1);
- if (! Wire.available()) return -1;
- return (i2cread());
-}
-
-/**************************************************************************/
-/*!
- @brief Instantiates a new MMA8451 class in I2C mode
-*/
-/**************************************************************************/
-Adafruit_MMA8451::Adafruit_MMA8451(int32_t sensorID) {
- _sensorID = sensorID;
-}
-
-/**************************************************************************/
-/*!
- @brief Setups the HW (reads coefficients values, etc.)
-*/
-/**************************************************************************/
-bool Adafruit_MMA8451::begin(uint8_t i2caddr) {
- Wire.begin();
- _i2caddr = i2caddr;
-
- /* Check connection */
- uint8_t deviceid = readRegister8(MMA8451_REG_WHOAMI);
- if (deviceid != 0x1A)
- {
- /* No MMA8451 detected ... return false */
- //Serial.println(deviceid, HEX);
- return false;
- }
-
- writeRegister8(MMA8451_REG_CTRL_REG2, 0x40); // reset
-
- while (readRegister8(MMA8451_REG_CTRL_REG2) & 0x40);
-
- // enable 4G range
- writeRegister8(MMA8451_REG_XYZ_DATA_CFG, MMA8451_RANGE_4_G);
- // High res
- writeRegister8(MMA8451_REG_CTRL_REG2, 0x02);
- // Low noise!
- writeRegister8(MMA8451_REG_CTRL_REG4, 0x01);
- // DRDY on INT1
- writeRegister8(MMA8451_REG_CTRL_REG4, 0x01);
- writeRegister8(MMA8451_REG_CTRL_REG5, 0x01);
-
- // Turn on orientation config
- writeRegister8(MMA8451_REG_PL_CFG, 0x40);
-
- // Activate!
- writeRegister8(MMA8451_REG_CTRL_REG1, 0x01); // active, max rate
-
- /*
- for (uint8_t i=0; i<0x30; i++) {
- Serial.print("$");
- Serial.print(i, HEX); Serial.print(" = 0x");
- Serial.println(readRegister8(i), HEX);
- }
- */
-
- return true;
-}
-
-
-void Adafruit_MMA8451::read(void) {
- // read x y z at once
- Wire.beginTransmission(_i2caddr);
- i2cwrite(MMA8451_REG_OUT_X_MSB);
- Wire.endTransmission(false); // MMA8451 + friends uses repeated start!!
-
- Wire.requestFrom(_i2caddr, 6);
- x = Wire.read(); x <<= 8; x |= Wire.read(); x >>= 2;
- y = Wire.read(); y <<= 8; y |= Wire.read(); y >>= 2;
- z = Wire.read(); z <<= 8; z |= Wire.read(); z >>= 2;
-
-
- uint8_t range = getRange();
- uint16_t divider = 1;
- if (range == MMA8451_RANGE_8_G) divider = 1024;
- if (range == MMA8451_RANGE_4_G) divider = 2048;
- if (range == MMA8451_RANGE_2_G) divider = 4096;
-
- x_g = (float)x / divider;
- y_g = (float)y / divider;
- z_g = (float)z / divider;
-
-}
-
-/**************************************************************************/
-/*!
- @brief Read the orientation:
- Portrait/Landscape + Up/Down/Left/Right + Front/Back
-*/
-/**************************************************************************/
-uint8_t Adafruit_MMA8451::getOrientation(void) {
- return readRegister8(MMA8451_REG_PL_STATUS) & 0x07;
-}
-
-/**************************************************************************/
-/*!
- @brief Sets the g range for the accelerometer
-*/
-/**************************************************************************/
-void Adafruit_MMA8451::setRange(mma8451_range_t range)
-{
- // lower bits are range
- writeRegister8(MMA8451_REG_CTRL_REG1, 0x00); // deactivate
- writeRegister8(MMA8451_REG_XYZ_DATA_CFG, range & 0x3);
- writeRegister8(MMA8451_REG_CTRL_REG1, 0x01); // active, max rate
-}
-
-/**************************************************************************/
-/*!
- @brief Sets the g range for the accelerometer
-*/
-/**************************************************************************/
-mma8451_range_t Adafruit_MMA8451::getRange(void)
-{
- /* Read the data format register to preserve bits */
- return (mma8451_range_t)(readRegister8(MMA8451_REG_XYZ_DATA_CFG) & 0x03);
-}
-
-/**************************************************************************/
-/*!
- @brief Sets the data rate for the MMA8451 (controls power consumption)
-*/
-/**************************************************************************/
-void Adafruit_MMA8451::setDataRate(mma8451_dataRate_t dataRate)
-{
- uint8_t ctl1 = readRegister8(MMA8451_REG_CTRL_REG1);
- ctl1 &= ~(0x28); // mask off bits
- ctl1 |= (dataRate << 3);
- writeRegister8(MMA8451_REG_CTRL_REG1, ctl1);
-}
-
-/**************************************************************************/
-/*!
- @brief Sets the data rate for the MMA8451 (controls power consumption)
-*/
-/**************************************************************************/
-mma8451_dataRate_t Adafruit_MMA8451::getDataRate(void)
-{
- return (mma8451_dataRate_t)((readRegister8(MMA8451_REG_CTRL_REG1) >> 3)& 0x07);
-}
-
-/**************************************************************************/
-/*!
- @brief Gets the most recent sensor event
-*/
-/**************************************************************************/
-void Adafruit_MMA8451::getEvent(sensors_event_t *event) {
- /* Clear the event */
- memset(event, 0, sizeof(sensors_event_t));
-
- event->version = sizeof(sensors_event_t);
- event->sensor_id = _sensorID;
- event->type = SENSOR_TYPE_ACCELEROMETER;
- event->timestamp = 0;
-
- read();
-
- event->acceleration.x = x_g;
- event->acceleration.y = y_g;
- event->acceleration.z = z_g;
-}
-
-/**************************************************************************/
-/*!
- @brief Gets the sensor_t data
-*/
-/**************************************************************************/
-void Adafruit_MMA8451::getSensor(sensor_t *sensor) {
- /* Clear the sensor_t object */
- memset(sensor, 0, sizeof(sensor_t));
-
- /* Insert the sensor name in the fixed length char array */
- strncpy (sensor->name, "MMA8451", sizeof(sensor->name) - 1);
- sensor->name[sizeof(sensor->name)- 1] = 0;
- sensor->version = 1;
- sensor->sensor_id = _sensorID;
- sensor->type = SENSOR_TYPE_ACCELEROMETER;
- sensor->min_delay = 0;
- sensor->max_value = 0;
- sensor->min_value = 0;
- sensor->resolution = 0;
-}
diff --git a/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.h b/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.h
deleted file mode 100644
index 7315bcc79..000000000
--- a/resources/arduino_files/libraries/Adafruit_MMA8451/Adafruit_MMA8451.h
+++ /dev/null
@@ -1,106 +0,0 @@
-/**************************************************************************/
-/*!
- @file Adafruit_MMA8451.h
- @author K. Townsend (Adafruit Industries)
- @license BSD (see license.txt)
-
- This is a library for the Adafruit MMA8451 Accel breakout board
- ----> https://www.adafruit.com/products/2019
-
- Adafruit invests time and resources providing this open source code,
- please support Adafruit and open-source hardware by purchasing
- products from Adafruit!
-
- @section HISTORY
-
- v1.0 - First release
-*/
-/**************************************************************************/
-
-#if ARDUINO >= 100
- #include "Arduino.h"
-#else
- #include "WProgram.h"
-#endif
-
-#include
-#include "Adafruit_Sensor.h"
-
-/*=========================================================================
- I2C ADDRESS/BITS
- -----------------------------------------------------------------------*/
- #define MMA8451_DEFAULT_ADDRESS (0x1D) // if A is GND, its 0x1C
-/*=========================================================================*/
-
-#define MMA8451_REG_OUT_X_MSB 0x01
-#define MMA8451_REG_SYSMOD 0x0B
-#define MMA8451_REG_WHOAMI 0x0D
-#define MMA8451_REG_XYZ_DATA_CFG 0x0E
-#define MMA8451_REG_PL_STATUS 0x10
-#define MMA8451_REG_PL_CFG 0x11
-#define MMA8451_REG_CTRL_REG1 0x2A
-#define MMA8451_REG_CTRL_REG2 0x2B
-#define MMA8451_REG_CTRL_REG4 0x2D
-#define MMA8451_REG_CTRL_REG5 0x2E
-
-
-
-#define MMA8451_PL_PUF 0
-#define MMA8451_PL_PUB 1
-#define MMA8451_PL_PDF 2
-#define MMA8451_PL_PDB 3
-#define MMA8451_PL_LRF 4
-#define MMA8451_PL_LRB 5
-#define MMA8451_PL_LLF 6
-#define MMA8451_PL_LLB 7
-
-typedef enum
-{
- MMA8451_RANGE_8_G = 0b10, // +/- 8g
- MMA8451_RANGE_4_G = 0b01, // +/- 4g
- MMA8451_RANGE_2_G = 0b00 // +/- 2g (default value)
-} mma8451_range_t;
-
-
-/* Used with register 0x2A (MMA8451_REG_CTRL_REG1) to set bandwidth */
-typedef enum
-{
- MMA8451_DATARATE_800_HZ = 0b000, // 400Hz
- MMA8451_DATARATE_400_HZ = 0b001, // 200Hz
- MMA8451_DATARATE_200_HZ = 0b010, // 100Hz
- MMA8451_DATARATE_100_HZ = 0b011, // 50Hz
- MMA8451_DATARATE_50_HZ = 0b100, // 25Hz
- MMA8451_DATARATE_12_5_HZ = 0b101, // 6.25Hz
- MMA8451_DATARATE_6_25HZ = 0b110, // 3.13Hz
- MMA8451_DATARATE_1_56_HZ = 0b111, // 1.56Hz
-} mma8451_dataRate_t;
-
-class Adafruit_MMA8451 : public Adafruit_Sensor {
- public:
- Adafruit_MMA8451(int32_t id = -1);
-
-
- bool begin(uint8_t addr = MMA8451_DEFAULT_ADDRESS);
-
- void read();
-
- void setRange(mma8451_range_t range);
- mma8451_range_t getRange(void);
-
- void setDataRate(mma8451_dataRate_t dataRate);
- mma8451_dataRate_t getDataRate(void);
-
- void getEvent(sensors_event_t *event);
- void getSensor(sensor_t *sensor);
-
- uint8_t getOrientation(void);
-
- int16_t x, y, z;
- float x_g, y_g, z_g;
-
- void writeRegister8(uint8_t reg, uint8_t value);
- private:
- uint8_t readRegister8(uint8_t reg);
- int32_t _sensorID;
- int8_t _i2caddr;
-};
diff --git a/resources/arduino_files/libraries/Adafruit_MMA8451/README.md b/resources/arduino_files/libraries/Adafruit_MMA8451/README.md
deleted file mode 100644
index fff9086ec..000000000
--- a/resources/arduino_files/libraries/Adafruit_MMA8451/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-#Adafruit MMA8451 Accelerometer Driver #
-
-This driver is for the Adafruit MMA8451 Accelerometer Breakout (http://www.adafruit.com/products/2019), and is based on Adafruit's Unified Sensor Library (Adafruit_Sensor).
-
-## About the MMA8451 ##
-
-The MMA8451 is a low-cost but high-precision digital accelerometer that uses repeated-start I2C mode, with adjustable data rata and 'range' (+/-2/4/8).
-
-More information on the MMA8451 can be found in the datasheet: http://www.adafruit.com/datasheets/MMA8451Q-1.pdf
-
-## What is the Adafruit Unified Sensor Library? ##
-
-The Adafruit Unified Sensor Library (https://github.com/adafruit/Adafruit_Sensor) provides a common interface and data type for any supported sensor. It defines some basic information about the sensor (sensor limits, etc.), and returns standard SI units of a specific type and scale for each supported sensor type.
-
-It provides a simple abstraction layer between your application and the actual sensor HW, allowing you to drop in any comparable sensor with only one or two lines of code to change in your project (essentially the constructor since the functions to read sensor data and get information about the sensor are defined in the base Adafruit_Sensor class).
-
-This is imporant useful for two reasons:
-
-1.) You can use the data right away because it's already converted to SI units that you understand and can compare, rather than meaningless values like 0..1023.
-
-2.) Because SI units are standardised in the sensor library, you can also do quick sanity checks working with new sensors, or drop in any comparable sensor if you need better sensitivity or if a lower cost unit becomes available, etc.
-
-Light sensors will always report units in lux, gyroscopes will always report units in rad/s, etc. ... freeing you up to focus on the data, rather than digging through the datasheet to understand what the sensor's raw numbers really mean.
-
-## About this Driver ##
-
-Adafruit invests time and resources providing this open source code. Please support Adafruit and open-source hardware by purchasing products from Adafruit!
-
-Written by Kevin (KTOWN) Townsend & Limor (LADYADA) Fried for Adafruit Industries.
diff --git a/resources/arduino_files/libraries/Adafruit_MMA8451/license.txt b/resources/arduino_files/libraries/Adafruit_MMA8451/license.txt
deleted file mode 100644
index f6a0f22b8..000000000
--- a/resources/arduino_files/libraries/Adafruit_MMA8451/license.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-Software License Agreement (BSD License)
-
-Copyright (c) 2012, Adafruit Industries
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-1. Redistributions of source code must retain the above copyright
-notice, this list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright
-notice, this list of conditions and the following disclaimer in the
-documentation and/or other materials provided with the distribution.
-3. Neither the name of the copyright holders nor the
-names of its contributors may be used to endorse or promote products
-derived from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
-EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
-DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.cpp b/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.cpp
deleted file mode 100644
index 2977b275c..000000000
--- a/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.cpp
+++ /dev/null
@@ -1,5 +0,0 @@
-#include "Adafruit_Sensor.h"
-#include
-
-void Adafruit_Sensor::constructor() {
-}
diff --git a/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.h b/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.h
deleted file mode 100644
index 7c0db4fa1..000000000
--- a/resources/arduino_files/libraries/Adafruit_Sensor/Adafruit_Sensor.h
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
-* Copyright (C) 2008 The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software< /span>
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-/* Update by K. Townsend (Adafruit Industries) for lighter typedefs, and
- * extended sensor support to include color, voltage and current */
-
-#ifndef _ADAFRUIT_SENSOR_H
-#define _ADAFRUIT_SENSOR_H
-
-#if ARDUINO >= 100
- #include "Arduino.h"
- #include "Print.h"
-#else
- #include "WProgram.h"
-#endif
-
-/* Intentionally modeled after sensors.h in the Android API:
- * https://github.com/android/platform_hardware_libhardware/blob/master/include/hardware/sensors.h */
-
-/* Constants */
-#define SENSORS_GRAVITY_EARTH (9.80665F) /**< Earth's gravity in m/s^2 */
-#define SENSORS_GRAVITY_MOON (1.6F) /**< The moon's gravity in m/s^2 */
-#define SENSORS_GRAVITY_SUN (275.0F) /**< The sun's gravity in m/s^2 */
-#define SENSORS_GRAVITY_STANDARD (SENSORS_GRAVITY_EARTH)
-#define SENSORS_MAGFIELD_EARTH_MAX (60.0F) /**< Maximum magnetic field on Earth's surface */
-#define SENSORS_MAGFIELD_EARTH_MIN (30.0F) /**< Minimum magnetic field on Earth's surface */
-#define SENSORS_PRESSURE_SEALEVELHPA (1013.25F) /**< Average sea level pressure is 1013.25 hPa */
-#define SENSORS_DPS_TO_RADS (0.017453293F) /**< Degrees/s to rad/s multiplier */
-#define SENSORS_GAUSS_TO_MICROTESLA (100) /**< Gauss to micro-Tesla multiplier */
-
-/** Sensor types */
-typedef enum
-{
- SENSOR_TYPE_ACCELEROMETER = (1), /**< Gravity + linear acceleration */
- SENSOR_TYPE_MAGNETIC_FIELD = (2),
- SENSOR_TYPE_ORIENTATION = (3),
- SENSOR_TYPE_GYROSCOPE = (4),
- SENSOR_TYPE_LIGHT = (5),
- SENSOR_TYPE_PRESSURE = (6),
- SENSOR_TYPE_PROXIMITY = (8),
- SENSOR_TYPE_GRAVITY = (9),
- SENSOR_TYPE_LINEAR_ACCELERATION = (10), /**< Acceleration not including gravity */
- SENSOR_TYPE_ROTATION_VECTOR = (11),
- SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
- SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
- SENSOR_TYPE_VOLTAGE = (15),
- SENSOR_TYPE_CURRENT = (16),
- SENSOR_TYPE_COLOR = (17)
-} sensors_type_t;
-
-/** struct sensors_vec_s is used to return a vector in a common format. */
-typedef struct {
- union {
- float v[3];
- struct {
- float x;
- float y;
- float z;
- };
- /* Orientation sensors */
- struct {
- float roll; /**< Rotation around the longitudinal axis (the plane body, 'X axis'). Roll is positive and increasing when moving downward. -90�<=roll<=90� */
- float pitch; /**< Rotation around the lateral axis (the wing span, 'Y axis'). Pitch is positive and increasing when moving upwards. -180�<=pitch<=180�) */
- float heading; /**< Angle between the longitudinal axis (the plane body) and magnetic north, measured clockwise when viewing from the top of the device. 0-359� */
- };
- };
- int8_t status;
- uint8_t reserved[3];
-} sensors_vec_t;
-
-/** struct sensors_color_s is used to return color data in a common format. */
-typedef struct {
- union {
- float c[3];
- /* RGB color space */
- struct {
- float r; /**< Red component */
- float g; /**< Green component */
- float b; /**< Blue component */
- };
- };
- uint32_t rgba; /**< 24-bit RGBA value */
-} sensors_color_t;
-
-/* Sensor event (36 bytes) */
-/** struct sensor_event_s is used to provide a single sensor event in a common format. */
-typedef struct
-{
- int32_t version; /**< must be sizeof(struct sensors_event_t) */
- int32_t sensor_id; /**< unique sensor identifier */
- int32_t type; /**< sensor type */
- int32_t reserved0; /**< reserved */
- int32_t timestamp; /**< time is in milliseconds */
- union
- {
- float data[4];
- sensors_vec_t acceleration; /**< acceleration values are in meter per second per second (m/s^2) */
- sensors_vec_t magnetic; /**< magnetic vector values are in micro-Tesla (uT) */
- sensors_vec_t orientation; /**< orientation values are in degrees */
- sensors_vec_t gyro; /**< gyroscope values are in rad/s */
- float temperature; /**< temperature is in degrees centigrade (Celsius) */
- float distance; /**< distance in centimeters */
- float light; /**< light in SI lux units */
- float pressure; /**< pressure in hectopascal (hPa) */
- float relative_humidity; /**< relative humidity in percent */
- float current; /**< current in milliamps (mA) */
- float voltage; /**< voltage in volts (V) */
- sensors_color_t color; /**< color in RGB component values */
- };
-} sensors_event_t;
-
-/* Sensor details (40 bytes) */
-/** struct sensor_s is used to describe basic information about a specific sensor. */
-typedef struct
-{
- char name[12]; /**< sensor name */
- int32_t version; /**< version of the hardware + driver */
- int32_t sensor_id; /**< unique sensor identifier */
- int32_t type; /**< this sensor's type (ex. SENSOR_TYPE_LIGHT) */
- float max_value; /**< maximum value of this sensor's value in SI units */
- float min_value; /**< minimum value of this sensor's value in SI units */
- float resolution; /**< smallest difference between two values reported by this sensor */
- int32_t min_delay; /**< min delay in microseconds between events. zero = not a constant rate */
-} sensor_t;
-
-class Adafruit_Sensor {
- public:
- // Constructor(s)
- void constructor();
-
- // These must be defined by the subclass
- virtual void enableAutoRange(bool enabled) {};
- virtual void getEvent(sensors_event_t*);
- virtual void getSensor(sensor_t*);
-
- private:
- bool _autoRange;
-};
-
-#endif
diff --git a/resources/arduino_files/libraries/Adafruit_Sensor/README.md b/resources/arduino_files/libraries/Adafruit_Sensor/README.md
deleted file mode 100644
index 068028607..000000000
--- a/resources/arduino_files/libraries/Adafruit_Sensor/README.md
+++ /dev/null
@@ -1,214 +0,0 @@
-# Adafruit Unified Sensor Driver #
-
-Many small embedded systems exist to collect data from sensors, analyse the data, and either take an appropriate action or send that sensor data to another system for processing.
-
-One of the many challenges of embedded systems design is the fact that parts you used today may be out of production tomorrow, or system requirements may change and you may need to choose a different sensor down the road.
-
-Creating new drivers is a relatively easy task, but integrating them into existing systems is both error prone and time consuming since sensors rarely use the exact same units of measurement.
-
-By reducing all data to a single **sensors\_event\_t** 'type' and settling on specific, **standardised SI units** for each sensor family the same sensor types return values that are comparable with any other similar sensor. This enables you to switch sensor models with very little impact on the rest of the system, which can help mitigate some of the risks and problems of sensor availability and code reuse.
-
-The unified sensor abstraction layer is also useful for data-logging and data-transmission since you only have one well-known type to log or transmit over the air or wire.
-
-## Unified Sensor Drivers ##
-
-The following drivers are based on the Adafruit Unified Sensor Driver:
-
-**Accelerometers**
- - [Adafruit\_ADXL345](https://github.com/adafruit/Adafruit_ADXL345)
- - [Adafruit\_LSM303DLHC](https://github.com/adafruit/Adafruit_LSM303DLHC)
-
-**Gyroscope**
- - [Adafruit\_L3GD20\_U](https://github.com/adafruit/Adafruit_L3GD20_U)
-
-**Light**
- - [Adafruit\_TSL2561](https://github.com/adafruit/Adafruit_TSL2561)
-
-**Magnetometers**
- - [Adafruit\_LSM303DLHC](https://github.com/adafruit/Adafruit_LSM303DLHC)
-
-**Barometric Pressure**
- - [Adafruit\_BMP085\_Unified](https://github.com/adafruit/Adafruit_BMP085_Unified)
-
-**Humidity & Temperature**
- - [Adafruit\_DHT\_Unified](https://github.com/adafruit/Adafruit_DHT_Unified)
-
-## How Does it Work? ##
-
-Any driver that supports the Adafruit unified sensor abstraction layer will implement the Adafruit\_Sensor base class. There are two main typedefs and one enum defined in Adafruit_Sensor.h that are used to 'abstract' away the sensor details and values:
-
-**Sensor Types (sensors\_type\_t)**
-
-These pre-defined sensor types are used to properly handle the two related typedefs below, and allows us determine what types of units the sensor uses, etc.
-
-```
-/** Sensor types */
-typedef enum
-{
- SENSOR_TYPE_ACCELEROMETER = (1),
- SENSOR_TYPE_MAGNETIC_FIELD = (2),
- SENSOR_TYPE_ORIENTATION = (3),
- SENSOR_TYPE_GYROSCOPE = (4),
- SENSOR_TYPE_LIGHT = (5),
- SENSOR_TYPE_PRESSURE = (6),
- SENSOR_TYPE_PROXIMITY = (8),
- SENSOR_TYPE_GRAVITY = (9),
- SENSOR_TYPE_LINEAR_ACCELERATION = (10),
- SENSOR_TYPE_ROTATION_VECTOR = (11),
- SENSOR_TYPE_RELATIVE_HUMIDITY = (12),
- SENSOR_TYPE_AMBIENT_TEMPERATURE = (13),
- SENSOR_TYPE_VOLTAGE = (15),
- SENSOR_TYPE_CURRENT = (16),
- SENSOR_TYPE_COLOR = (17)
-} sensors_type_t;
-```
-
-**Sensor Details (sensor\_t)**
-
-This typedef describes the specific capabilities of this sensor, and allows us to know what sensor we are using beneath the abstraction layer.
-
-```
-/* Sensor details (40 bytes) */
-/** struct sensor_s is used to describe basic information about a specific sensor. */
-typedef struct
-{
- char name[12];
- int32_t version;
- int32_t sensor_id;
- int32_t type;
- float max_value;
- float min_value;
- float resolution;
- int32_t min_delay;
-} sensor_t;
-```
-
-The individual fields are intended to be used as follows:
-
-- **name**: The sensor name or ID, up to a maximum of twelve characters (ex. "MPL115A2")
-- **version**: The version of the sensor HW and the driver to allow us to differentiate versions of the board or driver
-- **sensor\_id**: A unique sensor identifier that is used to differentiate this specific sensor instance from any others that are present on the system or in the sensor network
-- **type**: The sensor type, based on **sensors\_type\_t** in sensors.h
-- **max\_value**: The maximum value that this sensor can return (in the appropriate SI unit)
-- **min\_value**: The minimum value that this sensor can return (in the appropriate SI unit)
-- **resolution**: The smallest difference between two values that this sensor can report (in the appropriate SI unit)
-- **min\_delay**: The minimum delay in microseconds between two sensor events, or '0' if there is no constant sensor rate
-
-**Sensor Data/Events (sensors\_event\_t)**
-
-This typedef is used to return sensor data from any sensor supported by the abstraction layer, using standard SI units and scales.
-
-```
-/* Sensor event (36 bytes) */
-/** struct sensor_event_s is used to provide a single sensor event in a common format. */
-typedef struct
-{
- int32_t version;
- int32_t sensor_id;
- int32_t type;
- int32_t reserved0;
- int32_t timestamp;
- union
- {
- float data[4];
- sensors_vec_t acceleration;
- sensors_vec_t magnetic;
- sensors_vec_t orientation;
- sensors_vec_t gyro;
- float temperature;
- float distance;
- float light;
- float pressure;
- float relative_humidity;
- float current;
- float voltage;
- sensors_color_t color;
- };
-} sensors_event_t;
-```
-It includes the following fields:
-
-- **version**: Contain 'sizeof(sensors\_event\_t)' to identify which version of the API we're using in case this changes in the future
-- **sensor\_id**: A unique sensor identifier that is used to differentiate this specific sensor instance from any others that are present on the system or in the sensor network (must match the sensor\_id value in the corresponding sensor\_t enum above!)
-- **type**: the sensor type, based on **sensors\_type\_t** in sensors.h
-- **timestamp**: time in milliseconds when the sensor value was read
-- **data[4]**: An array of four 32-bit values that allows us to encapsulate any type of sensor data via a simple union (further described below)
-
-**Required Functions**
-
-In addition to the two standard types and the sensor type enum, all drivers based on Adafruit_Sensor must also implement the following two functions:
-
-```
-void getEvent(sensors_event_t*);
-```
-Calling this function will populate the supplied sensors\_event\_t reference with the latest available sensor data. You should call this function as often as you want to update your data.
-
-```
-void getSensor(sensor_t*);
-```
-Calling this function will provide some basic information about the sensor (the sensor name, driver version, min and max values, etc.
-
-**Standardised SI values for sensors\_event\_t**
-
-A key part of the abstraction layer is the standardisation of values on SI units of a particular scale, which is accomplished via the data[4] union in sensors\_event\_t above. This 16 byte union includes fields for each main sensor type, and uses the following SI units and scales:
-
-- **acceleration**: values are in **meter per second per second** (m/s^2)
-- **magnetic**: values are in **micro-Tesla** (uT)
-- **orientation**: values are in **degrees**
-- **gyro**: values are in **rad/s**
-- **temperature**: values in **degrees centigrade** (Celsius)
-- **distance**: values are in **centimeters**
-- **light**: values are in **SI lux** units
-- **pressure**: values are in **hectopascal** (hPa)
-- **relative\_humidity**: values are in **percent**
-- **current**: values are in **milliamps** (mA)
-- **voltage**: values are in **volts** (V)
-- **color**: values are in 0..1.0 RGB channel luminosity and 32-bit RGBA format
-
-## The Unified Driver Abstraction Layer in Practice ##
-
-Using the unified sensor abstraction layer is relatively easy once a compliant driver has been created.
-
-Every compliant sensor can now be read using a single, well-known 'type' (sensors\_event\_t), and there is a standardised way of interrogating a sensor about its specific capabilities (via sensor\_t).
-
-An example of reading the [TSL2561](https://github.com/adafruit/Adafruit_TSL2561) light sensor can be seen below:
-
-```
- Adafruit_TSL2561 tsl = Adafruit_TSL2561(TSL2561_ADDR_FLOAT, 12345);
- ...
- /* Get a new sensor event */
- sensors_event_t event;
- tsl.getEvent(&event);
-
- /* Display the results (light is measured in lux) */
- if (event.light)
- {
- Serial.print(event.light); Serial.println(" lux");
- }
- else
- {
- /* If event.light = 0 lux the sensor is probably saturated
- and no reliable data could be generated! */
- Serial.println("Sensor overload");
- }
-```
-
-Similarly, we can get the basic technical capabilities of this sensor with the following code:
-
-```
- sensor_t sensor;
-
- sensor_t sensor;
- tsl.getSensor(&sensor);
-
- /* Display the sensor details */
- Serial.println("------------------------------------");
- Serial.print ("Sensor: "); Serial.println(sensor.name);
- Serial.print ("Driver Ver: "); Serial.println(sensor.version);
- Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id);
- Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" lux");
- Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" lux");
- Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" lux");
- Serial.println("------------------------------------");
- Serial.println("");
-```
diff --git a/resources/arduino_files/libraries/DHT/DHT.cpp b/resources/arduino_files/libraries/DHT/DHT.cpp
deleted file mode 100644
index 86ad91c43..000000000
--- a/resources/arduino_files/libraries/DHT/DHT.cpp
+++ /dev/null
@@ -1,259 +0,0 @@
-/* DHT library
-
-MIT license
-written by Adafruit Industries
-*/
-
-#include "DHT.h"
-
-#define MIN_INTERVAL 2000
-
-DHT::DHT(uint8_t pin, uint8_t type, uint8_t count) {
- _pin = pin;
- _type = type;
- #ifdef __AVR
- _bit = digitalPinToBitMask(pin);
- _port = digitalPinToPort(pin);
- #endif
- _maxcycles = microsecondsToClockCycles(1000); // 1 millisecond timeout for
- // reading pulses from DHT sensor.
- // Note that count is now ignored as the DHT reading algorithm adjusts itself
- // basd on the speed of the processor.
-}
-
-void DHT::begin(void) {
- // set up the pins!
- pinMode(_pin, INPUT_PULLUP);
- // Using this value makes sure that millis() - lastreadtime will be
- // >= MIN_INTERVAL right away. Note that this assignment wraps around,
- // but so will the subtraction.
- _lastreadtime = -MIN_INTERVAL;
- DEBUG_PRINT("Max clock cycles: "); DEBUG_PRINTLN(_maxcycles, DEC);
-}
-
-//boolean S == Scale. True == Fahrenheit; False == Celcius
-float DHT::readTemperature(bool S, bool force) {
- float f = NAN;
-
- if (read(force)) {
- switch (_type) {
- case DHT11:
- f = data[2];
- if(S) {
- f = convertCtoF(f);
- }
- break;
- case DHT22:
- case DHT21:
- f = data[2] & 0x7F;
- f *= 256;
- f += data[3];
- f *= 0.1;
- if (data[2] & 0x80) {
- f *= -1;
- }
- if(S) {
- f = convertCtoF(f);
- }
- break;
- }
- }
- return f;
-}
-
-float DHT::convertCtoF(float c) {
- return c * 1.8 + 32;
-}
-
-float DHT::convertFtoC(float f) {
- return (f - 32) * 0.55555;
-}
-
-float DHT::readHumidity(bool force) {
- float f = NAN;
- if (read()) {
- switch (_type) {
- case DHT11:
- f = data[0];
- break;
- case DHT22:
- case DHT21:
- f = data[0];
- f *= 256;
- f += data[1];
- f *= 0.1;
- break;
- }
- }
- return f;
-}
-
-//boolean isFahrenheit: True == Fahrenheit; False == Celcius
-float DHT::computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit) {
- // Using both Rothfusz and Steadman's equations
- // http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
- float hi;
-
- if (!isFahrenheit)
- temperature = convertCtoF(temperature);
-
- hi = 0.5 * (temperature + 61.0 + ((temperature - 68.0) * 1.2) + (percentHumidity * 0.094));
-
- if (hi > 79) {
- hi = -42.379 +
- 2.04901523 * temperature +
- 10.14333127 * percentHumidity +
- -0.22475541 * temperature*percentHumidity +
- -0.00683783 * pow(temperature, 2) +
- -0.05481717 * pow(percentHumidity, 2) +
- 0.00122874 * pow(temperature, 2) * percentHumidity +
- 0.00085282 * temperature*pow(percentHumidity, 2) +
- -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2);
-
- if((percentHumidity < 13) && (temperature >= 80.0) && (temperature <= 112.0))
- hi -= ((13.0 - percentHumidity) * 0.25) * sqrt((17.0 - abs(temperature - 95.0)) * 0.05882);
-
- else if((percentHumidity > 85.0) && (temperature >= 80.0) && (temperature <= 87.0))
- hi += ((percentHumidity - 85.0) * 0.1) * ((87.0 - temperature) * 0.2);
- }
-
- return isFahrenheit ? hi : convertFtoC(hi);
-}
-
-boolean DHT::read(bool force) {
- // Check if sensor was read less than two seconds ago and return early
- // to use last reading.
- uint32_t currenttime = millis();
- if (!force && ((currenttime - _lastreadtime) < 2000)) {
- return _lastresult; // return last correct measurement
- }
- _lastreadtime = currenttime;
-
- // Reset 40 bits of received data to zero.
- data[0] = data[1] = data[2] = data[3] = data[4] = 0;
-
- // Send start signal. See DHT datasheet for full signal diagram:
- // http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf
-
- // Go into high impedence state to let pull-up raise data line level and
- // start the reading process.
- digitalWrite(_pin, HIGH);
- delay(250);
-
- // First set data line low for 20 milliseconds.
- pinMode(_pin, OUTPUT);
- digitalWrite(_pin, LOW);
- delay(20);
-
- uint32_t cycles[80];
- {
- // Turn off interrupts temporarily because the next sections are timing critical
- // and we don't want any interruptions.
- InterruptLock lock;
-
- // End the start signal by setting data line high for 40 microseconds.
- digitalWrite(_pin, HIGH);
- delayMicroseconds(40);
-
- // Now start reading the data line to get the value from the DHT sensor.
- pinMode(_pin, INPUT_PULLUP);
- delayMicroseconds(10); // Delay a bit to let sensor pull data line low.
-
- // First expect a low signal for ~80 microseconds followed by a high signal
- // for ~80 microseconds again.
- if (expectPulse(LOW) == 0) {
- DEBUG_PRINTLN(F("Timeout waiting for start signal low pulse."));
- _lastresult = false;
- return _lastresult;
- }
- if (expectPulse(HIGH) == 0) {
- DEBUG_PRINTLN(F("Timeout waiting for start signal high pulse."));
- _lastresult = false;
- return _lastresult;
- }
-
- // Now read the 40 bits sent by the sensor. Each bit is sent as a 50
- // microsecond low pulse followed by a variable length high pulse. If the
- // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
- // then it's a 1. We measure the cycle count of the initial 50us low pulse
- // and use that to compare to the cycle count of the high pulse to determine
- // if the bit is a 0 (high state cycle count < low state cycle count), or a
- // 1 (high state cycle count > low state cycle count). Note that for speed all
- // the pulses are read into a array and then examined in a later step.
- for (int i=0; i<80; i+=2) {
- cycles[i] = expectPulse(LOW);
- cycles[i+1] = expectPulse(HIGH);
- }
- } // Timing critical code is now complete.
-
- // Inspect pulses and determine which ones are 0 (high state cycle count < low
- // state cycle count), or 1 (high state cycle count > low state cycle count).
- for (int i=0; i<40; ++i) {
- uint32_t lowCycles = cycles[2*i];
- uint32_t highCycles = cycles[2*i+1];
- if ((lowCycles == 0) || (highCycles == 0)) {
- DEBUG_PRINTLN(F("Timeout waiting for pulse."));
- _lastresult = false;
- return _lastresult;
- }
- data[i/8] <<= 1;
- // Now compare the low and high cycle times to see if the bit is a 0 or 1.
- if (highCycles > lowCycles) {
- // High cycles are greater than 50us low cycle count, must be a 1.
- data[i/8] |= 1;
- }
- // Else high cycles are less than (or equal to, a weird case) the 50us low
- // cycle count so this must be a zero. Nothing needs to be changed in the
- // stored data.
- }
-
- DEBUG_PRINTLN(F("Received:"));
- DEBUG_PRINT(data[0], HEX); DEBUG_PRINT(F(", "));
- DEBUG_PRINT(data[1], HEX); DEBUG_PRINT(F(", "));
- DEBUG_PRINT(data[2], HEX); DEBUG_PRINT(F(", "));
- DEBUG_PRINT(data[3], HEX); DEBUG_PRINT(F(", "));
- DEBUG_PRINT(data[4], HEX); DEBUG_PRINT(F(" =? "));
- DEBUG_PRINTLN((data[0] + data[1] + data[2] + data[3]) & 0xFF, HEX);
-
- // Check we read 40 bits and that the checksum matches.
- if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
- _lastresult = true;
- return _lastresult;
- }
- else {
- DEBUG_PRINTLN(F("Checksum failure!"));
- _lastresult = false;
- return _lastresult;
- }
-}
-
-// Expect the signal line to be at the specified level for a period of time and
-// return a count of loop cycles spent at that level (this cycle count can be
-// used to compare the relative time of two pulses). If more than a millisecond
-// ellapses without the level changing then the call fails with a 0 response.
-// This is adapted from Arduino's pulseInLong function (which is only available
-// in the very latest IDE versions):
-// https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_pulse.c
-uint32_t DHT::expectPulse(bool level) {
- uint32_t count = 0;
- // On AVR platforms use direct GPIO port access as it's much faster and better
- // for catching pulses that are 10's of microseconds in length:
- #ifdef __AVR
- uint8_t portState = level ? _bit : 0;
- while ((*portInputRegister(_port) & _bit) == portState) {
- if (count++ >= _maxcycles) {
- return 0; // Exceeded timeout, fail.
- }
- }
- // Otherwise fall back to using digitalRead (this seems to be necessary on ESP8266
- // right now, perhaps bugs in direct port access functions?).
- #else
- while (digitalRead(_pin) == level) {
- if (count++ >= _maxcycles) {
- return 0; // Exceeded timeout, fail.
- }
- }
- #endif
-
- return count;
-}
diff --git a/resources/arduino_files/libraries/DHT/DHT.h b/resources/arduino_files/libraries/DHT/DHT.h
deleted file mode 100644
index d81f6dbc9..000000000
--- a/resources/arduino_files/libraries/DHT/DHT.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/* DHT library
-
-MIT license
-written by Adafruit Industries
-*/
-#ifndef DHT_H
-#define DHT_H
-
-#if ARDUINO >= 100
- #include "Arduino.h"
-#else
- #include "WProgram.h"
-#endif
-
-
-// Uncomment to enable printing out nice debug messages.
-//#define DHT_DEBUG
-
-// Define where debug output will be printed.
-#define DEBUG_PRINTER Serial
-
-// Setup debug printing macros.
-#ifdef DHT_DEBUG
- #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); }
- #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); }
-#else
- #define DEBUG_PRINT(...) {}
- #define DEBUG_PRINTLN(...) {}
-#endif
-
-// Define types of sensors.
-#define DHT11 11
-#define DHT22 22
-#define DHT21 21
-#define AM2301 21
-
-
-class DHT {
- public:
- DHT(uint8_t pin, uint8_t type, uint8_t count=6);
- void begin(void);
- float readTemperature(bool S=false, bool force=false);
- float convertCtoF(float);
- float convertFtoC(float);
- float computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit=true);
- float readHumidity(bool force=false);
- boolean read(bool force=false);
-
- private:
- uint8_t data[5];
- uint8_t _pin, _type;
- #ifdef __AVR
- // Use direct GPIO access on an 8-bit AVR so keep track of the port and bitmask
- // for the digital pin connected to the DHT. Other platforms will use digitalRead.
- uint8_t _bit, _port;
- #endif
- uint32_t _lastreadtime, _maxcycles;
- bool _lastresult;
-
- uint32_t expectPulse(bool level);
-
-};
-
-class InterruptLock {
- public:
- InterruptLock() {
- noInterrupts();
- }
- ~InterruptLock() {
- interrupts();
- }
-
-};
-
-#endif
diff --git a/resources/arduino_files/libraries/DHT/README.md b/resources/arduino_files/libraries/DHT/README.md
deleted file mode 100644
index d32afdc69..000000000
--- a/resources/arduino_files/libraries/DHT/README.md
+++ /dev/null
@@ -1,15 +0,0 @@
-This is an Arduino library for the DHT series of low cost temperature/humidity sensors.
-
-Tutorial: https://learn.adafruit.com/dht
-
-To download. click the DOWNLOADS button in the top right corner, rename the uncompressed folder DHT. Check that the DHT folder contains DHT.cpp and DHT.h. Place the DHT library folder your /libraries/ folder. You may need to create the libraries subfolder if its your first library. Restart the IDE.
-
-# Adafruit DHT Humidity & Temperature Unified Sensor Library
-
-This library also includes an optional class for the
-[DHT humidity and temperature sensor](https://learn.adafruit.com/dht/overview)
-which is designed to work with the [Adafruit unified sensor library](https://learn.adafruit.com/using-the-adafruit-unified-sensor-driver/introduction).
-
-You must have the following Arduino libraries installed to use this class:
-
-- [Adafruit Unified Sensor Library](https://github.com/adafruit/Adafruit_Sensor)
diff --git a/resources/arduino_files/libraries/DHT/keywords.txt b/resources/arduino_files/libraries/DHT/keywords.txt
deleted file mode 100644
index 146d4fa6c..000000000
--- a/resources/arduino_files/libraries/DHT/keywords.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-###########################################
-# Syntax Coloring Map For DHT-sensor-library
-###########################################
-
-###########################################
-# Datatypes (KEYWORD1)
-###########################################
-
-DHT KEYWORD1
-
-###########################################
-# Methods and Functions (KEYWORD2)
-###########################################
-
-begin KEYWORD2
-readTemperature KEYWORD2
-convertCtoF KEYWORD2
-convertFtoC KEYWORD2
-computeHeatIndex KEYWORD2
-readHumidity KEYWORD2
-read KEYWORD2
-
diff --git a/resources/arduino_files/libraries/DHT/library.properties b/resources/arduino_files/libraries/DHT/library.properties
deleted file mode 100644
index a54c1dc20..000000000
--- a/resources/arduino_files/libraries/DHT/library.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-name=DHT sensor library
-version=1.3.0
-author=Adafruit
-maintainer=Adafruit
-sentence=Arduino library for DHT11, DHT22, etc Temp & Humidity Sensors
-paragraph=Arduino library for DHT11, DHT22, etc Temp & Humidity Sensors
-category=Sensors
-url=https://github.com/adafruit/DHT-sensor-library
-architectures=*
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.cpp b/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.cpp
deleted file mode 100644
index 829c99d83..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.cpp
+++ /dev/null
@@ -1,885 +0,0 @@
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-
-#include "DallasTemperature.h"
-
-#if ARDUINO >= 100
-#include "Arduino.h"
-#else
-extern "C" {
-#include "WConstants.h"
-}
-#endif
-
-// OneWire commands
-#define STARTCONVO 0x44 // Tells device to take a temperature reading and put it on the scratchpad
-#define COPYSCRATCH 0x48 // Copy EEPROM
-#define READSCRATCH 0xBE // Read EEPROM
-#define WRITESCRATCH 0x4E // Write to EEPROM
-#define RECALLSCRATCH 0xB8 // Reload from last known
-#define READPOWERSUPPLY 0xB4 // Determine if device needs parasite power
-#define ALARMSEARCH 0xEC // Query bus for devices with an alarm condition
-
-// Scratchpad locations
-#define TEMP_LSB 0
-#define TEMP_MSB 1
-#define HIGH_ALARM_TEMP 2
-#define LOW_ALARM_TEMP 3
-#define CONFIGURATION 4
-#define INTERNAL_BYTE 5
-#define COUNT_REMAIN 6
-#define COUNT_PER_C 7
-#define SCRATCHPAD_CRC 8
-
-// Device resolution
-#define TEMP_9_BIT 0x1F // 9 bit
-#define TEMP_10_BIT 0x3F // 10 bit
-#define TEMP_11_BIT 0x5F // 11 bit
-#define TEMP_12_BIT 0x7F // 12 bit
-
-#define NO_ALARM_HANDLER ((AlarmHandler *)0)
-
-DallasTemperature::DallasTemperature()
-{
-#if REQUIRESALARMS
- setAlarmHandler(NO_ALARM_HANDLER);
-#endif
-}
-DallasTemperature::DallasTemperature(OneWire* _oneWire)
-{
- setOneWire(_oneWire);
-#if REQUIRESALARMS
- setAlarmHandler(NO_ALARM_HANDLER);
-#endif
-}
-
-bool DallasTemperature::validFamily(const uint8_t* deviceAddress) {
- switch (deviceAddress[0]) {
- case DS18S20MODEL:
- case DS18B20MODEL:
- case DS1822MODEL:
- case DS1825MODEL:
- case DS28EA00MODEL:
- return true;
- default:
- return false;
- }
-}
-
-void DallasTemperature::setOneWire(OneWire* _oneWire) {
-
- _wire = _oneWire;
- devices = 0;
- ds18Count = 0;
- parasite = false;
- bitResolution = 9;
- waitForConversion = true;
- checkForConversion = true;
-
-}
-
-// initialise the bus
-void DallasTemperature::begin(void) {
-
- DeviceAddress deviceAddress;
-
- _wire->reset_search();
- devices = 0; // Reset the number of devices when we enumerate wire devices
- ds18Count = 0; // Reset number of DS18xxx Family devices
-
- while (_wire->search(deviceAddress)) {
-
- if (validAddress(deviceAddress)) {
-
- if (!parasite && readPowerSupply(deviceAddress))
- parasite = true;
-
- bitResolution = max(bitResolution, getResolution(deviceAddress));
-
- devices++;
- if (validFamily(deviceAddress)) {
- ds18Count++;
- }
- }
- }
-
-}
-
-// returns the number of devices found on the bus
-uint8_t DallasTemperature::getDeviceCount(void) {
- return devices;
-}
-
-uint8_t DallasTemperature::getDS18Count(void) {
- return ds18Count;
-}
-
-// returns true if address is valid
-bool DallasTemperature::validAddress(const uint8_t* deviceAddress) {
- return (_wire->crc8(deviceAddress, 7) == deviceAddress[7]);
-}
-
-// finds an address at a given index on the bus
-// returns true if the device was found
-bool DallasTemperature::getAddress(uint8_t* deviceAddress, uint8_t index) {
-
- uint8_t depth = 0;
-
- _wire->reset_search();
-
- while (depth <= index && _wire->search(deviceAddress)) {
- if (depth == index && validAddress(deviceAddress))
- return true;
- depth++;
- }
-
- return false;
-
-}
-
-// attempt to determine if the device at the given address is connected to the bus
-bool DallasTemperature::isConnected(const uint8_t* deviceAddress) {
-
- ScratchPad scratchPad;
- return isConnected(deviceAddress, scratchPad);
-
-}
-
-// attempt to determine if the device at the given address is connected to the bus
-// also allows for updating the read scratchpad
-bool DallasTemperature::isConnected(const uint8_t* deviceAddress,
- uint8_t* scratchPad) {
- bool b = readScratchPad(deviceAddress, scratchPad);
- return b && (_wire->crc8(scratchPad, 8) == scratchPad[SCRATCHPAD_CRC]);
-}
-
-bool DallasTemperature::readScratchPad(const uint8_t* deviceAddress,
- uint8_t* scratchPad) {
-
- // send the reset command and fail fast
- int b = _wire->reset();
- if (b == 0)
- return false;
-
- _wire->select(deviceAddress);
- _wire->write(READSCRATCH);
-
- // Read all registers in a simple loop
- // byte 0: temperature LSB
- // byte 1: temperature MSB
- // byte 2: high alarm temp
- // byte 3: low alarm temp
- // byte 4: DS18S20: store for crc
- // DS18B20 & DS1822: configuration register
- // byte 5: internal use & crc
- // byte 6: DS18S20: COUNT_REMAIN
- // DS18B20 & DS1822: store for crc
- // byte 7: DS18S20: COUNT_PER_C
- // DS18B20 & DS1822: store for crc
- // byte 8: SCRATCHPAD_CRC
- for (uint8_t i = 0; i < 9; i++) {
- scratchPad[i] = _wire->read();
- }
-
- b = _wire->reset();
- return (b == 1);
-}
-
-void DallasTemperature::writeScratchPad(const uint8_t* deviceAddress,
- const uint8_t* scratchPad) {
-
- _wire->reset();
- _wire->select(deviceAddress);
- _wire->write(WRITESCRATCH);
- _wire->write(scratchPad[HIGH_ALARM_TEMP]); // high alarm temp
- _wire->write(scratchPad[LOW_ALARM_TEMP]); // low alarm temp
-
- // DS1820 and DS18S20 have no configuration register
- if (deviceAddress[0] != DS18S20MODEL)
- _wire->write(scratchPad[CONFIGURATION]);
-
- _wire->reset();
-
- // save the newly written values to eeprom
- _wire->select(deviceAddress);
- _wire->write(COPYSCRATCH, parasite);
- delay(20); // <--- added 20ms delay to allow 10ms long EEPROM write operation (as specified by datasheet)
-
- if (parasite)
- delay(10); // 10ms delay
- _wire->reset();
-
-}
-
-bool DallasTemperature::readPowerSupply(const uint8_t* deviceAddress) {
-
- bool ret = false;
- _wire->reset();
- _wire->select(deviceAddress);
- _wire->write(READPOWERSUPPLY);
- if (_wire->read_bit() == 0)
- ret = true;
- _wire->reset();
- return ret;
-
-}
-
-// set resolution of all devices to 9, 10, 11, or 12 bits
-// if new resolution is out of range, it is constrained.
-void DallasTemperature::setResolution(uint8_t newResolution) {
-
- bitResolution = constrain(newResolution, 9, 12);
- DeviceAddress deviceAddress;
- for (int i = 0; i < devices; i++) {
- getAddress(deviceAddress, i);
- setResolution(deviceAddress, bitResolution, true);
- }
-
-}
-
-// set resolution of a device to 9, 10, 11, or 12 bits
-// if new resolution is out of range, 9 bits is used.
-bool DallasTemperature::setResolution(const uint8_t* deviceAddress,
- uint8_t newResolution, bool skipGlobalBitResolutionCalculation) {
-
- // ensure same behavior as setResolution(uint8_t newResolution)
- newResolution = constrain(newResolution, 9, 12);
-
- // return when stored value == new value
- if (getResolution(deviceAddress) == newResolution)
- return true;
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
-
- // DS1820 and DS18S20 have no resolution configuration register
- if (deviceAddress[0] != DS18S20MODEL) {
-
- switch (newResolution) {
- case 12:
- scratchPad[CONFIGURATION] = TEMP_12_BIT;
- break;
- case 11:
- scratchPad[CONFIGURATION] = TEMP_11_BIT;
- break;
- case 10:
- scratchPad[CONFIGURATION] = TEMP_10_BIT;
- break;
- case 9:
- default:
- scratchPad[CONFIGURATION] = TEMP_9_BIT;
- break;
- }
- writeScratchPad(deviceAddress, scratchPad);
-
- // without calculation we can always set it to max
- bitResolution = max(bitResolution, newResolution);
-
- if (!skipGlobalBitResolutionCalculation
- && (bitResolution > newResolution)) {
- bitResolution = newResolution;
- DeviceAddress deviceAddr;
- for (int i = 0; i < devices; i++) {
- getAddress(deviceAddr, i);
- bitResolution = max(bitResolution,
- getResolution(deviceAddr));
- }
- }
- }
- return true; // new value set
- }
-
- return false;
-
-}
-
-// returns the global resolution
-uint8_t DallasTemperature::getResolution() {
- return bitResolution;
-}
-
-// returns the current resolution of the device, 9-12
-// returns 0 if device not found
-uint8_t DallasTemperature::getResolution(const uint8_t* deviceAddress) {
-
- // DS1820 and DS18S20 have no resolution configuration register
- if (deviceAddress[0] == DS18S20MODEL)
- return 12;
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
- switch (scratchPad[CONFIGURATION]) {
- case TEMP_12_BIT:
- return 12;
-
- case TEMP_11_BIT:
- return 11;
-
- case TEMP_10_BIT:
- return 10;
-
- case TEMP_9_BIT:
- return 9;
- }
- }
- return 0;
-
-}
-
-// sets the value of the waitForConversion flag
-// TRUE : function requestTemperature() etc returns when conversion is ready
-// FALSE: function requestTemperature() etc returns immediately (USE WITH CARE!!)
-// (1) programmer has to check if the needed delay has passed
-// (2) but the application can do meaningful things in that time
-void DallasTemperature::setWaitForConversion(bool flag) {
- waitForConversion = flag;
-}
-
-// gets the value of the waitForConversion flag
-bool DallasTemperature::getWaitForConversion() {
- return waitForConversion;
-}
-
-// sets the value of the checkForConversion flag
-// TRUE : function requestTemperature() etc will 'listen' to an IC to determine whether a conversion is complete
-// FALSE: function requestTemperature() etc will wait a set time (worst case scenario) for a conversion to complete
-void DallasTemperature::setCheckForConversion(bool flag) {
- checkForConversion = flag;
-}
-
-// gets the value of the waitForConversion flag
-bool DallasTemperature::getCheckForConversion() {
- return checkForConversion;
-}
-
-bool DallasTemperature::isConversionComplete() {
- uint8_t b = _wire->read_bit();
- return (b == 1);
-}
-
-// sends command for all devices on the bus to perform a temperature conversion
-void DallasTemperature::requestTemperatures() {
-
- _wire->reset();
- _wire->skip();
- _wire->write(STARTCONVO, parasite);
-
- // ASYNC mode?
- if (!waitForConversion)
- return;
- blockTillConversionComplete(bitResolution);
-
-}
-
-// sends command for one device to perform a temperature by address
-// returns FALSE if device is disconnected
-// returns TRUE otherwise
-bool DallasTemperature::requestTemperaturesByAddress(
- const uint8_t* deviceAddress) {
-
- uint8_t bitResolution = getResolution(deviceAddress);
- if (bitResolution == 0) {
- return false; //Device disconnected
- }
-
- _wire->reset();
- _wire->select(deviceAddress);
- _wire->write(STARTCONVO, parasite);
-
- // ASYNC mode?
- if (!waitForConversion)
- return true;
-
- blockTillConversionComplete(bitResolution);
-
- return true;
-
-}
-
-// Continue to check if the IC has responded with a temperature
-void DallasTemperature::blockTillConversionComplete(uint8_t bitResolution) {
-
- int delms = millisToWaitForConversion(bitResolution);
- if (checkForConversion && !parasite) {
- unsigned long now = millis();
- while (!isConversionComplete() && (millis() - delms < now))
- ;
- } else {
- delay(delms);
- }
-
-}
-
-// returns number of milliseconds to wait till conversion is complete (based on IC datasheet)
-int16_t DallasTemperature::millisToWaitForConversion(uint8_t bitResolution) {
-
- switch (bitResolution) {
- case 9:
- return 94;
- case 10:
- return 188;
- case 11:
- return 375;
- default:
- return 750;
- }
-
-}
-
-// sends command for one device to perform a temp conversion by index
-bool DallasTemperature::requestTemperaturesByIndex(uint8_t deviceIndex) {
-
- DeviceAddress deviceAddress;
- getAddress(deviceAddress, deviceIndex);
-
- return requestTemperaturesByAddress(deviceAddress);
-
-}
-
-// Fetch temperature for device index
-float DallasTemperature::getTempCByIndex(uint8_t deviceIndex) {
-
- DeviceAddress deviceAddress;
- if (!getAddress(deviceAddress, deviceIndex)) {
- return DEVICE_DISCONNECTED_C;
- }
-
- return getTempC((uint8_t*) deviceAddress);
-
-}
-
-// Fetch temperature for device index
-float DallasTemperature::getTempFByIndex(uint8_t deviceIndex) {
-
- DeviceAddress deviceAddress;
-
- if (!getAddress(deviceAddress, deviceIndex)) {
- return DEVICE_DISCONNECTED_F;
- }
-
- return getTempF((uint8_t*) deviceAddress);
-
-}
-
-// reads scratchpad and returns fixed-point temperature, scaling factor 2^-7
-int16_t DallasTemperature::calculateTemperature(const uint8_t* deviceAddress,
- uint8_t* scratchPad) {
-
- int16_t fpTemperature = (((int16_t) scratchPad[TEMP_MSB]) << 11)
- | (((int16_t) scratchPad[TEMP_LSB]) << 3);
-
- /*
- DS1820 and DS18S20 have a 9-bit temperature register.
-
- Resolutions greater than 9-bit can be calculated using the data from
- the temperature, and COUNT REMAIN and COUNT PER °C registers in the
- scratchpad. The resolution of the calculation depends on the model.
-
- While the COUNT PER °C register is hard-wired to 16 (10h) in a
- DS18S20, it changes with temperature in DS1820.
-
- After reading the scratchpad, the TEMP_READ value is obtained by
- truncating the 0.5°C bit (bit 0) from the temperature data. The
- extended resolution temperature can then be calculated using the
- following equation:
-
- COUNT_PER_C - COUNT_REMAIN
- TEMPERATURE = TEMP_READ - 0.25 + --------------------------
- COUNT_PER_C
-
- Hagai Shatz simplified this to integer arithmetic for a 12 bits
- value for a DS18S20, and James Cameron added legacy DS1820 support.
-
- See - http://myarduinotoy.blogspot.co.uk/2013/02/12bit-result-from-ds18s20.html
- */
-
- if (deviceAddress[0] == DS18S20MODEL) {
- fpTemperature = ((fpTemperature & 0xfff0) << 3) - 16
- + (((scratchPad[COUNT_PER_C] - scratchPad[COUNT_REMAIN]) << 7)
- / scratchPad[COUNT_PER_C]);
- }
-
- return fpTemperature;
-}
-
-// returns temperature in 1/128 degrees C or DEVICE_DISCONNECTED_RAW if the
-// device's scratch pad cannot be read successfully.
-// the numeric value of DEVICE_DISCONNECTED_RAW is defined in
-// DallasTemperature.h. It is a large negative number outside the
-// operating range of the device
-int16_t DallasTemperature::getTemp(const uint8_t* deviceAddress) {
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad))
- return calculateTemperature(deviceAddress, scratchPad);
- return DEVICE_DISCONNECTED_RAW;
-
-}
-
-// returns temperature in degrees C or DEVICE_DISCONNECTED_C if the
-// device's scratch pad cannot be read successfully.
-// the numeric value of DEVICE_DISCONNECTED_C is defined in
-// DallasTemperature.h. It is a large negative number outside the
-// operating range of the device
-float DallasTemperature::getTempC(const uint8_t* deviceAddress) {
- return rawToCelsius(getTemp(deviceAddress));
-}
-
-// returns temperature in degrees F or DEVICE_DISCONNECTED_F if the
-// device's scratch pad cannot be read successfully.
-// the numeric value of DEVICE_DISCONNECTED_F is defined in
-// DallasTemperature.h. It is a large negative number outside the
-// operating range of the device
-float DallasTemperature::getTempF(const uint8_t* deviceAddress) {
- return rawToFahrenheit(getTemp(deviceAddress));
-}
-
-// returns true if the bus requires parasite power
-bool DallasTemperature::isParasitePowerMode(void) {
- return parasite;
-}
-
-// IF alarm is not used one can store a 16 bit int of userdata in the alarm
-// registers. E.g. an ID of the sensor.
-// See github issue #29
-
-// note if device is not connected it will fail writing the data.
-void DallasTemperature::setUserData(const uint8_t* deviceAddress,
- int16_t data) {
- // return when stored value == new value
- if (getUserData(deviceAddress) == data)
- return;
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
- scratchPad[HIGH_ALARM_TEMP] = data >> 8;
- scratchPad[LOW_ALARM_TEMP] = data & 255;
- writeScratchPad(deviceAddress, scratchPad);
- }
-}
-
-int16_t DallasTemperature::getUserData(const uint8_t* deviceAddress) {
- int16_t data = 0;
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
- data = scratchPad[HIGH_ALARM_TEMP] << 8;
- data += scratchPad[LOW_ALARM_TEMP];
- }
- return data;
-}
-
-// note If address cannot be found no error will be reported.
-int16_t DallasTemperature::getUserDataByIndex(uint8_t deviceIndex) {
- DeviceAddress deviceAddress;
- getAddress(deviceAddress, deviceIndex);
- return getUserData((uint8_t*) deviceAddress);
-}
-
-void DallasTemperature::setUserDataByIndex(uint8_t deviceIndex, int16_t data) {
- DeviceAddress deviceAddress;
- getAddress(deviceAddress, deviceIndex);
- setUserData((uint8_t*) deviceAddress, data);
-}
-
-// Convert float Celsius to Fahrenheit
-float DallasTemperature::toFahrenheit(float celsius) {
- return (celsius * 1.8) + 32;
-}
-
-// Convert float Fahrenheit to Celsius
-float DallasTemperature::toCelsius(float fahrenheit) {
- return (fahrenheit - 32) * 0.555555556;
-}
-
-// convert from raw to Celsius
-float DallasTemperature::rawToCelsius(int16_t raw) {
-
- if (raw <= DEVICE_DISCONNECTED_RAW)
- return DEVICE_DISCONNECTED_C;
- // C = RAW/128
- return (float) raw * 0.0078125;
-
-}
-
-// convert from raw to Fahrenheit
-float DallasTemperature::rawToFahrenheit(int16_t raw) {
-
- if (raw <= DEVICE_DISCONNECTED_RAW)
- return DEVICE_DISCONNECTED_F;
- // C = RAW/128
- // F = (C*1.8)+32 = (RAW/128*1.8)+32 = (RAW*0.0140625)+32
- return ((float) raw * 0.0140625) + 32;
-
-}
-
-#if REQUIRESALARMS
-
-/*
-
- ALARMS:
-
- TH and TL Register Format
-
- BIT 7 BIT 6 BIT 5 BIT 4 BIT 3 BIT 2 BIT 1 BIT 0
- S 2^6 2^5 2^4 2^3 2^2 2^1 2^0
-
- Only bits 11 through 4 of the temperature register are used
- in the TH and TL comparison since TH and TL are 8-bit
- registers. If the measured temperature is lower than or equal
- to TL or higher than or equal to TH, an alarm condition exists
- and an alarm flag is set inside the DS18B20. This flag is
- updated after every temperature measurement; therefore, if the
- alarm condition goes away, the flag will be turned off after
- the next temperature conversion.
-
- */
-
-// sets the high alarm temperature for a device in degrees Celsius
-// accepts a float, but the alarm resolution will ignore anything
-// after a decimal point. valid range is -55C - 125C
-void DallasTemperature::setHighAlarmTemp(const uint8_t* deviceAddress,
- int8_t celsius) {
-
- // return when stored value == new value
- if (getHighAlarmTemp(deviceAddress) == celsius)
- return;
-
- // make sure the alarm temperature is within the device's range
- if (celsius > 125)
- celsius = 125;
- else if (celsius < -55)
- celsius = -55;
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
- scratchPad[HIGH_ALARM_TEMP] = (uint8_t) celsius;
- writeScratchPad(deviceAddress, scratchPad);
- }
-
-}
-
-// sets the low alarm temperature for a device in degrees Celsius
-// accepts a float, but the alarm resolution will ignore anything
-// after a decimal point. valid range is -55C - 125C
-void DallasTemperature::setLowAlarmTemp(const uint8_t* deviceAddress,
- int8_t celsius) {
-
- // return when stored value == new value
- if (getLowAlarmTemp(deviceAddress) == celsius)
- return;
-
- // make sure the alarm temperature is within the device's range
- if (celsius > 125)
- celsius = 125;
- else if (celsius < -55)
- celsius = -55;
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
- scratchPad[LOW_ALARM_TEMP] = (uint8_t) celsius;
- writeScratchPad(deviceAddress, scratchPad);
- }
-
-}
-
-// returns a int8_t with the current high alarm temperature or
-// DEVICE_DISCONNECTED for an address
-int8_t DallasTemperature::getHighAlarmTemp(const uint8_t* deviceAddress) {
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad))
- return (int8_t) scratchPad[HIGH_ALARM_TEMP];
- return DEVICE_DISCONNECTED_C;
-
-}
-
-// returns a int8_t with the current low alarm temperature or
-// DEVICE_DISCONNECTED for an address
-int8_t DallasTemperature::getLowAlarmTemp(const uint8_t* deviceAddress) {
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad))
- return (int8_t) scratchPad[LOW_ALARM_TEMP];
- return DEVICE_DISCONNECTED_C;
-
-}
-
-// resets internal variables used for the alarm search
-void DallasTemperature::resetAlarmSearch() {
-
- alarmSearchJunction = -1;
- alarmSearchExhausted = 0;
- for (uint8_t i = 0; i < 7; i++) {
- alarmSearchAddress[i] = 0;
- }
-
-}
-
-// This is a modified version of the OneWire::search method.
-//
-// Also added the OneWire search fix documented here:
-// http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295
-//
-// Perform an alarm search. If this function returns a '1' then it has
-// enumerated the next device and you may retrieve the ROM from the
-// OneWire::address variable. If there are no devices, no further
-// devices, or something horrible happens in the middle of the
-// enumeration then a 0 is returned. If a new device is found then
-// its address is copied to newAddr. Use
-// DallasTemperature::resetAlarmSearch() to start over.
-bool DallasTemperature::alarmSearch(uint8_t* newAddr) {
-
- uint8_t i;
- int8_t lastJunction = -1;
- uint8_t done = 1;
-
- if (alarmSearchExhausted)
- return false;
- if (!_wire->reset())
- return false;
-
- // send the alarm search command
- _wire->write(0xEC, 0);
-
- for (i = 0; i < 64; i++) {
-
- uint8_t a = _wire->read_bit();
- uint8_t nota = _wire->read_bit();
- uint8_t ibyte = i / 8;
- uint8_t ibit = 1 << (i & 7);
-
- // I don't think this should happen, this means nothing responded, but maybe if
- // something vanishes during the search it will come up.
- if (a && nota)
- return false;
-
- if (!a && !nota) {
- if (i == alarmSearchJunction) {
- // this is our time to decide differently, we went zero last time, go one.
- a = 1;
- alarmSearchJunction = lastJunction;
- } else if (i < alarmSearchJunction) {
-
- // take whatever we took last time, look in address
- if (alarmSearchAddress[ibyte] & ibit) {
- a = 1;
- } else {
- // Only 0s count as pending junctions, we've already exhausted the 0 side of 1s
- a = 0;
- done = 0;
- lastJunction = i;
- }
- } else {
- // we are blazing new tree, take the 0
- a = 0;
- alarmSearchJunction = i;
- done = 0;
- }
- // OneWire search fix
- // See: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295
- }
-
- if (a)
- alarmSearchAddress[ibyte] |= ibit;
- else
- alarmSearchAddress[ibyte] &= ~ibit;
-
- _wire->write_bit(a);
- }
-
- if (done)
- alarmSearchExhausted = 1;
- for (i = 0; i < 8; i++)
- newAddr[i] = alarmSearchAddress[i];
- return true;
-
-}
-
-// returns true if device address might have an alarm condition
-// (only an alarm search can verify this)
-bool DallasTemperature::hasAlarm(const uint8_t* deviceAddress) {
-
- ScratchPad scratchPad;
- if (isConnected(deviceAddress, scratchPad)) {
-
- int8_t temp = calculateTemperature(deviceAddress, scratchPad) >> 7;
-
- // check low alarm
- if (temp <= (int8_t) scratchPad[LOW_ALARM_TEMP])
- return true;
-
- // check high alarm
- if (temp >= (int8_t) scratchPad[HIGH_ALARM_TEMP])
- return true;
- }
-
- // no alarm
- return false;
-
-}
-
-// returns true if any device is reporting an alarm condition on the bus
-bool DallasTemperature::hasAlarm(void) {
-
- DeviceAddress deviceAddress;
- resetAlarmSearch();
- return alarmSearch(deviceAddress);
-}
-
-// runs the alarm handler for all devices returned by alarmSearch()
-// unless there no _AlarmHandler exist.
-void DallasTemperature::processAlarms(void) {
-
-if (!hasAlarmHandler())
-{
- return;
-}
-
- resetAlarmSearch();
- DeviceAddress alarmAddr;
-
- while (alarmSearch(alarmAddr)) {
- if (validAddress(alarmAddr)) {
- _AlarmHandler(alarmAddr);
- }
- }
-}
-
-// sets the alarm handler
-void DallasTemperature::setAlarmHandler(const AlarmHandler *handler) {
- _AlarmHandler = handler;
-}
-
-// checks if AlarmHandler has been set.
-bool DallasTemperature::hasAlarmHandler()
-{
- return _AlarmHandler != NO_ALARM_HANDLER;
-}
-
-#endif
-
-#if REQUIRESNEW
-
-// MnetCS - Allocates memory for DallasTemperature. Allows us to instance a new object
-void* DallasTemperature::operator new(unsigned int size) { // Implicit NSS obj size
-
- void * p;// void pointer
- p = malloc(size);// Allocate memory
- memset((DallasTemperature*)p,0,size);// Initialise memory
-
- //!!! CANT EXPLICITLY CALL CONSTRUCTOR - workaround by using an init() methodR - workaround by using an init() method
- return (DallasTemperature*) p;// Cast blank region to NSS pointer
-}
-
-// MnetCS 2009 - Free the memory used by this instance
-void DallasTemperature::operator delete(void* p) {
-
- DallasTemperature* pNss = (DallasTemperature*) p; // Cast to NSS pointer
- pNss->~DallasTemperature();// Destruct the object
-
- free(p);// Free the memory
-}
-
-#endif
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.h b/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.h
deleted file mode 100644
index d493c88d1..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/DallasTemperature.h
+++ /dev/null
@@ -1,251 +0,0 @@
-#ifndef DallasTemperature_h
-#define DallasTemperature_h
-
-#define DALLASTEMPLIBVERSION "3.7.9" // To be deprecated
-
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-
-// set to true to include code for new and delete operators
-#ifndef REQUIRESNEW
-#define REQUIRESNEW false
-#endif
-
-// set to true to include code implementing alarm search functions
-#ifndef REQUIRESALARMS
-#define REQUIRESALARMS true
-#endif
-
-#include
-#include "OneWire.h"
-
-// Model IDs
-#define DS18S20MODEL 0x10 // also DS1820
-#define DS18B20MODEL 0x28
-#define DS1822MODEL 0x22
-#define DS1825MODEL 0x3B
-#define DS28EA00MODEL 0x42
-
-// Error Codes
-#define DEVICE_DISCONNECTED_C -127
-#define DEVICE_DISCONNECTED_F -196.6
-#define DEVICE_DISCONNECTED_RAW -7040
-
-typedef uint8_t DeviceAddress[8];
-
-class DallasTemperature {
-public:
-
- DallasTemperature();
- DallasTemperature(OneWire*);
-
- void setOneWire(OneWire*);
-
- // initialise bus
- void begin(void);
-
- // returns the number of devices found on the bus
- uint8_t getDeviceCount(void);
-
- // returns the number of DS18xxx Family devices on bus
- uint8_t getDS18Count(void);
-
- // returns true if address is valid
- bool validAddress(const uint8_t*);
-
- // returns true if address is of the family of sensors the lib supports.
- bool validFamily(const uint8_t* deviceAddress);
-
- // finds an address at a given index on the bus
- bool getAddress(uint8_t*, uint8_t);
-
- // attempt to determine if the device at the given address is connected to the bus
- bool isConnected(const uint8_t*);
-
- // attempt to determine if the device at the given address is connected to the bus
- // also allows for updating the read scratchpad
- bool isConnected(const uint8_t*, uint8_t*);
-
- // read device's scratchpad
- bool readScratchPad(const uint8_t*, uint8_t*);
-
- // write device's scratchpad
- void writeScratchPad(const uint8_t*, const uint8_t*);
-
- // read device's power requirements
- bool readPowerSupply(const uint8_t*);
-
- // get global resolution
- uint8_t getResolution();
-
- // set global resolution to 9, 10, 11, or 12 bits
- void setResolution(uint8_t);
-
- // returns the device resolution: 9, 10, 11, or 12 bits
- uint8_t getResolution(const uint8_t*);
-
- // set resolution of a device to 9, 10, 11, or 12 bits
- bool setResolution(const uint8_t*, uint8_t,
- bool skipGlobalBitResolutionCalculation = false);
-
- // sets/gets the waitForConversion flag
- void setWaitForConversion(bool);
- bool getWaitForConversion(void);
-
- // sets/gets the checkForConversion flag
- void setCheckForConversion(bool);
- bool getCheckForConversion(void);
-
- // sends command for all devices on the bus to perform a temperature conversion
- void requestTemperatures(void);
-
- // sends command for one device to perform a temperature conversion by address
- bool requestTemperaturesByAddress(const uint8_t*);
-
- // sends command for one device to perform a temperature conversion by index
- bool requestTemperaturesByIndex(uint8_t);
-
- // returns temperature raw value (12 bit integer of 1/128 degrees C)
- int16_t getTemp(const uint8_t*);
-
- // returns temperature in degrees C
- float getTempC(const uint8_t*);
-
- // returns temperature in degrees F
- float getTempF(const uint8_t*);
-
- // Get temperature for device index (slow)
- float getTempCByIndex(uint8_t);
-
- // Get temperature for device index (slow)
- float getTempFByIndex(uint8_t);
-
- // returns true if the bus requires parasite power
- bool isParasitePowerMode(void);
-
- // Is a conversion complete on the wire? Only applies to the first sensor on the wire.
- bool isConversionComplete(void);
-
- int16_t millisToWaitForConversion(uint8_t);
-
-#if REQUIRESALARMS
-
- typedef void AlarmHandler(const uint8_t*);
-
- // sets the high alarm temperature for a device
- // accepts a int8_t. valid range is -55C - 125C
- void setHighAlarmTemp(const uint8_t*, int8_t);
-
- // sets the low alarm temperature for a device
- // accepts a int8_t. valid range is -55C - 125C
- void setLowAlarmTemp(const uint8_t*, int8_t);
-
- // returns a int8_t with the current high alarm temperature for a device
- // in the range -55C - 125C
- int8_t getHighAlarmTemp(const uint8_t*);
-
- // returns a int8_t with the current low alarm temperature for a device
- // in the range -55C - 125C
- int8_t getLowAlarmTemp(const uint8_t*);
-
- // resets internal variables used for the alarm search
- void resetAlarmSearch(void);
-
- // search the wire for devices with active alarms
- bool alarmSearch(uint8_t*);
-
- // returns true if ia specific device has an alarm
- bool hasAlarm(const uint8_t*);
-
- // returns true if any device is reporting an alarm on the bus
- bool hasAlarm(void);
-
- // runs the alarm handler for all devices returned by alarmSearch()
- void processAlarms(void);
-
- // sets the alarm handler
- void setAlarmHandler(const AlarmHandler *);
-
- // returns true if an AlarmHandler has been set
- bool hasAlarmHandler();
-
-#endif
-
- // if no alarm handler is used the two bytes can be used as user data
- // example of such usage is an ID.
- // note if device is not connected it will fail writing the data.
- // note if address cannot be found no error will be reported.
- // in short use carefully
- void setUserData(const uint8_t*, int16_t);
- void setUserDataByIndex(uint8_t, int16_t);
- int16_t getUserData(const uint8_t*);
- int16_t getUserDataByIndex(uint8_t);
-
- // convert from Celsius to Fahrenheit
- static float toFahrenheit(float);
-
- // convert from Fahrenheit to Celsius
- static float toCelsius(float);
-
- // convert from raw to Celsius
- static float rawToCelsius(int16_t);
-
- // convert from raw to Fahrenheit
- static float rawToFahrenheit(int16_t);
-
-#if REQUIRESNEW
-
- // initialize memory area
- void* operator new (unsigned int);
-
- // delete memory reference
- void operator delete(void*);
-
-#endif
-
-private:
- typedef uint8_t ScratchPad[9];
-
- // parasite power on or off
- bool parasite;
-
- // used to determine the delay amount needed to allow for the
- // temperature conversion to take place
- uint8_t bitResolution;
-
- // used to requestTemperature with or without delay
- bool waitForConversion;
-
- // used to requestTemperature to dynamically check if a conversion is complete
- bool checkForConversion;
-
- // count of devices on the bus
- uint8_t devices;
-
- // count of DS18xxx Family devices on bus
- uint8_t ds18Count;
-
- // Take a pointer to one wire instance
- OneWire* _wire;
-
- // reads scratchpad and returns the raw temperature
- int16_t calculateTemperature(const uint8_t*, uint8_t*);
-
- void blockTillConversionComplete(uint8_t);
-
-#if REQUIRESALARMS
-
- // required for alarmSearch
- uint8_t alarmSearchAddress[8];
- int8_t alarmSearchJunction;
- uint8_t alarmSearchExhausted;
-
- // the alarm handler function pointer
- AlarmHandler *_AlarmHandler;
-
-#endif
-
-};
-#endif
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/OneWire.cpp b/resources/arduino_files/libraries/dallas-temperature-control/OneWire.cpp
deleted file mode 100644
index 5c9945d51..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/OneWire.cpp
+++ /dev/null
@@ -1,597 +0,0 @@
-/*
-Copyright (c) 2007, Jim Studt (original old version - many contributors since)
-
-The latest version of this library may be found at:
- http://www.pjrc.com/teensy/td_libs_OneWire.html
-
-OneWire has been maintained by Paul Stoffregen (paul@pjrc.com) since
-January 2010.
-
-DO NOT EMAIL for technical support, especially not for ESP chips!
-All project support questions must be posted on public forums
-relevant to the board or chips used. If using Arduino, post on
-Arduino's forum. If using ESP, post on the ESP community forums.
-There is ABSOLUTELY NO TECH SUPPORT BY PRIVATE EMAIL!
-
-Github's issue tracker for OneWire should be used only to report
-specific bugs. DO NOT request project support via Github. All
-project and tech support questions must be posted on forums, not
-github issues. If you experience a problem and you are not
-absolutely sure it's an issue with the library, ask on a forum
-first. Only use github to report issues after experts have
-confirmed the issue is with OneWire rather than your project.
-
-Back in 2010, OneWire was in need of many bug fixes, but had
-been abandoned the original author (Jim Studt). None of the known
-contributors were interested in maintaining OneWire. Paul typically
-works on OneWire every 6 to 12 months. Patches usually wait that
-long. If anyone is interested in more actively maintaining OneWire,
-please contact Paul (this is pretty much the only reason to use
-private email about OneWire).
-
-OneWire is now very mature code. No changes other than adding
-definitions for newer hardware support are anticipated.
-
-Version 2.3:
- Unknown chip fallback mode, Roger Clark
- Teensy-LC compatibility, Paul Stoffregen
- Search bug fix, Love Nystrom
-
-Version 2.2:
- Teensy 3.0 compatibility, Paul Stoffregen, paul@pjrc.com
- Arduino Due compatibility, http://arduino.cc/forum/index.php?topic=141030
- Fix DS18B20 example negative temperature
- Fix DS18B20 example's low res modes, Ken Butcher
- Improve reset timing, Mark Tillotson
- Add const qualifiers, Bertrik Sikken
- Add initial value input to crc16, Bertrik Sikken
- Add target_search() function, Scott Roberts
-
-Version 2.1:
- Arduino 1.0 compatibility, Paul Stoffregen
- Improve temperature example, Paul Stoffregen
- DS250x_PROM example, Guillermo Lovato
- PIC32 (chipKit) compatibility, Jason Dangel, dangel.jason AT gmail.com
- Improvements from Glenn Trewitt:
- - crc16() now works
- - check_crc16() does all of calculation/checking work.
- - Added read_bytes() and write_bytes(), to reduce tedious loops.
- - Added ds2408 example.
- Delete very old, out-of-date readme file (info is here)
-
-Version 2.0: Modifications by Paul Stoffregen, January 2010:
-http://www.pjrc.com/teensy/td_libs_OneWire.html
- Search fix from Robin James
- http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
- Use direct optimized I/O in all cases
- Disable interrupts during timing critical sections
- (this solves many random communication errors)
- Disable interrupts during read-modify-write I/O
- Reduce RAM consumption by eliminating unnecessary
- variables and trimming many to 8 bits
- Optimize both crc8 - table version moved to flash
-
-Modified to work with larger numbers of devices - avoids loop.
-Tested in Arduino 11 alpha with 12 sensors.
-26 Sept 2008 -- Robin James
-http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1238032295/27#27
-
-Updated to work with arduino-0008 and to include skip() as of
-2007/07/06. --RJL20
-
-Modified to calculate the 8-bit CRC directly, avoiding the need for
-the 256-byte lookup table to be loaded in RAM. Tested in arduino-0010
--- Tom Pollard, Jan 23, 2008
-
-Jim Studt's original library was modified by Josh Larios.
-
-Tom Pollard, pollard@alum.mit.edu, contributed around May 20, 2008
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-Much of the code was inspired by Derek Yerger's code, though I don't
-think much of that remains. In any event that was..
- (copyleft) 2006 by Derek Yerger - Free to distribute freely.
-
-The CRC code was excerpted and inspired by the Dallas Semiconductor
-sample code bearing this copyright.
-//---------------------------------------------------------------------------
-// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the "Software"),
-// to deal in the Software without restriction, including without limitation
-// the rights to use, copy, modify, merge, publish, distribute, sublicense,
-// and/or sell copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
-// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
-//
-// Except as contained in this notice, the name of Dallas Semiconductor
-// shall not be used except as stated in the Dallas Semiconductor
-// Branding Policy.
-//--------------------------------------------------------------------------
-*/
-
-#include "OneWire.h"
-
-
-OneWire::OneWire(uint8_t pin)
-{
- pinMode(pin, INPUT);
- bitmask = PIN_TO_BITMASK(pin);
- baseReg = PIN_TO_BASEREG(pin);
-#if ONEWIRE_SEARCH
- reset_search();
-#endif
-}
-
-
-// Perform the onewire reset function. We will wait up to 250uS for
-// the bus to come high, if it doesn't then it is broken or shorted
-// and we return a 0;
-//
-// Returns 1 if a device asserted a presence pulse, 0 otherwise.
-//
-uint8_t OneWire::reset(void)
-{
- IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
- volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
- uint8_t r;
- uint8_t retries = 125;
-
- noInterrupts();
- DIRECT_MODE_INPUT(reg, mask);
- interrupts();
- // wait until the wire is high... just in case
- do {
- if (--retries == 0) return 0;
- delayMicroseconds(2);
- } while ( !DIRECT_READ(reg, mask));
-
- noInterrupts();
- DIRECT_WRITE_LOW(reg, mask);
- DIRECT_MODE_OUTPUT(reg, mask); // drive output low
- interrupts();
- delayMicroseconds(480);
- noInterrupts();
- DIRECT_MODE_INPUT(reg, mask); // allow it to float
- delayMicroseconds(70);
- r = !DIRECT_READ(reg, mask);
- interrupts();
- delayMicroseconds(410);
- return r;
-}
-
-//
-// Write a bit. Port and bit is used to cut lookup time and provide
-// more certain timing.
-//
-void OneWire::write_bit(uint8_t v)
-{
- IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
- volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
-
- if (v & 1) {
- noInterrupts();
- DIRECT_WRITE_LOW(reg, mask);
- DIRECT_MODE_OUTPUT(reg, mask); // drive output low
- delayMicroseconds(10);
- DIRECT_WRITE_HIGH(reg, mask); // drive output high
- interrupts();
- delayMicroseconds(55);
- } else {
- noInterrupts();
- DIRECT_WRITE_LOW(reg, mask);
- DIRECT_MODE_OUTPUT(reg, mask); // drive output low
- delayMicroseconds(65);
- DIRECT_WRITE_HIGH(reg, mask); // drive output high
- interrupts();
- delayMicroseconds(5);
- }
-}
-
-//
-// Read a bit. Port and bit is used to cut lookup time and provide
-// more certain timing.
-//
-uint8_t OneWire::read_bit(void)
-{
- IO_REG_TYPE mask IO_REG_MASK_ATTR = bitmask;
- volatile IO_REG_TYPE *reg IO_REG_BASE_ATTR = baseReg;
- uint8_t r;
-
- noInterrupts();
- DIRECT_MODE_OUTPUT(reg, mask);
- DIRECT_WRITE_LOW(reg, mask);
- delayMicroseconds(3);
- DIRECT_MODE_INPUT(reg, mask); // let pin float, pull up will raise
- delayMicroseconds(10);
- r = DIRECT_READ(reg, mask);
- interrupts();
- delayMicroseconds(53);
- return r;
-}
-
-//
-// Write a byte. The writing code uses the active drivers to raise the
-// pin high, if you need power after the write (e.g. DS18S20 in
-// parasite power mode) then set 'power' to 1, otherwise the pin will
-// go tri-state at the end of the write to avoid heating in a short or
-// other mishap.
-//
-void OneWire::write(uint8_t v, uint8_t power /* = 0 */) {
- uint8_t bitMask;
-
- for (bitMask = 0x01; bitMask; bitMask <<= 1) {
- OneWire::write_bit( (bitMask & v)?1:0);
- }
- if ( !power) {
- noInterrupts();
- DIRECT_MODE_INPUT(baseReg, bitmask);
- DIRECT_WRITE_LOW(baseReg, bitmask);
- interrupts();
- }
-}
-
-void OneWire::write_bytes(const uint8_t *buf, uint16_t count, bool power /* = 0 */) {
- for (uint16_t i = 0 ; i < count ; i++)
- write(buf[i]);
- if (!power) {
- noInterrupts();
- DIRECT_MODE_INPUT(baseReg, bitmask);
- DIRECT_WRITE_LOW(baseReg, bitmask);
- interrupts();
- }
-}
-
-//
-// Read a byte
-//
-uint8_t OneWire::read() {
- uint8_t bitMask;
- uint8_t r = 0;
-
- for (bitMask = 0x01; bitMask; bitMask <<= 1) {
- if ( OneWire::read_bit()) r |= bitMask;
- }
- return r;
-}
-
-void OneWire::read_bytes(uint8_t *buf, uint16_t count) {
- for (uint16_t i = 0 ; i < count ; i++)
- buf[i] = read();
-}
-
-//
-// Do a ROM select
-//
-void OneWire::select(const uint8_t rom[8])
-{
- uint8_t i;
-
- write(0x55); // Choose ROM
-
- for (i = 0; i < 8; i++) write(rom[i]);
-}
-
-//
-// Do a ROM skip
-//
-void OneWire::skip()
-{
- write(0xCC); // Skip ROM
-}
-
-void OneWire::depower()
-{
- noInterrupts();
- DIRECT_MODE_INPUT(baseReg, bitmask);
- interrupts();
-}
-
-#if ONEWIRE_SEARCH
-
-//
-// You need to use this function to start a search again from the beginning.
-// You do not need to do it for the first search, though you could.
-//
-void OneWire::reset_search()
-{
- // reset the search state
- LastDiscrepancy = 0;
- LastDeviceFlag = FALSE;
- LastFamilyDiscrepancy = 0;
- for(int i = 7; ; i--) {
- ROM_NO[i] = 0;
- if ( i == 0) break;
- }
-}
-
-// Setup the search to find the device type 'family_code' on the next call
-// to search(*newAddr) if it is present.
-//
-void OneWire::target_search(uint8_t family_code)
-{
- // set the search state to find SearchFamily type devices
- ROM_NO[0] = family_code;
- for (uint8_t i = 1; i < 8; i++)
- ROM_NO[i] = 0;
- LastDiscrepancy = 64;
- LastFamilyDiscrepancy = 0;
- LastDeviceFlag = FALSE;
-}
-
-//
-// Perform a search. If this function returns a '1' then it has
-// enumerated the next device and you may retrieve the ROM from the
-// OneWire::address variable. If there are no devices, no further
-// devices, or something horrible happens in the middle of the
-// enumeration then a 0 is returned. If a new device is found then
-// its address is copied to newAddr. Use OneWire::reset_search() to
-// start over.
-//
-// --- Replaced by the one from the Dallas Semiconductor web site ---
-//--------------------------------------------------------------------------
-// Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing
-// search state.
-// Return TRUE : device found, ROM number in ROM_NO buffer
-// FALSE : device not found, end of search
-//
-uint8_t OneWire::search(uint8_t *newAddr, bool search_mode /* = true */)
-{
- uint8_t id_bit_number;
- uint8_t last_zero, rom_byte_number, search_result;
- uint8_t id_bit, cmp_id_bit;
-
- unsigned char rom_byte_mask, search_direction;
-
- // initialize for search
- id_bit_number = 1;
- last_zero = 0;
- rom_byte_number = 0;
- rom_byte_mask = 1;
- search_result = 0;
-
- // if the last call was not the last one
- if (!LastDeviceFlag)
- {
- // 1-Wire reset
- if (!reset())
- {
- // reset the search
- LastDiscrepancy = 0;
- LastDeviceFlag = FALSE;
- LastFamilyDiscrepancy = 0;
- return FALSE;
- }
-
- // issue the search command
- if (search_mode == true) {
- write(0xF0); // NORMAL SEARCH
- } else {
- write(0xEC); // CONDITIONAL SEARCH
- }
-
- // loop to do the search
- do
- {
- // read a bit and its complement
- id_bit = read_bit();
- cmp_id_bit = read_bit();
-
- // check for no devices on 1-wire
- if ((id_bit == 1) && (cmp_id_bit == 1))
- break;
- else
- {
- // all devices coupled have 0 or 1
- if (id_bit != cmp_id_bit)
- search_direction = id_bit; // bit write value for search
- else
- {
- // if this discrepancy if before the Last Discrepancy
- // on a previous next then pick the same as last time
- if (id_bit_number < LastDiscrepancy)
- search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0);
- else
- // if equal to last pick 1, if not then pick 0
- search_direction = (id_bit_number == LastDiscrepancy);
-
- // if 0 was picked then record its position in LastZero
- if (search_direction == 0)
- {
- last_zero = id_bit_number;
-
- // check for Last discrepancy in family
- if (last_zero < 9)
- LastFamilyDiscrepancy = last_zero;
- }
- }
-
- // set or clear the bit in the ROM byte rom_byte_number
- // with mask rom_byte_mask
- if (search_direction == 1)
- ROM_NO[rom_byte_number] |= rom_byte_mask;
- else
- ROM_NO[rom_byte_number] &= ~rom_byte_mask;
-
- // serial number search direction write bit
- write_bit(search_direction);
-
- // increment the byte counter id_bit_number
- // and shift the mask rom_byte_mask
- id_bit_number++;
- rom_byte_mask <<= 1;
-
- // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask
- if (rom_byte_mask == 0)
- {
- rom_byte_number++;
- rom_byte_mask = 1;
- }
- }
- }
- while(rom_byte_number < 8); // loop until through all ROM bytes 0-7
-
- // if the search was successful then
- if (!(id_bit_number < 65))
- {
- // search successful so set LastDiscrepancy,LastDeviceFlag,search_result
- LastDiscrepancy = last_zero;
-
- // check for last device
- if (LastDiscrepancy == 0)
- LastDeviceFlag = TRUE;
-
- search_result = TRUE;
- }
- }
-
- // if no device found then reset counters so next 'search' will be like a first
- if (!search_result || !ROM_NO[0])
- {
- LastDiscrepancy = 0;
- LastDeviceFlag = FALSE;
- LastFamilyDiscrepancy = 0;
- search_result = FALSE;
- } else {
- for (int i = 0; i < 8; i++) newAddr[i] = ROM_NO[i];
- }
- return search_result;
- }
-
-#endif
-
-#if ONEWIRE_CRC
-// The 1-Wire CRC scheme is described in Maxim Application Note 27:
-// "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products"
-//
-
-#if ONEWIRE_CRC8_TABLE
-// This table comes from Dallas sample code where it is freely reusable,
-// though Copyright (C) 2000 Dallas Semiconductor Corporation
-static const uint8_t PROGMEM dscrc_table[] = {
- 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
- 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220,
- 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98,
- 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
- 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7,
- 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154,
- 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36,
- 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185,
- 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
- 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
- 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
- 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
- 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139,
- 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
- 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
- 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53};
-
-//
-// Compute a Dallas Semiconductor 8 bit CRC. These show up in the ROM
-// and the registers. (note: this might better be done without to
-// table, it would probably be smaller and certainly fast enough
-// compared to all those delayMicrosecond() calls. But I got
-// confused, so I use this table from the examples.)
-//
-uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
-{
- uint8_t crc = 0;
-
- while (len--) {
- crc = pgm_read_byte(dscrc_table + (crc ^ *addr++));
- }
- return crc;
-}
-#else
-//
-// Compute a Dallas Semiconductor 8 bit CRC directly.
-// this is much slower, but much smaller, than the lookup table.
-//
-uint8_t OneWire::crc8(const uint8_t *addr, uint8_t len)
-{
- uint8_t crc = 0;
-
- while (len--) {
-#if defined(__AVR__)
- crc = _crc_ibutton_update(crc, *addr++);
-#else
- uint8_t inbyte = *addr++;
- for (uint8_t i = 8; i; i--) {
- uint8_t mix = (crc ^ inbyte) & 0x01;
- crc >>= 1;
- if (mix) crc ^= 0x8C;
- inbyte >>= 1;
- }
-#endif
- }
- return crc;
-}
-#endif
-
-#if ONEWIRE_CRC16
-bool OneWire::check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc)
-{
- crc = ~crc16(input, len, crc);
- return (crc & 0xFF) == inverted_crc[0] && (crc >> 8) == inverted_crc[1];
-}
-
-uint16_t OneWire::crc16(const uint8_t* input, uint16_t len, uint16_t crc)
-{
-#if defined(__AVR__)
- for (uint16_t i = 0 ; i < len ; i++) {
- crc = _crc16_update(crc, input[i]);
- }
-#else
- static const uint8_t oddparity[16] =
- { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 };
-
- for (uint16_t i = 0 ; i < len ; i++) {
- // Even though we're just copying a byte from the input,
- // we'll be doing 16-bit computation with it.
- uint16_t cdata = input[i];
- cdata = (cdata ^ crc) & 0xff;
- crc >>= 8;
-
- if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4])
- crc ^= 0xC001;
-
- cdata <<= 6;
- crc ^= cdata;
- cdata <<= 1;
- crc ^= cdata;
- }
-#endif
- return crc;
-}
-#endif
-
-#endif
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/OneWire.h b/resources/arduino_files/libraries/dallas-temperature-control/OneWire.h
deleted file mode 100644
index c394f1d05..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/OneWire.h
+++ /dev/null
@@ -1,545 +0,0 @@
-#ifndef OneWire_h
-#define OneWire_h
-
-#include
-
-#if defined(__AVR__)
-#include
-#endif
-
-#if ARDUINO >= 100
-#include "Arduino.h" // for delayMicroseconds, digitalPinToBitMask, etc
-#else
-#include "WProgram.h" // for delayMicroseconds
-#include "pins_arduino.h" // for digitalPinToBitMask, etc
-#endif
-
-// You can exclude certain features from OneWire. In theory, this
-// might save some space. In practice, the compiler automatically
-// removes unused code (technically, the linker, using -fdata-sections
-// and -ffunction-sections when compiling, and Wl,--gc-sections
-// when linking), so most of these will not result in any code size
-// reduction. Well, unless you try to use the missing features
-// and redesign your program to not need them! ONEWIRE_CRC8_TABLE
-// is the exception, because it selects a fast but large algorithm
-// or a small but slow algorithm.
-
-// you can exclude onewire_search by defining that to 0
-#ifndef ONEWIRE_SEARCH
-#define ONEWIRE_SEARCH 1
-#endif
-
-// You can exclude CRC checks altogether by defining this to 0
-#ifndef ONEWIRE_CRC
-#define ONEWIRE_CRC 1
-#endif
-
-// Select the table-lookup method of computing the 8-bit CRC
-// by setting this to 1. The lookup table enlarges code size by
-// about 250 bytes. It does NOT consume RAM (but did in very
-// old versions of OneWire). If you disable this, a slower
-// but very compact algorithm is used.
-#ifndef ONEWIRE_CRC8_TABLE
-#define ONEWIRE_CRC8_TABLE 1
-#endif
-
-// You can allow 16-bit CRC checks by defining this to 1
-// (Note that ONEWIRE_CRC must also be 1.)
-#ifndef ONEWIRE_CRC16
-#define ONEWIRE_CRC16 1
-#endif
-
-#ifndef FALSE
-#define FALSE 0
-#endif
-#ifndef TRUE
-#define TRUE 1
-#endif
-
-// Platform specific I/O definitions
-
-#if defined(__AVR__)
-#define PIN_TO_BASEREG(pin) (portInputRegister(digitalPinToPort(pin)))
-#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
-#define IO_REG_TYPE uint8_t
-#define IO_REG_BASE_ATTR asm("r30")
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) (((*(base)) & (mask)) ? 1 : 0)
-#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) &= ~(mask))
-#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+1)) |= (mask))
-#define DIRECT_WRITE_LOW(base, mask) ((*((base)+2)) &= ~(mask))
-#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+2)) |= (mask))
-
-#elif defined(__MK20DX128__) || defined(__MK20DX256__) || defined(__MK66FX1M0__) || defined(__MK64FX512__)
-#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
-#define PIN_TO_BITMASK(pin) (1)
-#define IO_REG_TYPE uint8_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR __attribute__ ((unused))
-#define DIRECT_READ(base, mask) (*((base)+512))
-#define DIRECT_MODE_INPUT(base, mask) (*((base)+640) = 0)
-#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+640) = 1)
-#define DIRECT_WRITE_LOW(base, mask) (*((base)+256) = 1)
-#define DIRECT_WRITE_HIGH(base, mask) (*((base)+128) = 1)
-
-#elif defined(__MKL26Z64__)
-#define PIN_TO_BASEREG(pin) (portOutputRegister(pin))
-#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
-#define IO_REG_TYPE uint8_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) ((*((base)+16) & (mask)) ? 1 : 0)
-#define DIRECT_MODE_INPUT(base, mask) (*((base)+20) &= ~(mask))
-#define DIRECT_MODE_OUTPUT(base, mask) (*((base)+20) |= (mask))
-#define DIRECT_WRITE_LOW(base, mask) (*((base)+8) = (mask))
-#define DIRECT_WRITE_HIGH(base, mask) (*((base)+4) = (mask))
-
-#elif defined(__SAM3X8E__) || defined(__SAM3A8C__) || defined(__SAM3A4C__)
-// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
-// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
-// If you have trouble with OneWire on Arduino Due, please check the
-// status of delayMicroseconds() before reporting a bug in OneWire!
-#define PIN_TO_BASEREG(pin) (&(digitalPinToPort(pin)->PIO_PER))
-#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) (((*((base)+15)) & (mask)) ? 1 : 0)
-#define DIRECT_MODE_INPUT(base, mask) ((*((base)+5)) = (mask))
-#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+4)) = (mask))
-#define DIRECT_WRITE_LOW(base, mask) ((*((base)+13)) = (mask))
-#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+12)) = (mask))
-#ifndef PROGMEM
-#define PROGMEM
-#endif
-#ifndef pgm_read_byte
-#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
-#endif
-
-#elif defined(__PIC32MX__)
-#define PIN_TO_BASEREG(pin) (portModeRegister(digitalPinToPort(pin)))
-#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) (((*(base+4)) & (mask)) ? 1 : 0) //PORTX + 0x10
-#define DIRECT_MODE_INPUT(base, mask) ((*(base+2)) = (mask)) //TRISXSET + 0x08
-#define DIRECT_MODE_OUTPUT(base, mask) ((*(base+1)) = (mask)) //TRISXCLR + 0x04
-#define DIRECT_WRITE_LOW(base, mask) ((*(base+8+1)) = (mask)) //LATXCLR + 0x24
-#define DIRECT_WRITE_HIGH(base, mask) ((*(base+8+2)) = (mask)) //LATXSET + 0x28
-
-#elif defined(ARDUINO_ARCH_ESP8266)
-// Special note: I depend on the ESP community to maintain these definitions and
-// submit good pull requests. I can not answer any ESP questions or help you
-// resolve any problems related to ESP chips. Please do not contact me and please
-// DO NOT CREATE GITHUB ISSUES for ESP support. All ESP questions must be asked
-// on ESP community forums.
-#define PIN_TO_BASEREG(pin) ((volatile uint32_t*) GPO)
-#define PIN_TO_BITMASK(pin) (1 << pin)
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) ((GPI & (mask)) ? 1 : 0) //GPIO_IN_ADDRESS
-#define DIRECT_MODE_INPUT(base, mask) (GPE &= ~(mask)) //GPIO_ENABLE_W1TC_ADDRESS
-#define DIRECT_MODE_OUTPUT(base, mask) (GPE |= (mask)) //GPIO_ENABLE_W1TS_ADDRESS
-#define DIRECT_WRITE_LOW(base, mask) (GPOC = (mask)) //GPIO_OUT_W1TC_ADDRESS
-#define DIRECT_WRITE_HIGH(base, mask) (GPOS = (mask)) //GPIO_OUT_W1TS_ADDRESS
-
-#elif defined(ARDUINO_ARCH_ESP32)
-#include
-#define PIN_TO_BASEREG(pin) (0)
-#define PIN_TO_BITMASK(pin) (pin)
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-
-static inline __attribute__((always_inline))
-IO_REG_TYPE directRead(IO_REG_TYPE pin)
-{
- if ( pin < 32 )
- return (GPIO.in >> pin) & 0x1;
- else if ( pin < 40 )
- return (GPIO.in1.val >> (pin - 32)) & 0x1;
-
- return 0;
-}
-
-static inline __attribute__((always_inline))
-void directWriteLow(IO_REG_TYPE pin)
-{
- if ( pin < 32 )
- GPIO.out_w1tc = ((uint32_t)1 << pin);
- else if ( pin < 34 )
- GPIO.out1_w1tc.val = ((uint32_t)1 << (pin - 32));
-}
-
-static inline __attribute__((always_inline))
-void directWriteHigh(IO_REG_TYPE pin)
-{
- if ( pin < 32 )
- GPIO.out_w1ts = ((uint32_t)1 << pin);
- else if ( pin < 34 )
- GPIO.out1_w1ts.val = ((uint32_t)1 << (pin - 32));
-}
-
-static inline __attribute__((always_inline))
-void directModeInput(IO_REG_TYPE pin)
-{
- if ( digitalPinIsValid(pin) )
- {
- uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
-
- if ( rtc_reg ) // RTC pins PULL settings
- {
- ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
- ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
- }
-
- if ( pin < 32 )
- GPIO.enable_w1tc = ((uint32_t)1 << pin);
- else
- GPIO.enable1_w1tc.val = ((uint32_t)1 << (pin - 32));
-
- uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
- pinFunction |= FUN_IE; // input enable but required for output as well?
- pinFunction |= ((uint32_t)2 << MCU_SEL_S);
-
- ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
-
- GPIO.pin[pin].val = 0;
- }
-}
-
-static inline __attribute__((always_inline))
-void directModeOutput(IO_REG_TYPE pin)
-{
- if ( digitalPinIsValid(pin) && pin <= 33 ) // pins above 33 can be only inputs
- {
- uint32_t rtc_reg(rtc_gpio_desc[pin].reg);
-
- if ( rtc_reg ) // RTC pins PULL settings
- {
- ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
- ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
- }
-
- if ( pin < 32 )
- GPIO.enable_w1ts = ((uint32_t)1 << pin);
- else // already validated to pins <= 33
- GPIO.enable1_w1ts.val = ((uint32_t)1 << (pin - 32));
-
- uint32_t pinFunction((uint32_t)2 << FUN_DRV_S); // what are the drivers?
- pinFunction |= FUN_IE; // input enable but required for output as well?
- pinFunction |= ((uint32_t)2 << MCU_SEL_S);
-
- ESP_REG(DR_REG_IO_MUX_BASE + esp32_gpioMux[pin].reg) = pinFunction;
-
- GPIO.pin[pin].val = 0;
- }
-}
-
-#define DIRECT_READ(base, pin) directRead(pin)
-#define DIRECT_WRITE_LOW(base, pin) directWriteLow(pin)
-#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(pin)
-#define DIRECT_MODE_INPUT(base, pin) directModeInput(pin)
-#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(pin)
-#warning "ESP32 OneWire testing"
-
-#elif defined(__SAMD21G18A__)
-#define PIN_TO_BASEREG(pin) portModeRegister(digitalPinToPort(pin))
-#define PIN_TO_BITMASK(pin) (digitalPinToBitMask(pin))
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, mask) (((*((base)+8)) & (mask)) ? 1 : 0)
-#define DIRECT_MODE_INPUT(base, mask) ((*((base)+1)) = (mask))
-#define DIRECT_MODE_OUTPUT(base, mask) ((*((base)+2)) = (mask))
-#define DIRECT_WRITE_LOW(base, mask) ((*((base)+5)) = (mask))
-#define DIRECT_WRITE_HIGH(base, mask) ((*((base)+6)) = (mask))
-
-#elif defined(RBL_NRF51822)
-#define PIN_TO_BASEREG(pin) (0)
-#define PIN_TO_BITMASK(pin) (pin)
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, pin) nrf_gpio_pin_read(pin)
-#define DIRECT_WRITE_LOW(base, pin) nrf_gpio_pin_clear(pin)
-#define DIRECT_WRITE_HIGH(base, pin) nrf_gpio_pin_set(pin)
-#define DIRECT_MODE_INPUT(base, pin) nrf_gpio_cfg_input(pin, NRF_GPIO_PIN_NOPULL)
-#define DIRECT_MODE_OUTPUT(base, pin) nrf_gpio_cfg_output(pin)
-
-#elif defined(__arc__) /* Arduino101/Genuino101 specifics */
-
-#include "scss_registers.h"
-#include "portable.h"
-#include "avr/pgmspace.h"
-
-#define GPIO_ID(pin) (g_APinDescription[pin].ulGPIOId)
-#define GPIO_TYPE(pin) (g_APinDescription[pin].ulGPIOType)
-#define GPIO_BASE(pin) (g_APinDescription[pin].ulGPIOBase)
-#define DIR_OFFSET_SS 0x01
-#define DIR_OFFSET_SOC 0x04
-#define EXT_PORT_OFFSET_SS 0x0A
-#define EXT_PORT_OFFSET_SOC 0x50
-
-/* GPIO registers base address */
-#define PIN_TO_BASEREG(pin) ((volatile uint32_t *)g_APinDescription[pin].ulGPIOBase)
-#define PIN_TO_BITMASK(pin) pin
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-
-static inline __attribute__((always_inline))
-IO_REG_TYPE directRead(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
-{
- IO_REG_TYPE ret;
- if (SS_GPIO == GPIO_TYPE(pin)) {
- ret = READ_ARC_REG(((IO_REG_TYPE)base + EXT_PORT_OFFSET_SS));
- } else {
- ret = MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, EXT_PORT_OFFSET_SOC);
- }
- return ((ret >> GPIO_ID(pin)) & 0x01);
-}
-
-static inline __attribute__((always_inline))
-void directModeInput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
-{
- if (SS_GPIO == GPIO_TYPE(pin)) {
- WRITE_ARC_REG(READ_ARC_REG((((IO_REG_TYPE)base) + DIR_OFFSET_SS)) & ~(0x01 << GPIO_ID(pin)),
- ((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
- } else {
- MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) &= ~(0x01 << GPIO_ID(pin));
- }
-}
-
-static inline __attribute__((always_inline))
-void directModeOutput(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
-{
- if (SS_GPIO == GPIO_TYPE(pin)) {
- WRITE_ARC_REG(READ_ARC_REG(((IO_REG_TYPE)(base) + DIR_OFFSET_SS)) | (0x01 << GPIO_ID(pin)),
- ((IO_REG_TYPE)(base) + DIR_OFFSET_SS));
- } else {
- MMIO_REG_VAL_FROM_BASE((IO_REG_TYPE)base, DIR_OFFSET_SOC) |= (0x01 << GPIO_ID(pin));
- }
-}
-
-static inline __attribute__((always_inline))
-void directWriteLow(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
-{
- if (SS_GPIO == GPIO_TYPE(pin)) {
- WRITE_ARC_REG(READ_ARC_REG(base) & ~(0x01 << GPIO_ID(pin)), base);
- } else {
- MMIO_REG_VAL(base) &= ~(0x01 << GPIO_ID(pin));
- }
-}
-
-static inline __attribute__((always_inline))
-void directWriteHigh(volatile IO_REG_TYPE *base, IO_REG_TYPE pin)
-{
- if (SS_GPIO == GPIO_TYPE(pin)) {
- WRITE_ARC_REG(READ_ARC_REG(base) | (0x01 << GPIO_ID(pin)), base);
- } else {
- MMIO_REG_VAL(base) |= (0x01 << GPIO_ID(pin));
- }
-}
-
-#define DIRECT_READ(base, pin) directRead(base, pin)
-#define DIRECT_MODE_INPUT(base, pin) directModeInput(base, pin)
-#define DIRECT_MODE_OUTPUT(base, pin) directModeOutput(base, pin)
-#define DIRECT_WRITE_LOW(base, pin) directWriteLow(base, pin)
-#define DIRECT_WRITE_HIGH(base, pin) directWriteHigh(base, pin)
-
-#elif defined(__riscv)
-
-/*
- * Tested on highfive1
- *
- * Stable results are achieved operating in the
- * two high speed modes of the highfive1. It
- * seems to be less reliable in slow mode.
- */
-#define PIN_TO_BASEREG(pin) (0)
-#define PIN_TO_BITMASK(pin) digitalPinToBitMask(pin)
-#define IO_REG_TYPE uint32_t
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-
-static inline __attribute__((always_inline))
-IO_REG_TYPE directRead(IO_REG_TYPE mask)
-{
- return ((GPIO_REG(GPIO_INPUT_VAL) & mask) != 0) ? 1 : 0;
-}
-
-static inline __attribute__((always_inline))
-void directModeInput(IO_REG_TYPE mask)
-{
- GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
- GPIO_REG(GPIO_IOF_EN) &= ~mask;
-
- GPIO_REG(GPIO_INPUT_EN) |= mask;
- GPIO_REG(GPIO_OUTPUT_EN) &= ~mask;
-}
-
-static inline __attribute__((always_inline))
-void directModeOutput(IO_REG_TYPE mask)
-{
- GPIO_REG(GPIO_OUTPUT_XOR) &= ~mask;
- GPIO_REG(GPIO_IOF_EN) &= ~mask;
-
- GPIO_REG(GPIO_INPUT_EN) &= ~mask;
- GPIO_REG(GPIO_OUTPUT_EN) |= mask;
-}
-
-static inline __attribute__((always_inline))
-void directWriteLow(IO_REG_TYPE mask)
-{
- GPIO_REG(GPIO_OUTPUT_VAL) &= ~mask;
-}
-
-static inline __attribute__((always_inline))
-void directWriteHigh(IO_REG_TYPE mask)
-{
- GPIO_REG(GPIO_OUTPUT_VAL) |= mask;
-}
-
-#define DIRECT_READ(base, mask) directRead(mask)
-#define DIRECT_WRITE_LOW(base, mask) directWriteLow(mask)
-#define DIRECT_WRITE_HIGH(base, mask) directWriteHigh(mask)
-#define DIRECT_MODE_INPUT(base, mask) directModeInput(mask)
-#define DIRECT_MODE_OUTPUT(base, mask) directModeOutput(mask)
-
-#else
-#define PIN_TO_BASEREG(pin) (0)
-#define PIN_TO_BITMASK(pin) (pin)
-#define IO_REG_TYPE unsigned int
-#define IO_REG_BASE_ATTR
-#define IO_REG_MASK_ATTR
-#define DIRECT_READ(base, pin) digitalRead(pin)
-#define DIRECT_WRITE_LOW(base, pin) digitalWrite(pin, LOW)
-#define DIRECT_WRITE_HIGH(base, pin) digitalWrite(pin, HIGH)
-#define DIRECT_MODE_INPUT(base, pin) pinMode(pin,INPUT)
-#define DIRECT_MODE_OUTPUT(base, pin) pinMode(pin,OUTPUT)
-#warning "OneWire. Fallback mode. Using API calls for pinMode,digitalRead and digitalWrite. Operation of this library is not guaranteed on this architecture."
-
-#endif
-
-
-class OneWire
-{
- private:
- IO_REG_TYPE bitmask;
- volatile IO_REG_TYPE *baseReg;
-
-#if ONEWIRE_SEARCH
- // global search state
- unsigned char ROM_NO[8];
- uint8_t LastDiscrepancy;
- uint8_t LastFamilyDiscrepancy;
- uint8_t LastDeviceFlag;
-#endif
-
- public:
- OneWire( uint8_t pin);
-
- // Perform a 1-Wire reset cycle. Returns 1 if a device responds
- // with a presence pulse. Returns 0 if there is no device or the
- // bus is shorted or otherwise held low for more than 250uS
- uint8_t reset(void);
-
- // Issue a 1-Wire rom select command, you do the reset first.
- void select(const uint8_t rom[8]);
-
- // Issue a 1-Wire rom skip command, to address all on bus.
- void skip(void);
-
- // Write a byte. If 'power' is one then the wire is held high at
- // the end for parasitically powered devices. You are responsible
- // for eventually depowering it by calling depower() or doing
- // another read or write.
- void write(uint8_t v, uint8_t power = 0);
-
- void write_bytes(const uint8_t *buf, uint16_t count, bool power = 0);
-
- // Read a byte.
- uint8_t read(void);
-
- void read_bytes(uint8_t *buf, uint16_t count);
-
- // Write a bit. The bus is always left powered at the end, see
- // note in write() about that.
- void write_bit(uint8_t v);
-
- // Read a bit.
- uint8_t read_bit(void);
-
- // Stop forcing power onto the bus. You only need to do this if
- // you used the 'power' flag to write() or used a write_bit() call
- // and aren't about to do another read or write. You would rather
- // not leave this powered if you don't have to, just in case
- // someone shorts your bus.
- void depower(void);
-
-#if ONEWIRE_SEARCH
- // Clear the search state so that if will start from the beginning again.
- void reset_search();
-
- // Setup the search to find the device type 'family_code' on the next call
- // to search(*newAddr) if it is present.
- void target_search(uint8_t family_code);
-
- // Look for the next device. Returns 1 if a new address has been
- // returned. A zero might mean that the bus is shorted, there are
- // no devices, or you have already retrieved all of them. It
- // might be a good idea to check the CRC to make sure you didn't
- // get garbage. The order is deterministic. You will always get
- // the same devices in the same order.
- uint8_t search(uint8_t *newAddr, bool search_mode = true);
-#endif
-
-#if ONEWIRE_CRC
- // Compute a Dallas Semiconductor 8 bit CRC, these are used in the
- // ROM and scratchpad registers.
- static uint8_t crc8(const uint8_t *addr, uint8_t len);
-
-#if ONEWIRE_CRC16
- // Compute the 1-Wire CRC16 and compare it against the received CRC.
- // Example usage (reading a DS2408):
- // // Put everything in a buffer so we can compute the CRC easily.
- // uint8_t buf[13];
- // buf[0] = 0xF0; // Read PIO Registers
- // buf[1] = 0x88; // LSB address
- // buf[2] = 0x00; // MSB address
- // WriteBytes(net, buf, 3); // Write 3 cmd bytes
- // ReadBytes(net, buf+3, 10); // Read 6 data bytes, 2 0xFF, 2 CRC16
- // if (!CheckCRC16(buf, 11, &buf[11])) {
- // // Handle error.
- // }
- //
- // @param input - Array of bytes to checksum.
- // @param len - How many bytes to use.
- // @param inverted_crc - The two CRC16 bytes in the received data.
- // This should just point into the received data,
- // *not* at a 16-bit integer.
- // @param crc - The crc starting value (optional)
- // @return True, iff the CRC matches.
- static bool check_crc16(const uint8_t* input, uint16_t len, const uint8_t* inverted_crc, uint16_t crc = 0);
-
- // Compute a Dallas Semiconductor 16 bit CRC. This is required to check
- // the integrity of data received from many 1-Wire devices. Note that the
- // CRC computed here is *not* what you'll get from the 1-Wire network,
- // for two reasons:
- // 1) The CRC is transmitted bitwise inverted.
- // 2) Depending on the endian-ness of your processor, the binary
- // representation of the two-byte return value may have a different
- // byte order than the two bytes you get from 1-Wire.
- // @param input - Array of bytes to checksum.
- // @param len - How many bytes to use.
- // @param crc - The crc starting value (optional)
- // @return The CRC16, as defined by Dallas Semiconductor.
- static uint16_t crc16(const uint8_t* input, uint16_t len, uint16_t crc = 0);
-#endif
-#endif
-};
-
-#endif
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/README.md b/resources/arduino_files/libraries/dallas-temperature-control/README.md
deleted file mode 100644
index cf14d68ae..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/README.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Arduino Library for Maxim Temperature Integrated Circuits
-
-## Usage
-
-This library supports the following devices :
-
-
-* DS18B20
-* DS18S20 - Please note there appears to be an issue with this series.
-* DS1822
-* DS1820
-* MAX31820
-
-
-You will need a pull-up resistor of about 5 KOhm between the 1-Wire data line
-and your 5V power. If you are using the DS18B20, ground pins 1 and 3. The
-centre pin is the data line '1-wire'.
-
-We have included a "REQUIRESNEW" and "REQUIRESALARMS" definition. If you
-want to slim down the code feel free to use either of these by including
-
-
-
- #define REQUIRESNEW
-
-or
-
- #define REQUIRESALARMS
-
-
-at the top of DallasTemperature.h
-
-
-## Credits
-
-The OneWire code has been derived from
-http://www.arduino.cc/playground/Learning/OneWire.
-Miles Burton originally developed this library.
-Tim Newsome added support for multiple sensors on
-the same bus.
-Guil Barros [gfbarros@bappos.com] added getTempByAddress (v3.5)
- Note: these are implemented as getTempC(address) and getTempF(address)
-Rob Tillaart [rob.tillaart@gmail.com] added async modus (v3.7.0)
-
-
-## Website
-
-
-You can find the latest version of the library at
-https://www.milesburton.com/Dallas_Temperature_Control_Library
-
-# License
-
-This library is free software; you can redistribute it and/or
-modify it under the terms of the GNU Lesser General Public
-License as published by the Free Software Foundation; either
-version 2.1 of the License, or (at your option) any later version.
-
-This library is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public
-License along with this library; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/README.panoptes b/resources/arduino_files/libraries/dallas-temperature-control/README.panoptes
deleted file mode 100644
index c4193b7d2..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/README.panoptes
+++ /dev/null
@@ -1,4 +0,0 @@
-This is commit 9cc81f0599f5832f10560939ddea4a70f44b9893
-of https://github.com/milesburton/Arduino-Temperature-Control-Library.
-Date: January 9, 2018
-
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/keywords.txt b/resources/arduino_files/libraries/dallas-temperature-control/keywords.txt
deleted file mode 100644
index 37df4679c..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/keywords.txt
+++ /dev/null
@@ -1,54 +0,0 @@
-#######################################
-# Syntax Coloring Map For DallasTemperature
-#######################################
-
-#######################################
-# Datatypes (KEYWORD1)
-#######################################
-DallasTemperature KEYWORD1
-OneWire KEYWORD1
-AlarmHandler KEYWORD1
-DeviceAddress KEYWORD1
-
-#######################################
-# Methods and Functions (KEYWORD2)
-#######################################
-
-setResolution KEYWORD2
-getResolution KEYWORD2
-getTempC KEYWORD2
-toFahrenheit KEYWORD2
-getTempF KEYWORD2
-getTempCByIndex KEYWORD2
-getTempFByIndex KEYWORD2
-setWaitForConversion KEYWORD2
-getWaitForConversion KEYWORD2
-requestTemperatures KEYWORD2
-requestTemperaturesByAddress KEYWORD2
-requestTemperaturesByIndex KEYWORD2
-isParasitePowerMode KEYWORD2
-begin KEYWORD2
-getDeviceCount KEYWORD2
-getAddress KEYWORD2
-validAddress KEYWORD2
-isConnected KEYWORD2
-readScratchPad KEYWORD2
-writeScratchPad KEYWORD2
-readPowerSupply KEYWORD2
-setHighAlarmTemp KEYWORD2
-setLowAlarmTemp KEYWORD2
-getHighAlarmTemp KEYWORD2
-getLowAlarmTemp KEYWORD2
-resetAlarmSearch KEYWORD2
-alarmSearch KEYWORD2
-hasAlarm KEYWORD2
-toCelsius KEYWORD2
-processAlarmss KEYWORD2
-setAlarmHandlers KEYWORD2
-defaultAlarmHandler KEYWORD2
-calculateTemperature KEYWORD2
-
-#######################################
-# Constants (LITERAL1)
-#######################################
-
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/library.json b/resources/arduino_files/libraries/dallas-temperature-control/library.json
deleted file mode 100644
index 4d6934f2c..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/library.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "name": "DallasTemperature",
- "keywords": "onewire, 1-wire, bus, sensor, temperature",
- "description": "Arduino Library for Dallas Temperature ICs (DS18B20, DS18S20, DS1822, DS1820)",
- "repository":
- {
- "type": "git",
- "url": "https://github.com/milesburton/Arduino-Temperature-Control-Library.git"
- },
- "authors":
- [
- {
- "name": "Miles Burton",
- "email": "miles@mnetcs.com",
- "url": "http://www.milesburton.com",
- "maintainer": true
- },
- {
- "name": "Tim Newsome",
- "email": "nuisance@casualhacker.net"
- },
- {
- "name": "Guil Barros",
- "email": "gfbarros@bappos.com"
- },
- {
- "name": "Rob Tillaart",
- "email": "rob.tillaart@gmail.com"
- }
- ],
- "dependencies":
- {
- "name": "OneWire",
- "authors": "Paul Stoffregen",
- "frameworks": "arduino"
- },
- "version": "3.7.8",
- "frameworks": "arduino",
- "platforms": "*"
-}
diff --git a/resources/arduino_files/libraries/dallas-temperature-control/library.properties b/resources/arduino_files/libraries/dallas-temperature-control/library.properties
deleted file mode 100644
index 9d0ff1eef..000000000
--- a/resources/arduino_files/libraries/dallas-temperature-control/library.properties
+++ /dev/null
@@ -1,9 +0,0 @@
-name=DallasTemperature
-version=3.7.9
-author=Miles Burton , Tim Newsome , Guil Barros , Rob Tillaart
-maintainer=Miles Burton
-sentence=Arduino Library for Dallas Temperature ICs
-paragraph=Supports DS18B20, DS18S20, DS1822, DS1820
-category=Sensors
-url=https://github.com/milesburton/Arduino-Temperature-Control-Library
-architectures=*
diff --git a/resources/arduino_files/power_board/CharBuffer.h b/resources/arduino_files/power_board/CharBuffer.h
deleted file mode 120000
index 4e1fd92c4..000000000
--- a/resources/arduino_files/power_board/CharBuffer.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/CharBuffer.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/DHT.cpp b/resources/arduino_files/power_board/DHT.cpp
deleted file mode 120000
index fec37f7c9..000000000
--- a/resources/arduino_files/power_board/DHT.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/DHT.h b/resources/arduino_files/power_board/DHT.h
deleted file mode 120000
index 024c035f2..000000000
--- a/resources/arduino_files/power_board/DHT.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/DallasTemperature.cpp b/resources/arduino_files/power_board/DallasTemperature.cpp
deleted file mode 120000
index 8157317b7..000000000
--- a/resources/arduino_files/power_board/DallasTemperature.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/DallasTemperature.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/DallasTemperature.h b/resources/arduino_files/power_board/DallasTemperature.h
deleted file mode 120000
index 859d433e1..000000000
--- a/resources/arduino_files/power_board/DallasTemperature.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/DallasTemperature.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/OneWire.cpp b/resources/arduino_files/power_board/OneWire.cpp
deleted file mode 120000
index 6d738f983..000000000
--- a/resources/arduino_files/power_board/OneWire.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/OneWire.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/OneWire.h b/resources/arduino_files/power_board/OneWire.h
deleted file mode 120000
index bba15148d..000000000
--- a/resources/arduino_files/power_board/OneWire.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/OneWire.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/PinUtils.cpp b/resources/arduino_files/power_board/PinUtils.cpp
deleted file mode 120000
index 509845a44..000000000
--- a/resources/arduino_files/power_board/PinUtils.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/PinUtils.h b/resources/arduino_files/power_board/PinUtils.h
deleted file mode 120000
index 89359569c..000000000
--- a/resources/arduino_files/power_board/PinUtils.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/dht_handler.cpp b/resources/arduino_files/power_board/dht_handler.cpp
deleted file mode 120000
index b867ff99a..000000000
--- a/resources/arduino_files/power_board/dht_handler.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/dht_handler.h b/resources/arduino_files/power_board/dht_handler.h
deleted file mode 120000
index e40a38241..000000000
--- a/resources/arduino_files/power_board/dht_handler.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.h
\ No newline at end of file
diff --git a/resources/arduino_files/power_board/power_board.ino b/resources/arduino_files/power_board/power_board.ino
deleted file mode 100644
index d704dcb0a..000000000
--- a/resources/arduino_files/power_board/power_board.ino
+++ /dev/null
@@ -1,282 +0,0 @@
-#include
-
-#include "OneWire.h"
-#include "DallasTemperature.h"
-#include "dht_handler.h"
-#include "CharBuffer.h"
-#include "PinUtils.h"
-
-#define DHTTYPE DHT22 // DHT 22 (AM2302)
-
-/* DECLARE PINS */
-
-// Digital Pins
-const int DS18_PIN = 11; // DS18B20 Temperature (OneWire)
-const int DHT_PIN = 10; // DHT Temp & Humidity Pin
-
-// Relays
-const int RELAY_0 = A3; // 0_0 PROFET-0 Channel 0 (A3 = 17)
-const int RELAY_1 = 3; // 1_0 PROFET-0 Channel 1
-const int RELAY_2 = 4; // 0_1 PROFET-1 Channel 0
-const int RELAY_3 = 7; // 1_1 PROFET-1 Channel 1
-const int RELAY_4 = 8; // 0_2 PROFET-2 Channel 0
-
-// Current Sense
-const int IS_0 = A0; // (PROFET-0 A0 = 14)
-const int IS_1 = A1; // (PROFET-1 A1 = 15)
-const int IS_2 = A2; // (PROFET-2 A2 = 16)
-
-// Channel select
-const int DSEL_0 = 2; // PROFET-0
-const int DSEL_1 = 6; // PROFET-1
-
-// Enable Sensing
-const int DEN_0 = A4; // PROFET-0 (A4 = 18)
-const int DEN_1 = 5; // PROFET-1
-const int DEN_2 = 9; // PROFET-2
-
-const int relayArray[] = {RELAY_0, RELAY_1, RELAY_2, RELAY_3, RELAY_4};
-
-const int NUM_DS18 = 3; // Number of DS18B20 Sensors
-
-uint8_t sensors_address[NUM_DS18][8];
-
-// Temperature chip I/O
-OneWire ds(DS18_PIN);
-DallasTemperature sensors(&ds);
-
-// Setup DHT22
-DHTHandler dht_handler(DHT_PIN, DHTTYPE);
-
-int led_value = LOW;
-
-void setup() {
- Serial.begin(9600);
- Serial.flush();
-
- pinMode(LED_BUILTIN, OUTPUT);
-
- sensors.begin();
-
- // Setup sense pins
- pinMode(IS_0, INPUT);
- pinMode(IS_1, INPUT);
- pinMode(IS_2, INPUT);
-
- // Setup diagnosis enable pins
- pinMode(DEN_0, OUTPUT);
- pinMode(DEN_1, OUTPUT);
- pinMode(DEN_2, OUTPUT);
-
- // Setup relay pins
- pinMode(RELAY_0, OUTPUT);
- pinMode(RELAY_1, OUTPUT);
- pinMode(RELAY_2, OUTPUT);
- pinMode(RELAY_3, OUTPUT);
- pinMode(RELAY_4, OUTPUT);
-
- // Turn on everything to start
- // Setup relay pins
- digitalWrite(RELAY_0, HIGH);
- digitalWrite(RELAY_1, HIGH);
- digitalWrite(RELAY_2, HIGH);
- digitalWrite(RELAY_3, HIGH);
- digitalWrite(RELAY_4, HIGH);
-
- dht_handler.Init();
-
- //ENABLE DIAGNOSIS AND SELECT CHANNEL
- digitalWrite(DEN_0, HIGH); // DEN_0 goes HIGH so Diagnosis enabled for PROFET0
- digitalWrite(DEN_1, HIGH); // DEN_1 goes HIGH so Diagnosis enabled for PROFET1
- digitalWrite(DEN_2, HIGH); // DEN_2 goes HIGH so Diagnosis enabled for PROFET2
-
- digitalWrite(DSEL_0, LOW); // DSEL_0 LOW reads PROFET 0_0. DSEL_0 HIGH reades PROFET 0_1
- digitalWrite(DSEL_1, LOW); // DSEL_1 LOW reads PROFET 1_0. DSEL_1 HIGH reades PROFET 1_1
-}
-
-// Accumulates a line, parses it and takes the requested action if it is valid.
-class SerialInputHandler {
- public:
- void Handle() {
- while (Serial && Serial.available() > 0) {
- int c = Serial.read();
- if (wait_for_new_line_) {
- if (IsNewLine(c)) {
- wait_for_new_line_ = false;
- input_buffer_.Reset();
- }
- } else if (IsNewLine(c)) {
- ProcessInputBuffer();
- wait_for_new_line_ = false;
- input_buffer_.Reset();
- } else if (isprint(c)) {
- if (!input_buffer_.Append(static_cast(c))) {
- wait_for_new_line_ = true;
- }
- } else {
- // Input is not an acceptable character.
- wait_for_new_line_ = true;
- }
- }
- }
-
- private:
- // Allow the input line to end with NL, CR NL or CR.
- bool IsNewLine(int c) {
- return c == '\n' || c == '\r';
- }
-
- void ProcessInputBuffer() {
- uint8_t relay_index, new_state;
- if (input_buffer_.ParseUInt8(&relay_index) &&
- input_buffer_.MatchAndConsume(',') &&
- input_buffer_.ParseUInt8(&new_state) &&
- input_buffer_.Empty()) {
-
- int pin_num = relayArray[relay_index];
- switch (new_state) {
- case 0:
- turn_pin_off(pin_num);
- break;
- case 1:
- turn_pin_on(pin_num);
- break;
- case 9:
- toggle_pin(pin_num);
- break;
- }
- }
- }
-
- CharBuffer<8> input_buffer_;
- bool wait_for_new_line_{false};
-} serial_input_handler;
-
-void loop() {
-
- // Read any serial input
- // - Input will be two comma separated integers, the
- // first specifying the relayArray index and the second
- // the new desired state.
- // Example serial input:
- // 0,1 # Turn relay index 0 on (pin RELAY_0)
- // 0,2 # Turn relay index 0 off
- // 0,3 # Toggle relay index 0
- // 0,4 # Toggle relay index 0 w/ 30 sec delay
-
- serial_input_handler.Handle();
-
- delay(250);
-
- get_readings();
-
- // Simple heartbeat
- toggle_led();
- delay(250);
-}
-
-void get_readings() {
- float voltages[5];
- float temps[4];
- float humidity[1];
-
- read_voltages(voltages);
- read_dht_temp(temps, humidity);
- read_ds18b20_temp(temps);
-
- Serial.print("{");
-
- Serial.print("\"currents\":[");
- Serial.print(voltages[0], 3); Serial.print(',');
- Serial.print(voltages[1], 3); Serial.print(',');
- Serial.print(voltages[2], 3); Serial.print(',');
- Serial.print(voltages[3], 3); Serial.print(',');
- Serial.print(voltages[4], 3);
- Serial.print("],");
-
- Serial.print("\"temps\":[");
- Serial.print(temps[0], 2); Serial.print(',');
- Serial.print(temps[1], 2); Serial.print(',');
- Serial.print(temps[2], 2); Serial.print(',');
- Serial.print(temps[3], 2);
- Serial.print("],");
-
- Serial.print(" \"humidity\":"); Serial.print(humidity[0]); Serial.print(',');
-
- Serial.print("\"name\":\"power_board\"");
-
- Serial.println("}");
-}
-
-/* Read Voltages
-
-Gets the AC probe as well as the values of the current on the AC I_ pins
-
-https://www.arduino.cc/en/Reference/AnalogRead
-
- */
-void read_voltages(float voltages[]) {
-
- // Enable channels 0_0 and 1_0
- digitalWrite(DSEL_0, LOW);
- digitalWrite(DSEL_1, LOW);
-
- delay(500);
-
- float Diag0=analogRead(IS_0);
- float Diag1=analogRead(IS_1);
-
-
- // Enabled channels 0_1 and 1_1
- digitalWrite(DSEL_0, HIGH);
- digitalWrite(DSEL_1, HIGH);
-
- delay(500);
-
- float Diag3=analogRead(IS_0);
- float Diag4=analogRead(IS_1);
-
- float Diag2=analogRead(IS_2);
-
- float Iload0 = Diag0*5/1023*2360/1200; //conversion factor to compute Iload from sensed voltage
- float Iload1 = Diag1*5/1023*2360/1200;
- float Iload2 = Diag2*5/1023*2360/1200;
- float Iload3 = Diag3*5/1023*2360/1200;
- float Iload4 = Diag4*5/1023*2360/1200;
-
- voltages[0] = Iload0;
- voltages[1] = Iload3;
- voltages[2] = Iload1;
- voltages[3] = Iload4;
- voltages[4] = Iload2;
-}
-
-// Reading temperature or humidity takes about 250 milliseconds!
-// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
-void read_dht_temp(float temps[], float humidity[]) {
- dht_handler.Collect();
-
- humidity[0] = dht_handler.humidity();
- temps[0] = dht_handler.temperature();
-}
-
-void read_ds18b20_temp(float temps[]) {
-
- sensors.requestTemperatures();
-
- for (int x = 0; x < NUM_DS18; x++) {
- // Store in x+1 because DHT11 stores in index 0
- temps[x+1] = sensors.getTempCByIndex(x);
- }
-}
-
-
-/************************************
-* Utility Methods
-*************************************/
-
-void toggle_pin_delay(int pin_num) {
- turn_pin_off(pin_num);
- delay(1000 * 30);
- turn_pin_on(pin_num);
-}
diff --git a/resources/arduino_files/shared/CharBuffer.h b/resources/arduino_files/shared/CharBuffer.h
deleted file mode 100644
index c2709bf7b..000000000
--- a/resources/arduino_files/shared/CharBuffer.h
+++ /dev/null
@@ -1,90 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_CHAR_BUFFER_H
-#define RESOURCES_ARDUINO_FILES_SHARED_CHAR_BUFFER_H
-
-// CharBuffer stores characters and supports (minimal) parsing of
-// the buffered characters.
-template
-class CharBuffer {
- public:
- CharBuffer() {
- Reset();
- }
- void Reset() {
- write_cursor_ = read_cursor_ = 0;
- }
- // Appends a character to the buffer if there is room.
- // Returns true if there is room, else returns false.
- bool Append(char c) {
- if (write_cursor_ < kBufferSize) {
- buf_[write_cursor_++] = c;
- return true;
- }
- return false;
- }
- bool Empty() {
- return read_cursor_ >= write_cursor_;
- }
- char Next() {
- return buf_[read_cursor_++];
- }
- char Peek() {
- return buf_[read_cursor_];
- }
- // Parses the integer (uint8_t) in buffer starting at read_cursor_.
- // The integer must be non-negative (leading + or - are not supported).
- // Returns true if successful, false if there is not an integer at
- // read_cursor_ or if the integer is too big to fit into *output.
- bool ParseUInt8(uint8_t* output) {
- uint16_t v = 0;
- uint8_t len = 0;
- while (!Empty() && isdigit(Peek())) {
- char c = Next();
- v = v * 10 + c - '0';
- ++len;
- if (len > 3) {
- return false;
- }
- }
- if (len == 0 || v > 255) {
- return false;
- }
- *output = static_cast(v);
- return true;
- }
- bool ParseName(char** name, uint8_t* name_len) {
- if (Empty() || !islower(Peek())) {
- return false;
- }
- *name = buf_ + read_cursor_;
- Next();
- uint8_t len = 1;
- while (!Empty()) {
- char c = Peek();
- if (islower(c) || isdigit(c) || c == '_') {
- Next();
- len++;
- continue;
- }
- break;
- }
- *name_len = len;
- return true;
- }
- bool MatchAndConsume(char c) {
- if (Empty() || Peek() != c) {
- return false;
- }
- Next();
- return true;
- }
- void WriteBuffer() {
- Serial.write(buf_, write_cursor_);
- }
-
- private:
- char buf_[kBufferSize];
- uint8_t write_cursor_;
- uint8_t read_cursor_;
-};
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_CHAR_BUFFER_H
diff --git a/resources/arduino_files/shared/PinUtils.cpp b/resources/arduino_files/shared/PinUtils.cpp
deleted file mode 100644
index b13bd8baf..000000000
--- a/resources/arduino_files/shared/PinUtils.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-#include "PinUtils.h"
-
-#include
-
-#include "Arduino.h"
-
-void turn_pin_on(int pin_num) {
- digitalWrite(pin_num, HIGH);
-}
-
-void turn_pin_off(int pin_num) {
- digitalWrite(pin_num, LOW);
-}
-
-bool is_pin_on(int pin_num) {
- return digitalRead(pin_num) != LOW;
-}
-
-void toggle_pin(int pin_num) {
- digitalWrite(pin_num, !digitalRead(pin_num));
-}
-
-void toggle_led() {
- toggle_pin(LED_BUILTIN);
-}
-
-// pinMode copied from https://github.com/arduino/Arduino/issues/4606.
-int pinMode(int pin) {
- if (pin >= NUM_DIGITAL_PINS) return (-1);
-
- uint8_t bit = digitalPinToBitMask(pin);
- uint8_t port = digitalPinToPort(pin);
- volatile uint8_t *reg = portModeRegister(port);
- if (*reg & bit) return (OUTPUT);
-
- volatile uint8_t *out = portOutputRegister(port);
- return ((*out & bit) ? INPUT_PULLUP : INPUT);
-}
diff --git a/resources/arduino_files/shared/PinUtils.h b/resources/arduino_files/shared/PinUtils.h
deleted file mode 100644
index 338fb9598..000000000
--- a/resources/arduino_files/shared/PinUtils.h
+++ /dev/null
@@ -1,15 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_PIN_UTILS_H
-#define RESOURCES_ARDUINO_FILES_SHARED_PIN_UTILS_H
-
-// Utility Methods
-
-void turn_pin_on(int pin_num);
-void turn_pin_off(int pin_num);
-bool is_pin_on(int pin_num);
-void toggle_pin(int pin_num);
-void toggle_led();
-
-// Returns the mode (INPUT, OUTPUT, or INPUT_PULLUP) for a pin.
-int pinMode(int pin);
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_PIN_UTILS_H
\ No newline at end of file
diff --git a/resources/arduino_files/shared/dallas_temperature_handler.h b/resources/arduino_files/shared/dallas_temperature_handler.h
deleted file mode 100644
index eb841faa7..000000000
--- a/resources/arduino_files/shared/dallas_temperature_handler.h
+++ /dev/null
@@ -1,115 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_DALLAS_TEMPERATURE_HANLDER_H
-#define RESOURCES_ARDUINO_FILES_SHARED_DALLAS_TEMPERATURE_HANLDER_H
-
-#include "OneWire.h"
-#include "DallasTemperature.h"
-
-struct DallasTemperatureDeviceInfo {
- bool Init(DallasTemperature* dt, uint8_t device_num) {
- if (!dt->getAddress(address, device_num)) {
- return false;
- }
- index = device_num;
- resolution = dt->getResolution(address);
- return true;
- }
- void PrintInfo() {
- Serial.print("{\"ndx\":");
- Serial.print(index);
- Serial.print(", \"address\":\"");
- for (int j = 0; j < sizeof address; ++j) {
- if (j != 0) {
- Serial.print(" ");
- }
- Serial.print(static_cast(address[j]), HEX);
- }
- Serial.print("\", \"resolution\":");
- Serial.print(static_cast(resolution));
- Serial.print("}");
- }
- bool operator>(const DallasTemperatureDeviceInfo& rhs) const {
- return memcmp(address, rhs.address, sizeof address) < 0;
- }
-
- DeviceAddress address;
- float temperature;
- uint8_t index;
- uint8_t resolution;
-};
-
-// DallasTemperatureHandler collects temp values from Dallas One-Wire temp sensors.
-template
-class DallasTemperatureHandler {
- public:
- DallasTemperatureHandler(OneWire* one_wire) : dt_(one_wire), device_count_(0) {}
-
- void Init() {
- dt_.begin();
- dt_.setWaitForConversion(true);
-
- uint8_t devices = dt_.getDeviceCount();
- for (uint8_t device_num = 0; device_num < devices; ++device_num) {
- if (devices_[device_count_].Init(&dt_, device_num)) {
- ++device_count_;
- if (device_count_ >= kMaxSensors) {
- break;
- }
- }
- }
- // Sort the devices so we get a consistent order from run to run...
- // ... assuming the devices haven't been swapped, in which case we
- // need to redetermine their addresses.
- // Arduino libraries don't include sort(), so adapted insertion sort
- // code in the ArduinoSort library on github for just this purpose.
- for (uint8_t i = 1; i < device_count_; i++) {
- for (uint8_t j = i; j > 0 && devices_[j - 1] > devices_[j]; j--) {
- auto tmp = devices_[j - 1];
- devices_[j - 1] = devices_[j];
- devices_[j] = tmp;
- }
- }
- }
- void Collect() {
- // Ask all of the sensors to start a temperature "conversion"; I think
- // this means the analog-to-digital conversion, which stores the result
- // in a register in the sensor. requestTemperatures() will return when
- // the conversion is complete.
- dt_.requestTemperatures();
- for (uint8_t i = 0; i < device_count_; i++) {
- auto& device = devices_[i];
- device.temperature = dt_.getTempC(device.address);
- }
- }
- void Report() {
- if (device_count_ > 0) {
- // This is being added to a JSON dictionary, so print a comma
- // before the quoted name, which is then followed by a colon.
- Serial.print(", \"temperature\":[");
- for (uint8_t i = 0; i < device_count_; i++) {
- if (i != 0) {
- Serial.print(",");
- }
- auto& device = devices_[i];
- Serial.print(device.temperature);
- }
- Serial.print("]");
- }
- }
- void PrintDeviceInfo() {
- Serial.print(", \"temp_devices\":[");
- for (uint8_t i = 0; i < device_count_; i++) {
- if (i != 0) {
- Serial.print(", ");
- }
- devices_[i].PrintInfo();
- }
- Serial.print("]");
- }
-
- private:
- DallasTemperature dt_;
- DallasTemperatureDeviceInfo devices_[kMaxSensors];
- uint8_t device_count_;
-};
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_DALLAS_TEMPERATURE_HANLDER_H
diff --git a/resources/arduino_files/shared/dht_handler.cpp b/resources/arduino_files/shared/dht_handler.cpp
deleted file mode 100644
index 279474f6e..000000000
--- a/resources/arduino_files/shared/dht_handler.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#include "dht_handler.h"
-
-DHTHandler::DHTHandler(uint8_t pin, uint8_t type)
- : dht_(pin, type), humidity_(-1000), temperature_(-1000) {}
-
-void DHTHandler::Init() {
- dht_.begin();
-}
-
-void DHTHandler::Collect() {
- // Force readHumidity to actually talk to the device;
- // otherwise will read at most every 2 seconds, which
- // is sometimes just a little too far apart.
- // Note that the underlying read() routine has some big
- // delays (250ms and 40ms, plus some microsecond scale delays).
- humidity_ = dht_.readHumidity(/*force=*/true);
- // readTemperature will use the data collected by
- // readHumidity, which is just fine.
- temperature_ = dht_.readTemperature();
-}
-
-void DHTHandler::Report() {
- // This is being added to a JSON dictionary, so print a comma
- // before the quoted name, which is then followed by a colon.
- Serial.print(", \"humidity\":");
- Serial.print(humidity_);
- Serial.print(", \"temp_00\":");
- Serial.print(temperature_);
-}
diff --git a/resources/arduino_files/shared/dht_handler.h b/resources/arduino_files/shared/dht_handler.h
deleted file mode 100644
index 15a6537fb..000000000
--- a/resources/arduino_files/shared/dht_handler.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_DHT_HANDLER_H
-#define RESOURCES_ARDUINO_FILES_SHARED_DHT_HANDLER_H
-
-#include
-
-#include "DHT.h"
-
-// Reads and reports Humidity & Temperature values.
-class DHTHandler {
- public:
- DHTHandler(uint8_t pin, uint8_t type);
-
- // Initialize communication to the sensor.
- void Init();
-
- // Read the current values from the sensor.
- void Collect();
-
- // Print the values. Requires that Collect has been called.
- void Report();
-
- float humidity() const { return humidity_; }
- float temperature() const { return temperature_; }
-
- private:
- DHT dht_;
- float humidity_;
- float temperature_; // Celcius
-};
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_DHT_HANDLER_H
\ No newline at end of file
diff --git a/resources/arduino_files/shared/interval_timer.cpp b/resources/arduino_files/shared/interval_timer.cpp
deleted file mode 100644
index 0e6bb53d6..000000000
--- a/resources/arduino_files/shared/interval_timer.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "interval_timer.h"
-
-#include "Arduino.h"
-
-IntervalTimer::IntervalTimer(millis_t interval_ms)
- : IntervalTimer(interval_ms, interval_ms) {}
-
-IntervalTimer::IntervalTimer(millis_t interval_ms, millis_t remaining_ms)
- : last_time_(millis()), remaining_(remaining_ms), interval_(interval_ms) {}
-
-void IntervalTimer::Reset() {
- last_time_ = millis();
- remaining_ = interval_;
-}
-
-bool IntervalTimer::HasExpired() {
- millis_t now = millis();
- millis_t elapsed;
- if (now < last_time_) {
- // We've had wrap around of the millisecond clock. When we do so, we keep things simple by
- // NOT tracking the time up to the wrap around (i.e. static_cast(-1LL) - last_time_).
- elapsed = now;
- } else {
- elapsed = now - last_time_;
- }
- last_time_ = now;
-
- if (remaining_ <= elapsed) {
- // All done. Compute amount of time in next interval that has been consumed.
- elapsed -= remaining_;
- if (elapsed >= interval_) {
- remaining_ = 1;
- } else {
- remaining_ = interval_ - elapsed;
- }
- return true;
- } else {
- remaining_ -= elapsed;
- return false;
- }
-}
diff --git a/resources/arduino_files/shared/interval_timer.h b/resources/arduino_files/shared/interval_timer.h
deleted file mode 100644
index 53e531b59..000000000
--- a/resources/arduino_files/shared/interval_timer.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_INTERVAL_TIMER_H
-#define RESOURCES_ARDUINO_FILES_SHARED_INTERVAL_TIMER_H
-
-// A simple repeating, count-down timer. Avoids problems with wrap-around
-// by tracking the remaining time left rather than the absolute time.
-class IntervalTimer {
- public:
- // The type returned by millis().
- typedef unsigned long millis_t;
-
- // Constructs a timer where the first expiration is in interval_ms, and
- // then every interval_ms after that.
- IntervalTimer(millis_t interval_ms);
-
- // Constructs a timer where the first expiration is in remaining_ms, and
- // then every interval_ms after that.
- IntervalTimer(millis_t interval_ms, millis_t remaining_ms);
-
- // Starts a new interval at the current time.
- void Reset();
-
- // Returns true if the current interval has expired, in which case a new interval is started.
- bool HasExpired();
-
- private:
- millis_t last_time_;
- millis_t remaining_;
- const millis_t interval_;
-};
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_INTERVAL_TIMER_H
diff --git a/resources/arduino_files/shared/serial_input_handler.h b/resources/arduino_files/shared/serial_input_handler.h
deleted file mode 100644
index 1aca0bfa9..000000000
--- a/resources/arduino_files/shared/serial_input_handler.h
+++ /dev/null
@@ -1,116 +0,0 @@
-#ifndef RESOURCES_ARDUINO_FILES_SHARED_SERIAL_INPUT_HANDLER_H
-#define RESOURCES_ARDUINO_FILES_SHARED_SERIAL_INPUT_HANDLER_H
-
-#include
-
-// Support for accumulating a line of text input to be parsed by the sub-class.
-// Only characters satisfying IsNewLine() or isprint() are acceptable.
-template
-class SerialInputHandler {
- public:
- typedef void (*ProcessNumCommaNumFn)(uint8_t pin_num, uint8_t value);
- typedef void (*ProcessNameEqNumFn)(char* name, uint8_t name_len, uint8_t value);
- SerialInputHandler(ProcessNumCommaNumFn num_num_fn, ProcessNameEqNumFn name_num_fn)
- : num_num_fn_(num_num_fn), name_num_fn_(name_num_fn) {}
-
- void Handle() {
- while (AccumulateLine()) {
- wait_for_new_line_ = false;
- has_line_ = false;
- uint8_t pin_num;
- if (input_buffer_.ParseUInt8(&pin_num)) {
- uint8_t pin_status;
- if (input_buffer_.MatchAndConsume(',') &&
- input_buffer_.ParseUInt8(&pin_status) &&
- input_buffer_.Empty()) {
- num_num_fn_(pin_num, pin_status);
- } else {
- LineNotMatched(1);
- }
- input_buffer_.Reset();
- continue;
- }
- char* name = nullptr;
- uint8_t name_len = 0;
- if (input_buffer_.ParseName(&name, &name_len)) {
- uint8_t pin_status;
- if (input_buffer_.MatchAndConsume('=') &&
- input_buffer_.ParseUInt8(&pin_status) &&
- input_buffer_.Empty()) {
- name_num_fn_(name, name_len, pin_status);
- } else {
- LineNotMatched(2);
- }
- input_buffer_.Reset();
- continue;
- }
- LineNotMatched(0);
- input_buffer_.Reset();
- }
- }
-
- protected:
- bool AccumulateLine() {
- if (has_line_) {
- return true;
- }
- while (Serial && Serial.available() > 0) {
- int c = Serial.read();
- if (wait_for_new_line_) {
- if (IsNewLine(c)) {
- wait_for_new_line_ = false;
- input_buffer_.Reset();
- }
- } else if (IsNewLine(c)) {
- if (!input_buffer_.Empty()) {
- has_line_ = true;
- return true;
- }
- } else if (isblank(c)) {
- // Ignore space and tabs.
- } else if (isprint(c)) {
- if (!input_buffer_.Append(static_cast(c))) {
- // Too full.
- wait_for_new_line_ = true;
- }
- } else {
- // Input is not an acceptable character.
- if (input_buffer_.Empty()) {
- // Ignore unacceptable characters at the start of a line. Choosing to do this because
- // we sometimes see garbage when first connecting.
- } else {
- wait_for_new_line_ = true;
- }
- }
- }
- return false;
- }
-
- // Allow the input line to end with NL, CR NL or CR.
- bool IsNewLine(int c) {
- return c == '\n' || c == '\r';
- }
-
- void LineNotMatched(int reason) {
- Serial.print("LINE NOT MATCHED, reason=");
- Serial.println(reason);
- Serial.print("LINE: \"");
- input_buffer_.WriteBuffer();
- Serial.println("\"");
- }
-
- // Buffer in which we're accumulating
- CharBuffer input_buffer_;
-
- const ProcessNumCommaNumFn num_num_fn_;
- const ProcessNameEqNumFn name_num_fn_;
-
- // Has a line been accumulated but not yet processed?
- bool has_line_{false};
-
- // Has invalid input been received, and we're now waiting for a new line before restarting
- // the process of accumulating a line?
- bool wait_for_new_line_{false};
-};
-
-#endif // RESOURCES_ARDUINO_FILES_SHARED_SERIAL_INPUT_HANDLER_H
diff --git a/resources/arduino_files/telemetry_board/CharBuffer.h b/resources/arduino_files/telemetry_board/CharBuffer.h
deleted file mode 120000
index 4e1fd92c4..000000000
--- a/resources/arduino_files/telemetry_board/CharBuffer.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/CharBuffer.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/DHT.cpp b/resources/arduino_files/telemetry_board/DHT.cpp
deleted file mode 120000
index fec37f7c9..000000000
--- a/resources/arduino_files/telemetry_board/DHT.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/DHT.h b/resources/arduino_files/telemetry_board/DHT.h
deleted file mode 120000
index 024c035f2..000000000
--- a/resources/arduino_files/telemetry_board/DHT.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/DHT/DHT.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/DallasTemperature.cpp b/resources/arduino_files/telemetry_board/DallasTemperature.cpp
deleted file mode 120000
index 8157317b7..000000000
--- a/resources/arduino_files/telemetry_board/DallasTemperature.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/DallasTemperature.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/DallasTemperature.h b/resources/arduino_files/telemetry_board/DallasTemperature.h
deleted file mode 120000
index 859d433e1..000000000
--- a/resources/arduino_files/telemetry_board/DallasTemperature.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/DallasTemperature.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/OneWire.cpp b/resources/arduino_files/telemetry_board/OneWire.cpp
deleted file mode 120000
index 6d738f983..000000000
--- a/resources/arduino_files/telemetry_board/OneWire.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/OneWire.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/OneWire.h b/resources/arduino_files/telemetry_board/OneWire.h
deleted file mode 120000
index bba15148d..000000000
--- a/resources/arduino_files/telemetry_board/OneWire.h
+++ /dev/null
@@ -1 +0,0 @@
-../libraries/dallas-temperature-control/OneWire.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/PinUtils.cpp b/resources/arduino_files/telemetry_board/PinUtils.cpp
deleted file mode 120000
index 509845a44..000000000
--- a/resources/arduino_files/telemetry_board/PinUtils.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/PinUtils.h b/resources/arduino_files/telemetry_board/PinUtils.h
deleted file mode 120000
index 89359569c..000000000
--- a/resources/arduino_files/telemetry_board/PinUtils.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/PinUtils.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/dallas_temperature_handler.h b/resources/arduino_files/telemetry_board/dallas_temperature_handler.h
deleted file mode 120000
index 2aff5b077..000000000
--- a/resources/arduino_files/telemetry_board/dallas_temperature_handler.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dallas_temperature_handler.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/dht_handler.cpp b/resources/arduino_files/telemetry_board/dht_handler.cpp
deleted file mode 120000
index b867ff99a..000000000
--- a/resources/arduino_files/telemetry_board/dht_handler.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/dht_handler.h b/resources/arduino_files/telemetry_board/dht_handler.h
deleted file mode 120000
index e40a38241..000000000
--- a/resources/arduino_files/telemetry_board/dht_handler.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/dht_handler.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/interval_timer.cpp b/resources/arduino_files/telemetry_board/interval_timer.cpp
deleted file mode 120000
index 0bad4db6b..000000000
--- a/resources/arduino_files/telemetry_board/interval_timer.cpp
+++ /dev/null
@@ -1 +0,0 @@
-../shared/interval_timer.cpp
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/interval_timer.h b/resources/arduino_files/telemetry_board/interval_timer.h
deleted file mode 120000
index bc277a8dc..000000000
--- a/resources/arduino_files/telemetry_board/interval_timer.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/interval_timer.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/serial_input_handler.h b/resources/arduino_files/telemetry_board/serial_input_handler.h
deleted file mode 120000
index 015760e79..000000000
--- a/resources/arduino_files/telemetry_board/serial_input_handler.h
+++ /dev/null
@@ -1 +0,0 @@
-../shared/serial_input_handler.h
\ No newline at end of file
diff --git a/resources/arduino_files/telemetry_board/telemetry_board.ino b/resources/arduino_files/telemetry_board/telemetry_board.ino
deleted file mode 100644
index 40a575a63..000000000
--- a/resources/arduino_files/telemetry_board/telemetry_board.ino
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- The PANOPTES Baseline Unit (January 2017) telemetry board controls 5 relays on
- the V0 power board, and reads 4 current sensors, 3 temperature sensors and one
- combined humidity & temperature sensor. Each pass through loop() it checks for
- serial input regarding switching the relays, and every 2 seconds it prints
- out the values of the sensors and the settings of the relays.
-
- This program has a class for each type of sensor, with a common API:
- 1) An Init() method that is called from setup.
- 2) A Collect() method that is called during the sensor reading phase.
- 3) A Report() method that is called to print out json data.
-
- The serial input should be in this format:
- A,B
- Where A and B are two positive integers. The can be an ASCII
- new line or carriage return.
- The first integer (A) specifies the digital output pin that controls the relay,
- and the second (B) specifies the output value for that pin (0 or 1).
- See "Digital output pins" below for a list of the relay pins.
- If any other characters appear in the serial input, all serial input is ignored
- until a new line or carriage return is received.
-*/
-
-#include
-#include
-
-#include "OneWire.h"
-#include "dallas_temperature_handler.h"
-#include "dht_handler.h"
-#include "CharBuffer.h"
-#include "PinUtils.h"
-#include "interval_timer.h"
-#include "serial_input_handler.h"
-
-////////////////////////////////////////////////
-// __ __ _ //
-// \ \ / / (_) //
-// \ \ / /___ _ __ ___ _ ___ _ __ //
-// \ \/ // _ \| '__|/ __|| | / _ \ | '_ \ //
-// \ /| __/| | \__ \| || (_) || | | | //
-// \/ \___||_| |___/|_| \___/ |_| |_| //
-// //
-////////////////////////////////////////////////
-
-// Please update the version identifier when you
-// make changes to this code. The value needs to
-// be in JSON format (i.e. quoted and escaped if
-// a string).
-#define JSON_VERSION_ID "\"2018-01-14\""
-
-// How often, in milliseconds, to emit a report.
-#define REPORT_INTERVAL_MS 2000
-
-// How long, in milliseconds, to shutoff the computer when
-// requested to power-cycle the computer.
-#define POWER_CYCLE_MS 30000
-
-// Type of Digital Humidity and Temperature (DHT) Sensor
-#define DHTTYPE DHT22 // DHT 22 (AM2302)
-
-// Analog input pins to which the current sensors are attached.
-const int I_MAIN = A1; // 12V input to power board.
-const int I_FAN = A2; // Output to fan (at most a few hundred mA)
-const int I_MOUNT = A3; // Output to mount (up to ~1A)
-const int I_CAMERAS = A4; // Output to cameras (up to ~1.2A)
-
-// Scaling factors for the current sensor values. Given the use
-// of the Sparkfun current sensor, which has a potentiometer on
-// it, these need to be modified for each individual board. Switching
-// to a fixed scale sensor, such as on the Infineon board, will
-// remove this need. Another choice would be to output the raw analog
-// values, and leave it up to a later system to scale or normalize
-// the values.
-const float main_amps_mult = 2.8;
-const float fan_amps_mult = 1.8;
-const float mount_amps_mult = 1.8;
-const float cameras_amps_mult = 1.0;
-
-// Digital input pins
-const int AC_PIN = 11; // Is there any AC input?
-const int DS18_PIN = 10; // DS18B20 Temperature (OneWire)
-const int DHT_PIN = 9; // DHT Temp & Humidity Pin
-
-// Digital output pins
-const int COMP_RELAY = 8; // Computer Relay: change with caution; it runs the show.
-const int CAMERAS_RELAY = 7; // Cameras Relay Off: 70s Both On: 800s One On: 350
-const int FAN_RELAY = 6; // Fan Relay Off: 0 On: 80s
-const int WEATHER_RELAY = 5; // Weather Relay 250mA upon init and 250mA to read
-const int MOUNT_RELAY = 4; // Mount Relay
-
-OneWire ds(DS18_PIN);
-
-//////////////////////////////////////////////////////////////////////////////
-// Input Handlers: the support collecting the values of various sensors/pins,
-// and then reporting it later.
-
-// DHT22: Relative Humidity & Temperature Sensor.
-DHTHandler dht_handler(DHT_PIN, DHTTYPE);
-
-// There are 3 DS18B20 sensors in the Jan 2017 Telemetry Board design.
-DallasTemperatureHandler<3> dt_handler(&ds);
-
-// Base class of handlers below which emit a different name for each
-// instance of a sub-class.
-class BaseNameHandler {
- public:
- // Print quoted name for JSON dictionary key. The decision of
- // whether to add a comma before this is made by the caller.
- void PrintName() {
- Serial.print('"');
- Serial.print(name_);
- Serial.print("\":");
- }
-
- // Returns true if name_ is a string of length len and equals the
- // string starting at *s.
- bool NameEquals(const char* s, uint8_t len) {
- const char* p = name_;
- while (len > 0 && *p != '\0') {
- if (*s != *p) {
- return false;
- }
- --len;
- ++s;
- ++p;
- }
- return len == 0 && *p == '\0';
- }
-
- protected:
- BaseNameHandler(const char* name) : name_(name) {}
-
- private:
- const char* const name_;
-};
-
-class CurrentHandler : public BaseNameHandler {
- public:
- CurrentHandler(const char* name, int pin, float scale)
- : BaseNameHandler(name), pin_(pin), scale_(scale) {}
- void Collect() {
- reading_ = analogRead(pin_);
- amps_ = reading_ * scale_;
- }
- void ReportReading() {
- PrintName();
- Serial.print(reading_);
- }
- void ReportAmps() {
- PrintName();
- Serial.print(amps_);
- }
-
- private:
- const int pin_;
- const float scale_;
-
- int reading_;
- float amps_;
-};
-
-// One CurrentHandler instance for each of the current sensors.
-CurrentHandler current_handlers[] = {
- {"main", I_MAIN, main_amps_mult},
- {"fan", I_FAN, fan_amps_mult},
- {"mount", I_MOUNT, mount_amps_mult},
- {"cameras", I_CAMERAS, cameras_amps_mult},
-};
-
-class DigitalPinHandler : public BaseNameHandler {
- public:
- DigitalPinHandler(const char* name, int pin)
- : BaseNameHandler(name), pin_(pin) {}
- void Collect() {
- reading_ = digitalRead(pin_);
- }
- void Report() {
- PrintName();
- Serial.print(reading_);
- }
- int pin() const {
- return pin_;
- }
-
- private:
- const int pin_;
- int reading_;
-};
-
-// One DigitalPinHandler for each of the GPIO pins for which we report the value.
-// Most are actually output pins, but the Arduino API allows us to read the value
-// that is being output.
-DigitalPinHandler dp_handlers[] = {
- {"computer", COMP_RELAY},
- {"fan", FAN_RELAY},
- {"mount", MOUNT_RELAY},
- {"cameras", CAMERAS_RELAY},
- {"weather", WEATHER_RELAY},
- {"main", AC_PIN},
-};
-
-/////////////////////////////////////////////////////////////////////////////////////////
-// General reporting code.
-
-// Due to limitations of the Arduino preprocessor, we must place the following all on one line:
-template void ReportCollection(const char* name, T(&handlers)[size]) {
- // This is being added to a JSON dictionary, so print a comma
- // before the quoted name, which is then followed by a colon.
- Serial.print(", \"");
- Serial.print(name);
- Serial.print("\": {");
- bool first = true;
- for (auto& handler : handlers) {
- if (first) {
- first = false;
- } else {
- Serial.print(", ");
- }
- handler.Report();
- }
- Serial.print('}');
-}
-
-// Due to limitations of the Arduino preprocessor, we must place the following all on one line:
-template void PrintCollection(const char* name, T(&handlers)[size], void (T::*mf)()) {
- // This is being added to a JSON dictionary, so print a comma
- // before the quoted name, which is then followed by a colon.
- Serial.print(", \"");
- Serial.print(name);
- Serial.print("\": {");
- bool first = true;
- for (auto& handler : handlers) {
- if (first) {
- first = false;
- } else {
- Serial.print(",");
- }
- (handler.*mf)();
- }
- Serial.print('}');
-}
-
-// Produce a single JSON line with the current values reported by
-// the sensors and the settings of the relays.
-void Report(unsigned long now) {
- static uint32_t report_num = 0;
-
- // Collect values from all of the sensors & replays.
- dht_handler.Collect();
- dt_handler.Collect();
- for (auto& handler : dp_handlers) {
- handler.Collect();
- }
- for (auto& handler : current_handlers) {
- handler.Collect();
- }
-
- // Print the collected values as JSON.
- Serial.print("{\"name\":\"telemetry_board\", \"millis\":");
- Serial.print(millis());
- Serial.print(", \"report_num\":");
- Serial.print(++report_num);
- Serial.print(", \"ver\":");
- Serial.print(JSON_VERSION_ID);
-
- ReportCollection("power", dp_handlers);
- PrintCollection("current", current_handlers, &CurrentHandler::ReportReading);
- PrintCollection("amps", current_handlers, &CurrentHandler::ReportAmps);
- dht_handler.Report();
- dt_handler.Report();
-
- Serial.println("}");
-}
-
-//////////////////////////////////////////////////////////////////////////////////
-// Serial input support
-
-bool restarting_computer = false;
-IntervalTimer restart_computer_timer(POWER_CYCLE_MS);
-
-// Handle an input from the computer or user.
-// Changes the specified pin based on pin_status.
-void HandleNumNum(uint8_t pin_num, uint8_t pin_status) {
- switch (pin_num) {
- case COMP_RELAY:
- /* The computer shutting itself off:
- - Power down
- - Wait 30 seconds
- - Power up
- */
- if (pin_status == 0) {
- if (restarting_computer) {
- Serial.println("ALREADY restarting_computer!!");
- return;
- }
- Serial.print("Turning off the computer relay for .");
- Serial.print(POWER_CYCLE_MS);
- Serial.println("ms.");
- turn_pin_off(COMP_RELAY);
- restart_computer_timer.Reset();
- restarting_computer = true;
- return;
- }
- break;
- case CAMERAS_RELAY:
- case FAN_RELAY:
- case WEATHER_RELAY:
- case MOUNT_RELAY:
- if (pin_status == 1) {
- turn_pin_on(pin_num);
- return;
- } else if (pin_status == 0) {
- turn_pin_off(pin_num);
- return;
- } else if (pin_status == 9) {
- toggle_pin(pin_num);
- return;
- }
- }
- Serial.print("NO MATCH FOUND FOR pin_num=");
- Serial.print(static_cast(pin_num));
- Serial.print(" pin_status=");
- Serial.println(static_cast(pin_status));
-}
-
-// Handle an input from the computer or user.
-// Changes the pin specified by name based on pin_status.
-void HandleNameNum(char* name, uint8_t name_len, uint8_t pin_status) {
- for (auto& handler : dp_handlers) {
- if (handler.NameEquals(name, name_len)) {
- HandleNumNum(handler.pin(), pin_status);
- return;
- }
- }
- Serial.print("NO MATCH FOUND FOR name=\"");
- Serial.write(name, name_len);
- Serial.print("\" pin_status=");
- Serial.println(static_cast(pin_status));
-}
-
-SerialInputHandler<16> serial_input_handler(HandleNumNum, HandleNameNum);
-
-//////////////////////////////////////////////////////////////////////////////////
-// Primary Arduino defined methods: setup(), called once at start, and loop(),
-// called repeatedly (roughly as soon as it returns).
-
-void setup() {
- Serial.begin(9600);
- Serial.flush();
-
- pinMode(LED_BUILTIN, OUTPUT);
-
- // Setup relay pins.
- pinMode(COMP_RELAY, OUTPUT);
- pinMode(CAMERAS_RELAY, OUTPUT);
- pinMode(FAN_RELAY, OUTPUT);
- pinMode(WEATHER_RELAY, OUTPUT);
- pinMode(MOUNT_RELAY, OUTPUT);
-
- // Turn relays on to start
- digitalWrite(COMP_RELAY, HIGH);
- digitalWrite(CAMERAS_RELAY, HIGH);
- digitalWrite(FAN_RELAY, HIGH);
- digitalWrite(WEATHER_RELAY, HIGH);
- digitalWrite(MOUNT_RELAY, HIGH);
-
- // Setup communications with the sensors.
- dht_handler.Init();
- dt_handler.Init();
-
- pinMode(AC_PIN, INPUT);
-}
-
-void loop() {
- if (restarting_computer) {
- if (restart_computer_timer.HasExpired()) {
- restarting_computer = false;
- turn_pin_on(COMP_RELAY);
- Serial.println("Turned on the computer relay.");
- }
- } else {
- serial_input_handler.Handle();
- }
-
- // Every REPORT_INTERVAL_MS we want to produce a report on sensor values,
- // and relay settings.
- static IntervalTimer report_timer(REPORT_INTERVAL_MS);
- if (report_timer.HasExpired()) {
- digitalWrite(LED_BUILTIN, HIGH);
- Report(millis());
- digitalWrite(LED_BUILTIN, LOW);
- } else if (!Serial) {
- // Do a rapid blink of the LED if there is apparently no serial
- // line connection. Note that we still call the serial input
- // handler and print reports, just in case !Serial is wrong.
- static IntervalTimer fast_blink_timer(100);
- if (fast_blink_timer.HasExpired()) {
- toggle_led();
- }
- }
-}
diff --git a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v100.pdf b/resources/manuals/AAGCloudWatcher_Rs232_Comms_v100.pdf
deleted file mode 100644
index ca8c67dca..000000000
Binary files a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v100.pdf and /dev/null differ
diff --git a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v110.pdf b/resources/manuals/AAGCloudWatcher_Rs232_Comms_v110.pdf
deleted file mode 100644
index b5532e2c5..000000000
Binary files a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v110.pdf and /dev/null differ
diff --git a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v120.pdf b/resources/manuals/AAGCloudWatcher_Rs232_Comms_v120.pdf
deleted file mode 100644
index 591596dd9..000000000
Binary files a/resources/manuals/AAGCloudWatcher_Rs232_Comms_v120.pdf and /dev/null differ
diff --git a/resources/manuals/RainSensorHeaterAlgorithm.pdf b/resources/manuals/RainSensorHeaterAlgorithm.pdf
deleted file mode 100644
index 1f1db2c1c..000000000
Binary files a/resources/manuals/RainSensorHeaterAlgorithm.pdf and /dev/null differ
diff --git a/resources/manuals/iOptron Mount RS-232 Command language 2014.pdf b/resources/manuals/iOptron Mount RS-232 Command language 2014.pdf
deleted file mode 100644
index 7e17efc1b..000000000
Binary files a/resources/manuals/iOptron Mount RS-232 Command language 2014.pdf and /dev/null differ
diff --git a/resources/rpi/network-config b/resources/rpi/network-config
new file mode 100644
index 000000000..0ea269e41
--- /dev/null
+++ b/resources/rpi/network-config
@@ -0,0 +1,23 @@
+# This file contains a netplan-compatible configuration which cloud-init
+# will apply on first-boot. Please refer to the cloud-init documentation and
+# the netplan reference for full details:
+#
+# https://cloudinit.readthedocs.io/
+# https://netplan.io/reference
+
+version: 2
+ethernets:
+ eth0:
+ dhcp4: true
+ optional: true
+#
+# DON'T RELY ON WIFI FOR INSTALL
+#
+# Update ssid and password to match.
+#
+wifis:
+ wlan0:
+ dhcp4: true
+ access-points:
+ "panoptes-net":
+ password: "net-panoptes"
diff --git a/resources/rpi/user-data b/resources/rpi/user-data
new file mode 100644
index 000000000..354af3a06
--- /dev/null
+++ b/resources/rpi/user-data
@@ -0,0 +1,94 @@
+#cloud-config
+# https://cloudinit.readthedocs.io/
+
+hostname: pocs-control
+
+# If you have set up ssh on github you can pull down your
+# key automatically so that you can log into the unit without
+# a password.
+ssh_import_id:
+ # - gh:your_github_id
+ - gh:panoptes
+
+#####################################################################
+# You shouldn't need to change anything below.
+#####################################################################
+
+ntp:
+ enabled: true
+ servers:
+ - time1.google.com
+ - time2.google.com
+ - time3.google.com
+ - time4.google.com
+
+# Setting "expire: true" will force a password change on first login.
+chpasswd:
+ expire: true
+ list:
+ - panoptes:panoptes
+
+ssh_pwauth: yes
+
+# New groups to create.
+groups:
+ - panoptes
+ - docker
+
+users:
+ - name: panoptes
+ gecos: PANOPTES User
+ primary_group: panoptes
+ groups: users, admin, dialout, plugdev, docker, i2c, input, gpio, panoptes
+ sudo: "ALL=(ALL) NOPASSWD:ALL"
+ lock_passwd: false
+ shell: /bin/zsh
+
+## Update apt database and upgrade packages on first boot
+package_update: true
+package_upgrade: true
+
+byobu: enable
+
+## Install additional packages on first boot.
+packages:
+ - apt-transport-https
+ - byobu
+ - ca-certificates
+ - git
+ - htop
+ - httpie
+ - jq
+ - neovim
+ - software-properties-common
+ - speedometer
+ - vim-nox
+ - watchdog
+ - zsh
+
+write_files:
+ # Allow panoptes user to mount via sshfs.
+ - content: |
+ user_allow_other
+ path: /etc/fuse.conf
+ append: true
+
+## Get and run the install script upon first boot.
+runcmd:
+ # Setup hardware watchdog
+# - echo 'interface = eth0' >> /etc/watchdog.conf
+# - echo 'interface = wlan0' >> /etc/watchdog.conf
+ - echo 'watchdog-device = /dev/watchdog' >> /etc/watchdog.conf
+ - echo 'watchdog-timeout = 15' >> /etc/watchdog.conf
+ - echo 'max-load-1 = 24' >> /etc/watchdog.conf
+ # Get the install file.
+ - mkdir -p /var/panoptes/scripts
+ - chown -R panoptes:panoptes /var/panoptes
+ - wget https://install.projectpanoptes.org -O /var/panoptes/scripts/install-pocs.sh
+ - bash /var/panoptes/scripts/install-pocs.sh
+
+power_state:
+ mode: reboot
+ condition: True
+
+final_message: Welcome to the PANOPTES Observatory Control System!
diff --git a/resources/rpi/usercfg.txt b/resources/rpi/usercfg.txt
new file mode 100644
index 000000000..69def8d2a
--- /dev/null
+++ b/resources/rpi/usercfg.txt
@@ -0,0 +1,34 @@
+# Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in
+# this file. Please refer to the README file for a description of the various
+# configuration files on the boot partition.
+#
+# https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README
+# PANOPTES POCS Pi customisation for Raspberry Pi 4B+
+
+dtoverlay=disable-bt
+dtoverlay=i2c-gpio
+
+dtparam=audio=off
+
+dtparam=i2c=on
+dtparam=spi=on
+dtparam=watchdog=on
+
+# Disable HDMI to save power
+# hdmi_blanking=2
+
+# Disable WiFi to save power
+# dtoverlay=disable-wifi
+
+# Disable ethernet port LEDs
+dtparam=eth_led0=4
+dtparam=eth_led1=4
+
+# Disable activity LED
+dtoverlay=act-led
+dtparam=act_led_trigger=none
+dtparam=act_led_activelow=off
+
+# Disable power LED
+dtparam=pwr_led_trigger=none
+dtparam=pwr_led_activelow=off
diff --git a/resources/state_table/simple_state_table_01.yaml b/resources/state_table/simple_state_table_01.yaml
deleted file mode 100644
index 619f1c8f6..000000000
--- a/resources/state_table/simple_state_table_01.yaml
+++ /dev/null
@@ -1,78 +0,0 @@
----
-initial: parked
-states:
- -
- name: sleeping
- children:
- - cleanup
- - shutdown
- - initialize
- - parked
- -
- name: working
- children:
- - scheduling
- - slewing
- - tracking
- - observing
- - analyzing
-transitions:
- -
- source: parked
- dest: sleeping
- trigger: sleep
- -
- source: cleanup
- dest: shutdown
- trigger: sleep
- -
- source: shutdown
- dest: initialize
- trigger: get_ready
- -
- source: initialize
- dest: parked
- trigger: wakeup
- conditions: initialize
- -
- source: parked
- dest: scheduling
- trigger: schedule
- -
- source: scheduling
- dest: visiting
- trigger: visit
- conditions: has_target
- -
- source: scheduling
- dest: parked
- trigger: park
- -
- source: slewing
- dest: tracking
- trigger: track
- conditions:
- - mount_is_tracking
- - has_visits
- -
- source: tracking
- dest: observing
- trigger: observe
- conditions:
- - mount_is_tracking
- - has_exposures
- -
- source: observing
- dest: analyzing
- trigger: analyze
- -
- source: analyzing
- dest: visiting_tracking
- trigger: correct_tracking
- conditions:
- - mount_is_tracking
- - has_visits
- -
- source: visiting_analyzing
- dest: scheduling
- trigger: schedule
\ No newline at end of file
diff --git a/resources/state_table/simple_state_table_02.yaml b/resources/state_table/simple_state_table_02.yaml
deleted file mode 100644
index 0b7570a35..000000000
--- a/resources/state_table/simple_state_table_02.yaml
+++ /dev/null
@@ -1,77 +0,0 @@
----
-initial: parked
-states:
- - parked
- - parking
- - shutdown
- - sleeping
- - ready
- - scheduling
- -
- name: visiting
- children:
- - slewing
- - tracking
- - observing
- - analyzing
-transitions:
- -
- source: parked
- dest: shutdown
- trigger: shutdown
- -
- source: shutdown
- dest: sleeping
- trigger: sleep
- -
- source: 'parking'
- dest: parked
- trigger: set_park
- -
- source: parked
- dest: ready
- trigger: get_ready
- conditions: initialize
- -
- source: ready
- dest: scheduling
- trigger: schedule
- -
- source: scheduling
- dest: visiting
- trigger: visit
- conditions: has_target
- -
- source: scheduling
- dest: parking
- trigger: park
- conditions: has_target
- -
- source: visiting_slewing
- dest: visiting_tracking
- trigger: track
- conditions:
- - mount_is_tracking
- - has_visits
- -
- source: visiting_tracking
- dest: visiting_observing
- trigger: observe
- conditions:
- - mount_is_tracking
- - has_exposures
- -
- source: visiting_observing
- dest: visiting_analyzing
- trigger: analyze
- -
- source: visiting_analyzing
- dest: visiting_tracking
- trigger: correct_tracking
- conditions:
- - mount_is_tracking
- - has_visits
- -
- source: visiting_analyzing
- dest: scheduling
- trigger: schedule
\ No newline at end of file
diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh
new file mode 100644
index 000000000..1dd9a39b8
--- /dev/null
+++ b/scripts/entrypoint.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -e
+
+# Pass arguments
+exec gosu pocs-user /usr/bin/env bash -ic "$@"
diff --git a/scripts/install/install-pocs.sh b/scripts/install/install-pocs.sh
index 7bb1e4636..4068d3748 100644
--- a/scripts/install/install-pocs.sh
+++ b/scripts/install/install-pocs.sh
@@ -5,10 +5,11 @@ usage() {
echo -n "##################################################
# Install POCS and friends.
#
-# Script Version: 2020-07-27
+# Script Version: 2021-01-18
#
# This script is designed to install the PANOPTES Observatory
-# Control System (POCS) on a cleanly installed Ubuntu system.
+# Control System (POCS) on a cleanly installed Ubuntu system
+# (ideally on a Raspberry Pi).
#
# This script is meant for quick & easy install via:
#
@@ -20,29 +21,20 @@ usage() {
#
# The script will do the following:
#
-# * Create the needed directory structure.
-# * Ensure that docker and docker-compose are installed.
-# * Fetch and/or build the docker images needed to run.
-# * If in "developer" mode, clone user's fork and set panoptes upstream.
-# * Write the environment variables to ${PANDIR}/env
+# * Create
+# * Create the needed directory structure for POCS.
+# * Install docker and tools on the host computer.
+# * Fetch the docker images needed to run.
+# * Source ${PANDIR}/env if it exists.
#
# Docker Images:
#
-# ${DOCKER_BASE}/panoptes-utils
-# ${DOCKER_BASE}/pocs
+# ${DOCKER_BASE}/panoptes-pocs:latest
+# ${DOCKER_BASE}/aag-weather:latest
#
-# The script will ask if it should be installed in "developer" mode or not.
+# The regular install is for running units.
#
-# The regular install is for running units and will not create local (to the
-# host system) copies of the files.
-#
-# The "developer" mode will ask for a github username and will clone and
-# fetch the repos. The $(docker/setup-local-enviornment.sh) script will then
-# be run to build the docker images locally.
-#
-# If not in "developer" mode, the docker images will be pulled from GCR.
-#
-# The script has been tested with a fresh install of Ubuntu 20.04
+# The script has been tested with a fresh install of Ubuntu Server 20.10
# but may work on other linux systems.
#
# Changes:
@@ -52,14 +44,14 @@ usage() {
# * 2020-07-09 (wtgee) - Fix conditional for writing shell rc files. Use 3rd
# party docker-compose (linuxserver.io) for arm.
# * 2020-07-27 (wtgee) - Cleanup and consistency for Unit install.
+# * 2020-11-08 (wtgee) - Add zsh, anaconda. Docker from apt.
+# * 2021-01-18 (wtgee) - Simplify to only install minimal required on host,
+# removing zsh, etc. Removed Darwin options.
#
#############################################################
- $ $(basename $0) [--developer] [--user panoptes] [--pandir /var/panoptes]
+ $ $(basename $0) [--user panoptes] [--pandir /var/panoptes]
Options:
- DEVELOPER Install POCS in developer mode, default False.
-
- If in DEVELOPER mode, the following options are also available:
USER The PANUSER environment variable, defaults to current user (i.e. PANUSER=$USER).
PANDIR Default install directory, defaults to PANDIR=${PANDIR}. Saved as PANDIR
environment variable.
@@ -69,98 +61,14 @@ usage() {
# Better select prompt.
PS3="Select: "
-DEVELOPER=${DEVELOPER:-false}
+# TODO should be checking to matching userid=1000
PANUSER=${PANUSER:-$USER}
PANDIR=${PANDIR:-/var/panoptes}
LOGFILE="${PANDIR}/install-pocs.log"
OS="$(uname -s)"
-ARCH="$(uname -m)"
-ENV_FILE="${PANDIR}/env"
-
-GITHUB_USER="panoptes"
-GITHUB_URL="https://github.com/${GITHUB_USER}"
-PANOPTES_UPSTREAM_URL="https://github.com/panoptes"
-
-# Repositories to clone.
-REPOS=("POCS" "panoptes-utils" "panoptes-tutorials")
-
-DOCKER_COMPOSE_INSTALL="https://raw.githubusercontent.com/linuxserver/docker-docker-compose/master/run.sh"
DOCKER_BASE=${DOCKER_BASE:-"gcr.io/panoptes-exp"}
-while [[ $# -gt 0 ]]; do
- key="$1"
- case ${key} in
- --developer)
- DEVELOPER=true
- shift # past bool argument
- ;;
- --install-unit)
- DEVELOPER=false
- shift # past bool argument
- ;;
- -u | --user)
- PANUSER="$2"
- shift # past argument
- shift # past value
- ;;
- -d | --pandir)
- PANDIR="$2"
- shift # past argument
- shift # past value
- ;;
- -h | --help)
- PANDIR="$2"
- usage
- return
- ;;
- esac
-done
-
-if ! ${DEVELOPER}; then
- echo "How would you like to install the unit?"
- select mode in "Developer" "PANOPTES Unit"; do
- case ${mode} in
- Developer)
- echo "Enabling developer mode. Note that you will need your GitHub username to proceed."
- DEVELOPER=true
- break
- ;;
- "PANOPTES Unit")
- echo "Installing POCS for a PANOPTES unit."
- break
- ;;
- esac
- done
-fi
-
-if "${DEVELOPER}"; then
- echo "To install POCS as a developer make sure you have first forked the following repositories:"
- echo ""
- echo " https://github.com/panoptes/POCS"
- echo " https://github.com/panoptes/panoptes-utils"
- echo " https://github.com/panoptes/panoptes-tutorials"
- echo ""
- echo "You will also need to have an ssh key set up on github.com."
- echo "See https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh"
-
- read -p "Github User [panoptes]: " GITHUB_USER
-
- # If a different user, make sure we can access github as that user, otherwise exit.
- if test "${GITHUB_USER}" != "panoptes"; then
- echo "Testing github ssh access for user: ${GITHUB_USER}"
-
- # Test for ssh access
- if [[ $(ssh -T git@github.com 2>&1) =~ "success" ]]; then
- GITHUB_URL="git@github.com:${GITHUB_USER}"
- else
- echo "Can't ssh to github.com. Have you set up your ssh keys?"
- echo "See https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh"
- return
- fi
- fi
-fi
-
function command_exists() {
# https://gist.github.com/gubatron/1eb077a1c5fcf510e8e5
# this should be a very portable way of checking if something is on the path
@@ -169,144 +77,58 @@ function command_exists() {
}
function make_directories() {
- if [[ ! -d "${PANDIR}" ]]; then
- # Make directories and make PANUSER the owner.
- sudo mkdir -p "${PANDIR}"
- else
- echo "Would you like to continue with the existing directory?"
- select yn in "Yes" "No"; do
- case ${yn} in
- Yes)
- echo "Proceeding with existing directory"
- break
- ;;
- No)
- echo "Exiting script"
- return
- ;;
- esac
- done
- fi
-
sudo mkdir -p "${PANDIR}/logs"
sudo mkdir -p "${PANDIR}/images"
- sudo mkdir -p "${PANDIR}/config_files"
- sudo mkdir -p "${PANDIR}/.key"
sudo chown -R "${PANUSER}":"${PANUSER}" "${PANDIR}"
}
-function setup_env_vars() {
- if [[ ! -f "${ENV_FILE}" ]]; then
- echo "Writing environment variables to ${ENV_FILE}"
- cat >>"${ENV_FILE}" <>"${SHELL_RC_PATH}"
- fi
- fi
- done
- fi
-}
-
function system_deps() {
- if [[ "${OS}" == "Linux" ]]; then
- sudo apt-get update >>"${LOGFILE}" 2>&1
- sudo apt-get --yes install \
- wget curl git openssh-server ack jq httpie byobu \
- >>"${LOGFILE}" 2>&1
- elif [[ "${OS}" == "Darwin" ]]; then
- sudo brew update | sudo tee -a "${LOGFILE}"
- sudo brew install \
- wget curl git jq httpie |
- sudo tee -a "${LOGFILE}"
- fi
+ sudo apt-get update | sudo tee -a "${LOGFILE}" 2>&1
+ sudo apt-get --yes install \
+ ack \
+ byobu \
+ docker.io \
+ docker-compose \
+ git \
+ htop \
+ httpie \
+ jq \
+ openssh-server \
+ speedometer \
+ vim-nox \
+ wget | sudo tee -a "${LOGFILE}" 2>&1
# Add an SSH key if one doesn't exist.
if [[ ! -f "${HOME}/.ssh/id_rsa" ]]; then
echo "Adding ssh key"
ssh-keygen -t rsa -N "" -f "${HOME}/.ssh/id_rsa"
fi
-}
-
-function get_repos() {
- echo "Cloning ${REPOS}"
- for repo in "${REPOS[@]}"; do
- if [[ ! -d "${PANDIR}/${repo}" ]]; then
- cd "${PANDIR}"
- echo "Cloning ${GITHUB_URL}/${repo}"
- # Just redirect the errors because otherwise looks like it hangs.
- git clone --single-branch --quiet "${GITHUB_URL}/${repo}.git" &>>"${LOGFILE}"
- # Set panoptes as upstream if clone succeeded.
- if [ $? -eq 0 ]; then
- cd "${repo}"
- git remote add upstream "${PANOPTES_UPSTREAM_URL}/${repo}"
- fi
- else
- echo "${repo} already exists in ${PANDIR}. No auto-update for now, skipping repo."
- fi
- done
-}
+ # Add to docker group if not already.
+ sudo usermod -aG docker "${PANUSER}" | sudo tee -a "${LOGFILE}" 2>&1
-function get_docker() {
- if ! command_exists docker; then
- echo "Installing Docker"
- if [[ "${OS}" == "Linux" ]]; then
- /bin/bash -c "$(wget -qO- https://get.docker.com)" &>>"${LOGFILE}"
-
- echo "Adding ${PANUSER} to docker group"
- sudo usermod -aG docker "${PANUSER}" >>"${LOGFILE}" 2>&1
- elif [[ "${OS}" == "Darwin" ]]; then
- brew cask install docker
- echo "Adding ${PANUSER} to docker group"
- sudo dscl -aG docker "${PANUSER}"
- fi
- fi
+ # Source the environment variables if available.
+ cat <>"/home/${PANUSER}/.bashrc"
+export LANG="en_US.UTF-8"
- if ! command_exists docker-compose; then
- echo "Installing docker-compose"
- sudo wget -q "${DOCKER_COMPOSE_INSTALL}" -O /usr/local/bin/docker-compose
- sudo chmod a+x /usr/local/bin/docker-compose
- fi
+# Load POCS env file if it exists
+if test -f "${PANDIR}/POCS/env"; then
+ source "${PANDIR}/POCS/env"
+fi
+EOF
}
function get_or_build_images() {
- if ${DEVELOPER}; then
- echo "Building local PANOPTES docker images."
+ echo "Pulling POCS docker images from Google Cloud Registry (GCR)."
- cd "${PANDIR}/POCS"
- ./docker/setup-local-environment.sh
- else
- echo "Pulling PANOPTES docker images from Google Cloud Registry (GCR)."
-
- sudo docker pull "${DOCKER_BASE}/panoptes-pocs:latest"
- sudo docker pull "${DOCKER_BASE}/panoptes-utils:latest"
- sudo docker pull "${DOCKER_BASE}/aag-weather:latest"
- fi
+ sudo docker pull "${DOCKER_BASE}/panoptes-pocs:latest"
+ sudo docker pull "${DOCKER_BASE}/aag-weather:latest"
}
function do_install() {
clear
- echo "Installing PANOPTES software."
- if ${DEVELOPER}; then
- echo "**** Developer Mode ****"
- echo "GITHUB_USER=${GITHUB_USER}"
- fi
+ echo "Installing POCS software."
echo "PANUSER: ${PANUSER}"
echo "PANDIR: ${PANDIR}"
echo "OS: ${OS}"
@@ -315,20 +137,9 @@ function do_install() {
echo "Creating directories in ${PANDIR}"
make_directories
- echo "Setting up environment variables in ${ENV_FILE}"
- setup_env_vars
-
echo "Installing system dependencies"
system_deps
- echo "Installing docker and docker-compose"
- get_docker
-
- if ${DEVELOPER}; then
- echo "Cloning PANOPTES source code"
- get_repos
- fi
-
get_or_build_images
echo "Please reboot your machine before using POCS."
diff --git a/scripts/list-arduinos.py b/scripts/list-arduinos.py
deleted file mode 100755
index 0ac6a7833..000000000
--- a/scripts/list-arduinos.py
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env python3
-
-import sys
-
-from panoptes.pocs.sensors import arduino_io
-from panoptes.utils import rs232
-
-
-# Support testing by just listing the available devices.
-if __name__ == '__main__':
- port_infos = rs232.get_serial_port_info()
- if port_infos:
- fmt = '{:20s} {:30s} {}'
- print(fmt.format('Device', 'Manufacturer', 'Description'))
- for pi in port_infos:
- print(fmt.format(pi.device, pi.manufacturer, pi.description))
- print()
- devices = arduino_io.get_arduino_ports()
- if devices:
- print("Arduino devices: {}".format(", ".join(devices)))
- else:
- print("No Arduino devices found.")
- sys.exit(1)
-
- print()
- boards_and_ports = arduino_io.auto_detect_arduino_devices()
- for board, port in boards_and_ports:
- print('Found board {!r} on port {!r}'.format(board, port))
- sys.exit(0)
diff --git a/scripts/pocs-shell.py b/scripts/pocs-shell.py
index a6dad9345..e71881a12 100755
--- a/scripts/pocs-shell.py
+++ b/scripts/pocs-shell.py
@@ -1,24 +1,21 @@
#!/usr/bin/env python3
import readline
import time
-
from cmd import Cmd
from pprint import pprint
from astropy.utils import console
-
from panoptes.pocs import hardware
+from panoptes.pocs.camera import create_cameras_from_config
from panoptes.pocs.core import POCS
+from panoptes.pocs.mount import create_mount_from_config
from panoptes.pocs.observatory import Observatory
-from panoptes.utils import current_time
-from panoptes.utils import string_to_params
+from panoptes.pocs.scheduler import create_scheduler_from_config
from panoptes.utils import error
from panoptes.utils import images as img_utils
from panoptes.utils.config import client
-
-from panoptes.pocs.mount import create_mount_from_config
-from panoptes.pocs.camera import create_cameras_from_config
-from panoptes.pocs.scheduler import create_scheduler_from_config
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import string_to_params
class PocsShell(Cmd):
diff --git a/scripts/reset-usb-device.py b/scripts/reset-usb-device.py
deleted file mode 100755
index 2832179ea..000000000
--- a/scripts/reset-usb-device.py
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/usr/bin/env python
-
-# This is copied from: https://unix.stackexchange.com/a/412223
-# It has not yet been adapted in any particular way for PANOPTES
-# (i.e. no connection with our config files, no logging).
-# We may want to add a setuid variant that supports resetting
-# a named /dev/tty* device IFF it is a USB device, and IFF that
-# device can't be successfully used (i.e. open and read(0) fails).
-
-import os
-import sys
-from subprocess import Popen, PIPE
-import fcntl
-
-instructions = '''
-Usage: python reset_usb.py help : Show this help
- sudo python reset_usb.py list : List all USB devices
- sudo python reset_usb.py path /dev/bus/usb/XXX/YYY : Reset USB device using path /dev/bus/usb/XXX/YYY
- sudo python reset_usb.py search "search terms" : Search for USB device using the search terms within the search string returned by list and reset matching device
- sudo python reset_usb.py listpci : List all PCI USB devices
- sudo python reset_usb.py pathpci /sys/bus/pci/drivers/.../XXXX:XX:XX.X : Reset PCI USB device using path
- sudo python reset_usb.py searchpci "search terms" : Search for PCI USB device using the search terms within the search string returned by listpci and reset matching device
- '''
-
-
-if len(sys.argv) < 2:
- print(instructions)
- sys.exit(0)
-
-option = sys.argv[1].lower()
-if 'help' in option:
- print(instructions)
- sys.exit(0)
-
-
-def create_pci_list():
- pci_usb_list = list()
- try:
- lspci_out = Popen('lspci -Dvmm', shell=True, bufsize=64, stdin=PIPE, stdout=PIPE, close_fds=True).stdout.read().strip().decode('utf-8')
- pci_devices = lspci_out.split('%s%s' % (os.linesep, os.linesep))
- for pci_device in pci_devices:
- device_dict = dict()
- categories = pci_device.split(os.linesep)
- for category in categories:
- key, value = category.split('\t')
- device_dict[key[:-1]] = value.strip()
- if 'USB' not in device_dict['Class']:
- continue
- for root, dirs, files in os.walk('/sys/bus/pci/drivers/'):
- slot = device_dict['Slot']
- if slot in dirs:
- device_dict['path'] = os.path.join(root, slot)
- break
- pci_usb_list.append(device_dict)
- except Exception as ex:
- print('Failed to list pci devices! Error: %s' % ex)
- sys.exit(-1)
- return pci_usb_list
-
-
-def create_usb_list():
- device_list = list()
- try:
- lsusb_out = Popen('lsusb -v', shell=True, bufsize=64, stdin=PIPE, stdout=PIPE, close_fds=True).stdout.read().strip().decode('utf-8')
- usb_devices = lsusb_out.split('%s%s' % (os.linesep, os.linesep))
- for device_categories in usb_devices:
- if not device_categories:
- continue
- categories = device_categories.split(os.linesep)
- device_stuff = categories[0].strip().split()
- bus = device_stuff[1]
- device = device_stuff[3][:-1]
- device_dict = {'bus': bus, 'device': device}
- device_info = ' '.join(device_stuff[6:])
- device_dict['description'] = device_info
- for category in categories:
- if not category:
- continue
- categoryinfo = category.strip().split()
- if categoryinfo[0] == 'iManufacturer':
- manufacturer_info = ' '.join(categoryinfo[2:])
- device_dict['manufacturer'] = manufacturer_info
- if categoryinfo[0] == 'iProduct':
- device_info = ' '.join(categoryinfo[2:])
- device_dict['device'] = device_info
- path = '/dev/bus/usb/%s/%s' % (bus, device)
- device_dict['path'] = path
-
- device_list.append(device_dict)
- except Exception as ex:
- print('Failed to list usb devices! Error: %s' % ex)
- sys.exit(-1)
- return device_list
-
-
-if 'listpci' in option:
- pci_usb_list = create_pci_list()
- for device in pci_usb_list:
- print('path=%s' % device['path'])
- print(' manufacturer=%s' % device['SVendor'])
- print(' device=%s' % device['SDevice'])
- print(' search string=%s %s' % (device['SVendor'], device['SDevice']))
- sys.exit(0)
-
-if 'list' in option:
- usb_list = create_usb_list()
- for device in usb_list:
- print('path=%s' % device['path'])
- print(' description=%s' % device['description'])
- print(' manufacturer=%s' % device['manufacturer'])
- print(' device=%s' % device['device'])
- print(' search string=%s %s %s' % (device['description'], device['manufacturer'], device['device']))
- sys.exit(0)
-
-if len(sys.argv) < 3:
- print(instructions)
- sys.exit(0)
-
-option2 = sys.argv[2]
-
-print('Resetting device: %s' % option2)
-
-
-# echo -n "0000:39:00.0" | tee /sys/bus/pci/drivers/xhci_hcd/unbind;echo -n "0000:39:00.0" | tee /sys/bus/pci/drivers/xhci_hcd/bind
-def reset_pci_usb_device(dev_path):
- folder, slot = os.path.split(dev_path)
- try:
- fp = open(os.path.join(folder, 'unbind'), 'wt')
- fp.write(slot)
- fp.close()
- fp = open(os.path.join(folder, 'bind'), 'wt')
- fp.write(slot)
- fp.close()
- print('Successfully reset %s' % dev_path)
- sys.exit(0)
- except Exception as ex:
- print('Failed to reset device! Error: %s' % ex)
- sys.exit(-1)
-
-
-if 'pathpci' in option:
- reset_pci_usb_device(option2)
-
-
-if 'searchpci' in option:
- pci_usb_list = create_pci_list()
- for device in pci_usb_list:
- text = '%s %s' % (device['SVendor'], device['SDevice'])
- if option2 in text:
- reset_pci_usb_device(device['path'])
- print('Failed to find device!')
- sys.exit(-1)
-
-
-def reset_usb_device(dev_path):
- USBDEVFS_RESET = 21780
- try:
- f = open(dev_path, 'w', os.O_WRONLY)
- fcntl.ioctl(f, USBDEVFS_RESET, 0)
- print('Successfully reset %s' % dev_path)
- sys.exit(0)
- except Exception as ex:
- print('Failed to reset device! Error: %s' % ex)
- sys.exit(-1)
-
-
-if 'path' in option:
- reset_usb_device(option2)
-
-
-if 'search' in option:
- usb_list = create_usb_list()
- for device in usb_list:
- text = '%s %s %s' % (device['description'], device['manufacturer'], device['device'])
- if option2 in text:
- reset_usb_device(device['path'])
- print('Failed to find device!')
- sys.exit(-1)
diff --git a/scripts/setup-local-environment.sh b/scripts/setup-local-environment.sh
deleted file mode 100755
index a043c4fde..000000000
--- a/scripts/setup-local-environment.sh
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env bash
-set -e
-
-INCLUDE_BASE=${INCLUDE_BASE:-true} # INCLUDE_UTILS must be true to work.
-INCLUDE_UTILS=${INCLUDE_UTILS:-false}
-INCLUDE_DEVELOPER=${INCLUDE_DEVELOPER:-false}
-
-PANOPTES_UTILS=${PANOPTES_UTILS:-$PANDIR/panoptes-utils}
-PANOPTES_POCS=${PANOPTES_POCS:-$PANDIR/POCS}
-_UTILS_IMAGE_URL="gcr.io/panoptes-exp/panoptes-utils:latest"
-
-echo "Setting up local environment."
-cd "${PANOPTES_POCS}"
-
-build_utils() {
- /bin/bash "${PANOPTES_UTILS}/docker/setup-local-environment.sh"
- # Use our local image for build below instead of gcr.io image.
- _UTILS_IMAGE_URL="panoptes-utils:develop"
-}
-
-build_develop() {
- echo "Building local panoptes-pocs:develop from ${_UTILS_IMAGE_URL} in ${PANOPTES_POCS}"
- docker build \
- -t "panoptes-pocs:develop" \
- -f "${PANOPTES_POCS}/docker/Dockerfile" \
- "${PANOPTES_POCS}"
-}
-
-build_developer() {
- echo "Building local panoptes-pocs:developer from panoptes-pocs:develop in ${PANOPTES_POCS}"
- docker build \
- -t "panoptes-pocs:developer" \
- -f "${PANOPTES_POCS}/docker/developer/Dockerfile" \
- "${PANOPTES_POCS}"
-}
-
-####################################################################################
-# Script logic below
-####################################################################################
-
-if [ "${INCLUDE_UTILS}" = true ]; then
- build_utils
-fi
-
-build_develop
-
-if [ "${INCLUDE_DEVELOPER}" = true ]; then
- build_developer
-fi
-
-cat <=3.2a0,<3.3a0
install_requires =
astroplan
astropy
- Flask
- matplotlib
- numpy
- panoptes-utils>=0.2.26
- pyserial>=3.1.1
- PyYaml
- requests
- scalpl
- scipy
+ panoptes-utils>=0.2.30
+ pyserial
transitions
# 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.7
[options.packages.find]
where = src
@@ -65,30 +57,21 @@ exclude =
[options.extras_require]
# Add here additional requirements for extra features, to install with:
-# `pip install POCS[PDF]` like:
-developer =
- google-cloud-sdk
- google-cloud-bigquery[pandas,pyarrow]
- google-cloud-firestore
- google-cloud-storage
- jupyterlab
- pandas
- tabulate
+# `pip install POCS[google,testing]`:
+focuser =
+ matplotlib
+ scipy
google =
google-cloud-storage
-plotting =
- altair
- bokeh
- holoviews
- hvplot
- seaborn
+ gsutil
testing =
coverage
+ mocket
pycodestyle
pytest
pytest-cov
pytest-doctestplus
- pytest-remotedata>=0.3.1'
+ pytest-remotedata>=0.3.1
responses
[options.entry_points]
@@ -104,21 +87,18 @@ testing =
[test]
# py.test options when running `python setup.py test`
+extras = True
+
+[tool:pytest]
addopts =
--cov panoptes.pocs
--cov panoptes.peas
--cov-branch
--cov-report term-missing:skip-covered
+ --cov-report xml:build/coverage.xml
--no-cov-on-fail
--doctest-modules
--doctest-ignore-import-errors
- --failed-first
- -x
- --verbose
-extras = True
-
-[tool:pytest]
-addopts =
--doctest-modules
-x
-vv
@@ -136,6 +116,7 @@ filterwarnings =
ignore::pytest.PytestDeprecationWarning
doctest_plus = enabled
markers =
+ theskyx
without_camera
with_camera
without_mount
@@ -177,18 +158,6 @@ extensions =
markdown
namespace = panoptes
-[coverage:run]
-branch = True
-concurrency =
- multiprocessing
- threading
-parallel = True
-
-[coverage:paths]
-source =
- src/
- */site-packages/
-
[coverage:report]
# Regexes for lines to exclude from consideration
exclude_lines =
diff --git a/src/panoptes/peas/remote_sensors.py b/src/panoptes/peas/remote_sensors.py
index 9607597a0..0521d3830 100644
--- a/src/panoptes/peas/remote_sensors.py
+++ b/src/panoptes/peas/remote_sensors.py
@@ -1,10 +1,9 @@
import requests
-
-from panoptes.utils import current_time
+from panoptes.pocs.utils.logger import get_logger
from panoptes.utils import error
from panoptes.utils.config.client import get_config
from panoptes.utils.database import PanDB
-from panoptes.pocs.utils.logger import get_logger
+from panoptes.utils.time import current_time
class RemoteMonitor(object):
diff --git a/src/panoptes/peas/sensors.py b/src/panoptes/peas/sensors.py
deleted file mode 100644
index fe505b257..000000000
--- a/src/panoptes/peas/sensors.py
+++ /dev/null
@@ -1,206 +0,0 @@
-import sys
-
-# Note: list_comports is modified by test_sensors.py, so if changing
-# this import, the test will also need to be updated.
-from serial.tools.list_ports import comports as list_comports
-
-from panoptes.utils.config.client import get_config
-from panoptes.utils.database import PanDB
-from panoptes.utils.rs232 import SerialData
-from panoptes.utils import error
-
-from panoptes.pocs.utils.logger import get_logger
-
-
-class ArduinoSerialMonitor(object):
- """Monitors the serial lines and tries to parse any data received as JSON.
-
- Checks for the `camera_box` and `computer_box` entries in the config and tries to connect.
- Values are updated in the database.
- """
-
- def __init__(self, sensor_name=None, auto_detect=False, *args, **kwargs):
- self.logger = get_logger()
-
- # Setup the DB either from kwargs or config.
- self.db = None
- db_type = get_config('db.type', default='file')
-
- if 'db_type' in kwargs:
- self.logger.info(f"Setting up {kwargs['db_type']} type database")
- db_type = kwargs.get('db_type', db_type)
-
- self.db = PanDB(db_type=db_type)
-
- # Store each serial reader
- self.serial_readers = dict()
-
- # Don't allow sensor_name and auto_detect
- if sensor_name is not None:
- auto_detect = False
-
- if auto_detect or get_config('environment.auto_detect', default=False):
-
- self.logger.debug('Performing auto-detect')
- for (sensor_name, serial_reader) in auto_detect_arduino_devices():
- self.logger.info(f'Found name "{sensor_name}" on {serial_reader.name}')
- self.serial_readers[sensor_name] = {
- 'reader': serial_reader,
- 'port': serial_reader.name,
- }
- else:
- # Try to connect to a range of ports
- for name, sensor_config in get_config('environment', default={}).items():
- if name != sensor_name:
- continue
-
- if 'serial_port' not in sensor_config:
- continue
-
- port = sensor_config['serial_port']
- serial_reader = self._connect_serial(port)
- self.serial_readers[sensor_name] = {
- 'reader': serial_reader,
- 'port': port,
- }
-
- if len(self.serial_readers) == 0:
- raise error.BadSerialConnection
-
- def _connect_serial(self, port):
- self.logger.info(f'Attempting to connect to serial port: {port}')
- serial_reader = SerialData(port=port, baudrate=9600)
- if serial_reader.is_connected is False:
- try:
- serial_reader.connect()
- except Exception:
- self.logger.warning(f'Could not connect to port: {port}')
- return None
-
- self.logger.info(f'Connected to {port}')
- return serial_reader
-
- def disconnect(self):
- for sensor_name, reader_info in self.serial_readers.items():
- reader = reader_info['reader']
- reader.stop()
-
- def capture(self, store_result=True):
- """
- Helper function to return serial sensor info.
-
- Reads each of the connected sensors. If a value is received, attempts
- to parse the value as json.
-
- Returns:
- sensor_data (dict): Dictionary of sensors keyed by sensor name.
- """
-
- # Read from all the readers; we send messages with sensor data immediately, but accumulate
- # data from all sensors before storing in the db.
- # Note that there is no guarantee that these are the LATEST reports emitted by the sensors,
- # as the OS or PySerial object may have a backlog, and especially because we are reading
- # these in lock step; if one produces a report every 1.9 seconds, and the other every 2.1
- # seconds, then we will generally wait an extra 0.2 seconds on each loop relative to the
- # rate at which the fast one is producing output, For this reason, we really need to split
- # these into two separate threads and probably two seperate Mongo collections (e.g. not
- # 'environment' but 'camera_board' and 'telemetry_board').
- sensor_data = dict()
- for sensor_name, reader_info in self.serial_readers.items():
- reader = reader_info['reader']
-
- self.logger.debug(f'ArduinoSerialMonitor.capture reading sensor {sensor_name}')
- try:
- reading = reader.get_and_parse_reading()
- if not reading:
- self.logger.debug(f'Unable to get reading from {sensor_name}')
- continue
-
- self.logger.debug(f'{sensor_name}: {reading!r}')
-
- time_stamp, data = reading
- data['date'] = time_stamp
- sensor_data[sensor_name] = data
-
- if store_result and len(sensor_data) > 0:
- self.db.insert_current(sensor_name, data)
-
- # Make a separate power entry
- if 'power' in data:
- self.db.insert_current('power', data['power'])
-
- except Exception as e:
- self.logger.warning(f'Exception while reading from {sensor_name=}: {e!r}')
-
- return sensor_data
-
-
-def auto_detect_arduino_devices(comports=None):
- if comports is None:
- comports = find_arduino_devices()
- result = []
- for port in comports:
- v = detect_board_on_port(port)
- if v:
- result.append(v)
- return result
-
-
-def find_arduino_devices():
- """Find devices (paths or URLs) that appear to be Arduinos.
-
- Returns:
- a list of strings; device paths (e.g. /dev/ttyACM1) or URLs (e.g. rfc2217://host:port
- arduino_simulator://?board=camera).
- """
- comports = list_comports()
- return [p.device for p in comports if 'Arduino' in p.description]
-
-
-def detect_board_on_port(port):
- """Open a port and determine which type of board its producing output.
-
- Returns: (name, serial_reader) if we can read a line of JSON from the
- port, parse it and find a 'name' attribute in the top-level object.
- Else returns None.
- """
- logger = get_logger()
- logger.debug(f'Attempting to connect to serial {port=}')
- try:
- serial_reader = SerialData(port=port, baudrate=9600, retry_limit=1, retry_delay=0)
- if not serial_reader.is_connected:
- serial_reader.connect()
- logger.debug(f'Connected to {port=}')
- except Exception:
- logger.warning(f'Could not connect to {port=}')
- return None
- try:
- reading = serial_reader.get_and_parse_reading(retry_limit=3)
- if not reading:
- return None
- (ts, data) = reading
- if isinstance(data, dict) and 'name' in data and isinstance(data['name'], str):
- result = (data['name'], serial_reader)
- serial_reader = None
- return result
- logger.warning(f'Unable to find board name in {reading=!r}')
- return None
- except Exception as e:
- logger.error(f'Exception while auto-detecting port {port=!r}: {e!r}')
- finally:
- if serial_reader:
- serial_reader.disconnect()
-
-
-# Support testing by just listing the available devices.
-if __name__ == '__main__':
- devices = find_arduino_devices()
- if devices:
- print(f'Arduino devices: {",".join(devices)}')
- else:
- print("No Arduino devices found.")
- sys.exit(1)
- names_and_readers = auto_detect_arduino_devices()
- for (name, serial_reader) in names_and_readers:
- print(f'Device {serial_reader.name} has {name=}')
- serial_reader.disconnect()
diff --git a/src/panoptes/peas/tests/serial_handlers/__init__.py b/src/panoptes/peas/tests/serial_handlers/__init__.py
deleted file mode 100644
index 508100140..000000000
--- a/src/panoptes/peas/tests/serial_handlers/__init__.py
+++ /dev/null
@@ -1,9 +0,0 @@
-"""The protocol_*.py files in this package are based on PySerial's file
-test/handlers/protocol_test.py, modified for different behaviors. The call
- serial.serial_for_url("XYZ://") looks for a class Serial in a file named protocol_XYZ.py in this
-package (i.e. directory).
-
-This package init file will be loaded as part of searching for a protocol handler in this package.
-It is important to use root-relative imports (e.g. relative to the POCS directory) so that all
-modules and packages are loaded only once.
-"""
diff --git a/src/panoptes/peas/tests/test_boards.py b/src/panoptes/peas/tests/test_boards.py
deleted file mode 100644
index 621438c2c..000000000
--- a/src/panoptes/peas/tests/test_boards.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import pytest
-
-from panoptes.utils import error
-from panoptes.peas.sensors import ArduinoSerialMonitor
-
-
-@pytest.mark.with_sensors
-@pytest.fixture(scope='module')
-def monitor():
- return ArduinoSerialMonitor(auto_detect=True)
-
-
-@pytest.mark.with_sensors
-def test_create(monitor):
- assert monitor is not None
-
-
-@pytest.mark.with_sensors
-def test_has_readers(monitor):
- assert len(monitor.serial_readers) > 0
-
-
-@pytest.mark.without_sensors
-def test_bad_autodetect():
- # Will fail if no connections
- with pytest.raises(error.BadSerialConnection):
- ArduinoSerialMonitor(auto_detect=True)
-
-
-def test_bad_sensor_name():
- with pytest.raises(error.BadSerialConnection):
- ArduinoSerialMonitor(sensor_name='foobar', db_type='memory')
diff --git a/src/panoptes/peas/tests/test_sensors.py b/src/panoptes/peas/tests/test_sensors.py
index 698685667..cda972c12 100644
--- a/src/panoptes/peas/tests/test_sensors.py
+++ b/src/panoptes/peas/tests/test_sensors.py
@@ -1,145 +1,12 @@
# Test sensors.py ability to read from two sensor boards.
-import collections
import pytest
-import serial
import responses
-from panoptes.peas import sensors as sensors_module
from panoptes.peas import remote_sensors
-from panoptes.utils import rs232
from panoptes.utils import error
-SerDevInfo = collections.namedtuple('SerDevInfo', 'device description')
-
-
-@pytest.fixture(scope='function')
-def serial_handlers():
- # Install our test handlers for the duration.
- serial.protocol_handler_packages.insert(0, 'panoptes.utils.serial_handlers')
- yield True
- # Remove our test handlers.
- serial.protocol_handler_packages.remove('panoptes.utils.serial_handlers')
-
-
-def list_comports():
- return [
- SerDevInfo(device='bogus://', description='Not an arduino'),
- SerDevInfo(device='loop://', description='Some Arduino device'),
- SerDevInfo(device='arduinosimulator://?board=telemetry&name=t1',
- description='Some Arduino device'),
- SerDevInfo(device='arduinosimulator://?board=camera&name=c1', description='Arduino Micro'),
- ]
-
-
-@pytest.fixture(scope='function')
-def inject_list_comports():
- saved = sensors_module.list_comports
- sensors_module.list_comports = list_comports
- yield True
- sensors_module.list_comports = saved
-
-
-# --------------------------------------------------------------------------------------------------
-# Basic tests of FakeArduinoSerialHandler.
-
-def test_create_camera_simulator(serial_handlers):
- ser = rs232.SerialData(port='arduinosimulator://?board=camera', baudrate=9600)
- assert ser.is_connected is True
- ser.disconnect()
- assert ser.is_connected is False
- ser.connect()
- assert ser.is_connected is True
- # First read will typically get a fragment of a line, but then the next will
- # get a full line.
- s = ser.read()
- assert s.endswith('\n')
- s = ser.read()
- assert s.startswith('{')
- assert s.endswith('}\r\n')
- assert 'camera_board' in s
- ser.disconnect()
- assert ser.is_connected is False
-
-
-def test_create_default_simulator(serial_handlers):
- """Defaults to creating telemetry_board messages."""
- ser = rs232.SerialData(port='arduinosimulator://', baudrate=9600)
- assert ser.is_connected is True
- (ts, reading) = ser.get_and_parse_reading(retry_limit=2)
- assert isinstance(reading, dict)
- assert reading['name'] == 'telemetry_board'
- report_num = reading['report_num']
- assert 1 <= report_num
- assert report_num <= 2
- ser.disconnect()
- assert ser.is_connected is False
-
-
-# --------------------------------------------------------------------------------------------------
-
-def test_detect_board_on_port_not_a_board():
- """detect_board_on_port will fail if the port doesn't produce the expected output.
-
- Detection will fail because loop:// handler doesn't print anything.
- """
- assert sensors_module.detect_board_on_port('loop://') is None
-
-
-def test_detect_board_on_port_no_handler_installed():
- """Can't find our simulator, so returns None.
-
- This test doesn't have `serial_handlers` as a param, so the arduinosimulator can't be found by
- PySerial's `serial_for_url`. Therefore, detect_board_on_port won't be able to determine the
- type of board.
- """
- assert sensors_module.detect_board_on_port('arduinosimulator://?board=telemetry') is None
-
-
-def test_detect_board_on_port_telemetry(serial_handlers):
- """Detect a telemetry board."""
- v = sensors_module.detect_board_on_port('arduinosimulator://?board=telemetry')
- assert isinstance(v, tuple)
- assert v[0] == 'telemetry_board'
- assert isinstance(v[1], rs232.SerialData)
- assert v[1].is_connected is True
- v[1].disconnect()
- assert v[1].is_connected is False
-
-
-# --------------------------------------------------------------------------------------------------
-
-def test_find_arduino_devices(inject_list_comports):
- v = sensors_module.find_arduino_devices()
- assert len(v) == 3
- assert v == [
- 'loop://',
- 'arduinosimulator://?board=telemetry&name=t1',
- 'arduinosimulator://?board=camera&name=c1',
- ]
-
-
-# --------------------------------------------------------------------------------------------------
-
-def test_auto_detect_arduino_devices(inject_list_comports, serial_handlers):
- v = sensors_module.auto_detect_arduino_devices()
- assert len(v) == 2
- for ndx, (board, name) in enumerate([('telemetry', 't1'), ('camera', 'c1')]):
- print('ndx=%r board=%r name=%r' % (ndx, board, name))
- expected = 'arduinosimulator://?board=%s&name=%s' % (board, name)
- assert v[ndx][0] == '{}_board'.format(board)
- assert isinstance(v[ndx][1], rs232.SerialData)
- assert v[ndx][1].name == expected
- assert v[ndx][1].ser.port == expected
- # ser.name is set by the simulator, a way to test that the correcct
- # simulator setup is in use.
- assert v[ndx][1].ser.name == name
- assert v[ndx][1].is_connected is True
- v[ndx][1].disconnect()
- assert v[ndx][1].is_connected is False
-
-
@pytest.fixture
def remote_response():
return {
diff --git a/src/panoptes/pocs/base.py b/src/panoptes/pocs/base.py
index 84d7b6109..052121b92 100644
--- a/src/panoptes/pocs/base.py
+++ b/src/panoptes/pocs/base.py
@@ -1,10 +1,11 @@
-from requests.exceptions import ConnectionError
+import os
from panoptes.pocs import __version__
-from panoptes.utils.database import PanDB
-from panoptes.utils.config import client
-from panoptes.pocs.utils.logger import get_logger
from panoptes.pocs import hardware
+from panoptes.pocs.utils.logger import get_logger
+from panoptes.utils.config import client
+from panoptes.utils.database import PanDB
+from requests.exceptions import ConnectionError
# Global database.
PAN_DB_OBJ = None
@@ -16,20 +17,21 @@ class PanBase(object):
Defines common properties for each class (e.g. logger, config, db).
"""
- def __init__(self, config_port='6563', *args, **kwargs):
+ def __init__(self, config_host=None, config_port=None, *args, **kwargs):
self.__version__ = __version__
- self._config_port = config_port
+ self._config_host = config_host or os.getenv('PANOPTES_CONFIG_HOST', 'localhost')
+ self._config_port = config_port or os.getenv('PANOPTES_CONFIG_PORT', 6563)
self.logger = get_logger()
- # If the user requests a db_type then update runtime config
- db_type = kwargs.get('db_type', self.get_config('db.type', default='file'))
- db_name = kwargs.get('db_name', self.get_config('db.name', default='panoptes'))
- db_folder = kwargs.get('db_folder', self.get_config('db.folder', default='json_store'))
-
global PAN_DB_OBJ
if PAN_DB_OBJ is None:
+ # If the user requests a db_type then update runtime config
+ db_type = kwargs.get('db_type', self.get_config('db.type', default='file'))
+ db_name = kwargs.get('db_name', self.get_config('db.name', default='panoptes'))
+ db_folder = kwargs.get('db_folder', self.get_config('db.folder', default='json_store'))
+
PAN_DB_OBJ = PanDB(db_type=db_type, db_name=db_name, storage_dir=db_folder)
self.db = PAN_DB_OBJ
@@ -45,9 +47,12 @@ def get_config(self, *args, **kwargs):
"""
config_value = None
try:
- config_value = client.get_config(port=self._config_port, *args, **kwargs)
+ config_value = client.get_config(host=self._config_host,
+ port=self._config_port,
+ verbose=False,
+ *args, **kwargs)
except ConnectionError as e: # pragma: no cover
- self.logger.critical(f'Cannot connect to config_server from {self.__class__}: {e!r}')
+ self.logger.warning(f'Cannot connect to config_server from {self.__class__}: {e!r}')
return config_value
@@ -69,10 +74,12 @@ def set_config(self, key, new_value, *args, **kwargs):
new_value = hardware.ALL_NAMES
try:
- self.logger.trace(f'Setting config {key=} {new_value=}')
- config_value = client.set_config(key, new_value, port=self._config_port, *args,
- **kwargs)
- self.logger.trace(f'Config set {config_value=}')
+ self.logger.trace(f'Setting config key={key!r} new_value={new_value!r}')
+ config_value = client.set_config(key, new_value,
+ host=self._config_host,
+ port=self._config_port,
+ *args, **kwargs)
+ self.logger.trace(f'Config set config_value={config_value!r}')
except ConnectionError as e: # pragma: no cover
self.logger.critical(f'Cannot connect to config_server from {self.__class__}: {e!r}')
diff --git a/src/panoptes/pocs/camera/__init__.py b/src/panoptes/pocs/camera/__init__.py
index 90ecce299..62847d106 100644
--- a/src/panoptes/pocs/camera/__init__.py
+++ b/src/panoptes/pocs/camera/__init__.py
@@ -1,12 +1,12 @@
+import copy
from collections import OrderedDict
import re
import shutil
import subprocess
import random
+from contextlib import suppress
-from astropy import units as u
from panoptes.pocs.camera.camera import AbstractCamera # noqa
-from panoptes.pocs.camera.camera import AbstractGPhotoCamera # noqa
from panoptes.pocs.utils.logger import get_logger
from panoptes.utils import error
@@ -45,13 +45,27 @@ def list_connected_cameras():
return ports
-def create_cameras_from_config(*args, **kwargs):
+def create_cameras_from_config(config=None,
+ cameras=None,
+ auto_primary=True,
+ recreate_existing=False,
+ *args, **kwargs):
"""Create camera object(s) based on the config.
Creates a camera for each camera item listed in the config. Ensures the
appropriate camera module is loaded.
Args:
+ config (dict or None): A config object for a camera or None to lookup in
+ config-server.
+ cameras (list of panoptes.pocs.camera.Camera or None): A list of camera
+ objects or None.
+ auto_primary (bool): If True, when no camera is marked as the primary camera,
+ the first camera in the list will be used as primary. Default True.
+ recreate_existing (bool): If True, a camera object will be recreated if an
+ existing camera with the same `uid` is already assigned. Should currently
+ only affect cameras that use the `sdk` (i.g. not DSLRs). Default False
+ raises an exception if camera is already assigned.
*args (list): Passed to `get_config`.
**kwargs (dict): Can pass a `cameras` object that overrides the info in
the configuration file. Can also pass `auto_detect`(bool) to try and
@@ -67,26 +81,21 @@ def create_cameras_from_config(*args, **kwargs):
auto_detect=True and no cameras are found.
error.PanError: Description
"""
+ camera_config = config or get_config('cameras', *args, **kwargs)
- config = get_config(*args, **kwargs)
-
- # Helper method to first check kwargs then config
- def kwargs_or_config(item, default=None):
- return kwargs.get(item, config.get(item, default))
-
- cameras = OrderedDict()
- camera_info = kwargs_or_config('cameras')
- if not camera_info:
+ if not camera_config:
# cameras section either missing or empty
logger.info('No camera information in config.')
- return cameras
-
- logger.debug(f"Camera config: {camera_info}")
+ return None
- auto_detect = camera_info.get('auto_detect', False)
+ logger.debug(f"camera_config={camera_config!r}")
+ camera_defaults = camera_config.get('defaults', dict())
+ cameras = cameras or OrderedDict()
ports = list()
+ auto_detect = camera_defaults.get('auto_detect', False)
+
# Lookup the connected ports
if auto_detect:
logger.debug("Auto-detecting ports for cameras")
@@ -96,23 +105,18 @@ def kwargs_or_config(item, default=None):
logger.warning(e)
if len(ports) == 0:
- raise error.CameraNotFound(
- msg="No cameras detected. For testing, use camera simulator.")
+ raise error.CameraNotFound(msg="No cameras detected. For testing, use camera simulator.")
else:
- logger.debug(f"Detected Ports: {ports}")
+ logger.debug(f"Detected ports={ports!r}")
primary_camera = None
- # Different models require different connections methods.
- model_requires = {
- 'canon_gphoto2': 'port',
- 'sbig': 'serial_number',
- 'zwo': 'serial_number',
- 'fli': 'serial_number',
- }
+ device_info = camera_config['devices']
+ for cam_num, cfg in enumerate(device_info):
+ # Get a copy of the camera defaults and update with device config.
+ device_config = camera_defaults.copy()
+ device_config.update(cfg)
- device_info = camera_info['devices']
- for cam_num, device_config in enumerate(device_info):
cam_name = device_config.setdefault('name', f'Cam{cam_num:02d}')
# Check for proper connection method.
@@ -127,116 +131,49 @@ def kwargs_or_config(item, default=None):
continue
elif model == 'simulator':
device_config['port'] = f'usb:999,{random.randint(0, 1000):03d}'
- else:
- try:
- # This is either `port` or `serial_number`.
- connect_method = model_requires[model]
- connect_value = device_config[connect_method]
- device_config[connect_method] = connect_value
- except KeyError as e:
- logger.warning(f"Camera error: connect_method missing for {model}: {e!r}")
logger.debug(f'Creating camera: {model}')
try:
- module = load_module(f'panoptes.pocs.camera.{model}')
- logger.debug(f'Camera module: {module}')
- # Create the camera object
- cam = module.Camera(**device_config)
+ module = load_module(model)
+ logger.debug(f'Camera module: module={module!r}')
+
+ if recreate_existing:
+ with suppress(AttributeError):
+ module._assigned_cameras = set()
+
+ # We either got a class or a module.
+ if callable(module):
+ camera = module(**device_config)
+ else:
+ if hasattr(module, 'Camera'):
+ camera = module.Camera(**device_config)
+ else:
+ raise error.NotFound(f'module={module!r} does not have a Camera object')
except error.NotFound:
logger.error(f"Cannot find camera module with config: {device_config}")
except Exception as e:
- logger.error(f"Cannot create camera type: {device_config['model']} {e}")
+ logger.error(f"Cannot create camera type: {model} {e}")
else:
- is_primary = ''
- if camera_info.get('primary', '') == cam.uid:
- cam.is_primary = True
- primary_camera = cam
- is_primary = ' [Primary]'
+ # Check if the config specified a primary camera and if it matches.
+ if camera.uid == camera_config.get('primary'):
+ camera.is_primary = True
+ primary_camera = camera
- logger.debug(f"Camera created: {cam.name} {cam.uid}{is_primary}")
+ logger.debug(f"Camera created: camera={camera!r}")
- cameras[cam_name] = cam
+ cameras[cam_name] = camera
if len(cameras) == 0:
raise error.CameraNotFound(msg="No cameras available")
# If no camera was specified as primary use the first
- if primary_camera is None:
+ if primary_camera is None and auto_primary:
+ logger.info(f'No primary camera given, assigning the first camera (auto_primary={auto_primary!r})')
primary_camera = list(cameras.values())[0] # First camera
primary_camera.is_primary = True
- logger.debug(f"Primary camera: {primary_camera}")
- logger.debug(f"{len(cameras)} cameras created")
-
- return cameras
-
-
-def create_camera_simulator(num_cameras=2):
- """Create simulator camera object(s).
-
- Args:
- num_cameras (int): The number of simulated cameras to create, default 2.
-
- Returns:
- OrderedDict: An ordered dictionary of created camera objects, with the
- camera name as key and camera instance as value. Returns an empty
- OrderedDict if there is no camera configuration items.
-
- Raises:
- error.CameraNotFound: Raised if camera cannot be found at specified port or if
- auto_detect=True and no cameras are found.
- """
- if num_cameras == 0:
- raise error.CameraNotFound(msg="No cameras available")
-
- cameras = OrderedDict()
-
- # Set up a simulated camera with fully configured simulated focuser.
- device_config = {
- 'model': 'simulator',
- 'port': '/dev/camera/simulator',
- 'focuser': {'model': 'simulator',
- 'focus_port': '/dev/ttyFAKE',
- 'initial_position': 20000,
- 'autofocus_range': (40, 80),
- 'autofocus_step': (10, 20),
- 'autofocus_seconds': 0.1,
- 'autofocus_size': 500},
- 'filterwheel': {'model': 'simulator',
- 'filter_names': ['one', 'deux', 'drei', 'quattro'],
- 'move_time': 0.1 * u.second,
- 'timeout': 0.5 * u.second},
- 'readout_time': 0.5,
- }
- logger.debug(f"SimulatorCamera config: {device_config=}")
-
- primary_camera = None
- for cam_num in range(num_cameras):
- cam_name = f'SimCam{cam_num:02d}'
-
- logger.debug(f'Using camera simulator {cam_name}')
-
- camera_model = device_config['model']
- logger.debug(f'Creating camera: {camera_model}')
-
- module = load_module(f'panoptes.pocs.camera.{camera_model}')
- logger.debug(f'Camera module: {module}')
-
- # Create the camera object
- cam = module.Camera(name=cam_name, **device_config)
-
- is_primary = ''
- if cam_num == 0:
- cam.is_primary = True
- primary_camera = cam
- is_primary = ' [Primary]'
-
- logger.debug(f"Camera created: {cam.name} {cam.uid}{is_primary}")
-
- cameras[cam_name] = cam
-
- logger.debug(f"Primary camera: {primary_camera}")
- logger.debug(f"{len(cameras)} cameras created")
+ logger.info(f"Primary camera: {primary_camera}")
+ logger.success(f"{len(cameras)} cameras created")
return cameras
diff --git a/src/panoptes/pocs/camera/camera.py b/src/panoptes/pocs/camera/camera.py
index 5d418f5df..0ebd2ea27 100644
--- a/src/panoptes/pocs/camera/camera.py
+++ b/src/panoptes/pocs/camera/camera.py
@@ -1,95 +1,62 @@
import copy
import os
-import re
-import shutil
-import subprocess
import threading
import time
+from abc import ABCMeta
+from abc import abstractmethod
from contextlib import suppress
-from abc import ABCMeta, abstractmethod
+import astropy.units as u
from astropy.io import fits
from astropy.time import Time
-import astropy.units as u
-
-from panoptes.utils import current_time
+from panoptes.pocs.base import PanBase
from panoptes.utils import error
-from panoptes.utils import listify
from panoptes.utils import images as img_utils
-from panoptes.utils import get_quantity_value
-from panoptes.utils import CountdownTimer
from panoptes.utils.images import fits as fits_utils
from panoptes.utils.library import load_module
-
-from panoptes.pocs.base import PanBase
-from panoptes.utils.serializers import from_yaml
-
-
-def parse_config(lines): # pragma: no cover
- yaml_string = ''
- for line in lines:
- IsID = len(line.split('/')) > 1
- IsLabel = re.match(r'^Label:\s*(.*)', line)
- IsType = re.match(r'^Type:\s*(.*)', line)
- IsCurrent = re.match(r'^Current:\s*(.*)', line)
- IsChoice = re.match(r'^Choice:\s*(\d+)\s*(.*)', line)
- IsPrintable = re.match(r'^Printable:\s*(.*)', line)
- IsHelp = re.match(r'^Help:\s*(.*)', line)
- if IsLabel or IsType or IsCurrent:
- line = f' {line}'
- elif IsChoice:
- if int(IsChoice.group(1)) == 0:
- line = ' Choices:\n {}: {:d}'.format(IsChoice.group(2), int(IsChoice.group(1)))
- else:
- line = ' {}: {:d}'.format(IsChoice.group(2), int(IsChoice.group(1)))
- elif IsPrintable:
- line = ' {}'.format(line)
- elif IsHelp:
- line = ' {}'.format(line)
- elif IsID:
- line = '- ID: {}'.format(line)
- elif line == '':
- continue
- else:
- print(f'Line not parsed: {line}')
- yaml_string += f'{line}\n'
- properties_list = from_yaml(yaml_string)
- if isinstance(properties_list, list):
- properties = {}
- for property in properties_list:
- if property['Label']:
- properties[property['Label']] = property
- else:
- properties = properties_list
- return properties
+from panoptes.utils.time import CountdownTimer
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import get_quantity_value
class AbstractCamera(PanBase, metaclass=ABCMeta):
"""Base class for all cameras.
Attributes:
- filter_type (str): Type of filter attached to camera, default RGGB.
- focuser (`panoptes.pocs.focuser.AbstractFocuser`|None): Focuser for the camera,
- default None.
+ filter_type (str): Type of filter attached to camera. If a filterwheel is present this
+ will return the filterwheel.current_filter property, otherwise it will return the
+ value of the filter_type keyword argument, or if that argument was not given it
+ it will query the camera driver, e.g. 'M' for unfiltered monochrome camera,
+ 'RGGB' for Bayer matrix colour camera.
+ focuser (`panoptes.pocs.focuser.AbstractFocuser`|None): Focuser for the camera, default
+ None.
filter_wheel (`panoptes.pocs.filterwheel.AbstractFilterWheel`|None): Filter wheel for the
- camera,
- default None.
+ camera, default None.
+ uid (str): Unique identifier of the camera.
is_primary (bool): If this camera is the primary camera for the system, default False.
model (str): The model of camera, such as 'gphoto2', 'sbig', etc. Default 'simulator'.
name (str): Name of the camera, default 'Generic Camera'.
port (str): The port the camera is connected to, typically a usb device, default None.
+ temperature (astropy.units.Quantity): Current temperature of the image sensor.
target_temperature (astropy.units.Quantity): image sensor cooling target temperature.
temperature_tolerance (astropy.units.Quantity): tolerance for image sensor temperature.
+ cooling_enabled (bool): True if image sensor cooling is active.
+ cooling_power (astropy.unit.Quantity): Current image sensor cooling power level in percent.
+ egain (astropy.units.Quantity): Image sensor gain in e-/ADU as reported by the camera.
gain (int): The gain setting of the camera (ZWO cameras only).
+ bitdepth (astropy.units.Quantity): ADC bit depth in bits.
image_type (str): Image format of the camera, e.g. 'RAW16', 'RGB24' (ZWO cameras only).
timeout (astropy.units.Quantity): max time to wait after exposure before TimeoutError.
readout_time (float): approximate time to readout the camera after an exposure.
file_extension (str): file extension used by the camera's image data, e.g. 'fits'
library_path (str): path to camera library, e.g. '/usr/local/lib/libfli.so' (SBIG, FLI, ZWO)
properties (dict): A collection of camera properties as read from the camera.
+ is_connected (bool): True if camera is connected.
is_cooled_camera (bool): True if camera has image sensor cooling capability.
is_temperature_stable (bool): True if image sensor temperature is stable.
is_exposing (bool): True if an exposure is currently under way, otherwise False.
+ is_ready (bool): True if the camera is ready to take an exposure.
+ can_take_internal_darks (bool): True if the camera can take internal dark exposures.
Notes:
The port parameter is not used by SBIG or ZWO cameras, and is deprecated for FLI cameras.
@@ -100,8 +67,11 @@ class AbstractCamera(PanBase, metaclass=ABCMeta):
or `panoptes.pocs.camera.libasi.ASIDriver.set_ID()`.
"""
- _subcomponent_classes = {'Focuser', 'FilterWheel'}
- _subcomponent_names = {sub_class.casefold() for sub_class in _subcomponent_classes}
+ _SUBCOMPONENT_LIST = {
+ # attribute: full qualified namespace for base class.
+ 'focuser': 'panoptes.pocs.focuser.focuser.AbstractFocuser',
+ 'filterwheel': 'panoptes.pocs.filterwheel.filterwheel.AbstractFilterWheel',
+ }
def __init__(self,
name='Generic Camera',
@@ -121,20 +91,44 @@ def __init__(self,
self._readout_time = get_quantity_value(kwargs.get('readout_time', 5.0), unit=u.second)
self._file_extension = kwargs.get('file_extension', 'fits')
self._timeout = get_quantity_value(kwargs.get('timeout', 10), unit=u.second)
+
# Default is uncooled camera. Should be set to True if appropriate in camera connect()
# method, based on info received from camera.
self._is_cooled_camera = False
+ self._cooling_enabled = False
+ self._is_temperature_stable = False
+ self._temperature_thread = None
self.temperature_tolerance = kwargs.get('temperature_tolerance', 0.5 * u.Celsius)
+ self._cooling_required_table_time = None
+ self._cooling_sleep_delay = None
+ self._cooling_timeout = None
self._connected = False
self._current_observation = None
- self._exposure_event = threading.Event()
- self._exposure_event.set()
- self._is_exposing = False
-
- for subcomponent_class in self._subcomponent_classes:
- self._create_subcomponent(subcomponent=kwargs.get(subcomponent_class.casefold()),
- class_name=subcomponent_class)
+ self._is_exposing_event = threading.Event()
+ self._exposure_error = None
+
+ # By default assume camera isn't capable of internal darks.
+ self._internal_darks = kwargs.get('internal_darks', False)
+
+ # Set up any subcomponents.
+ self.subcomponents = dict()
+ for attr_name, class_path in self._SUBCOMPONENT_LIST.items():
+ # Create the subcomponent as an attribute with default None.
+ self.logger.debug(f'Setting default attr_name={attr_name!r} to None')
+ setattr(self, attr_name, None)
+
+ # If given subcomponent class (or dict), try to create instance.
+ subcomponent = kwargs.get(attr_name)
+ if subcomponent is not None:
+ self.logger.debug(f'Found subcomponent={subcomponent!r}, creating instance')
+
+ subcomponent = self._create_subcomponent(class_path, subcomponent)
+ self.logger.debug(
+ f'Assigning subcomponent={subcomponent!r} to attr_name={attr_name!r}')
+ setattr(self, attr_name, subcomponent)
+ # Keep a list of active subcomponents
+ self.subcomponents[attr_name] = subcomponent
self.logger.debug(f'Camera created: {self}')
@@ -193,7 +187,7 @@ def target_temperature(self):
raise NotImplementedError # pragma: no cover
@target_temperature.setter
- def target_temperature(self, target_temperature):
+ def target_temperature(self, target):
"""
Set value of the CCD set point, the target temperature for the camera's image sensor
cooling control.
@@ -201,7 +195,11 @@ def target_temperature(self, target_temperature):
Note: this only needs to be implemented for cameras which have cooled image sensors,
not for those that don't (e.g. DSLRs).
"""
- raise NotImplementedError # pragma: no cover
+ if not isinstance(target, u.Quantity):
+ target = target * u.Celsius
+ self.logger.debug(f"Setting {self} cooling set point to {target}.")
+
+ self._set_target_temperature(target)
@property
def temperature_tolerance(self):
@@ -239,7 +237,8 @@ def cooling_enabled(self, enable):
Note: this only needs to be implemented for cameras which have cooled image sensors,
and allow cooling to be enabled/disabled (e.g. SBIG cameras).
"""
- raise NotImplementedError # pragma: no cover
+ self.logger.debug(f"Setting {self.name} cooling enabled to {enable}")
+ self._set_cooling_enabled(enable)
@property
def cooling_power(self):
@@ -273,24 +272,34 @@ def is_temperature_stable(self):
An uncooled camera, or cooled camera with cooling disabled, will always return False.
"""
if self.is_cooled_camera and self.cooling_enabled:
- at_target = abs(self.temperature - self.target_temperature) \
- < self.temperature_tolerance
- if not at_target or self.cooling_power == 100 * u.percent:
+
+ # Temperature must be within tolerance
+ temp_difference = abs(self.temperature - self.target_temperature)
+ at_target_temp = temp_difference <= self.temperature_tolerance
+
+ # Camera cooling power must not be 100%
+ cooling_at_maximum = get_quantity_value(self.cooling_power, u.percent) == 100
+
+ temp_is_stable = at_target_temp and not cooling_at_maximum
+
+ if not temp_is_stable:
self.logger.warning(f'Unstable CCD temperature in {self}.')
- self.logger.warning(f'Cooling={self.cooling_power:.02f} '
- f'Temp={self.temperature:.02f} '
- f'Target={self.target_temperature:.02f} '
- f'Tolerance={self.temperature_tolerance:.02f}')
- return False
- else:
- return True
+ self.logger.debug(f'Cooling power={self.cooling_power:.02f} '
+ f'Temperature={self.temperature:.02f} '
+ f'Target temp={self.target_temperature:.02f} '
+ f'Temp tol={self.temperature_tolerance:.02f} '
+ f"Temp diff={temp_difference:.02f} "
+ f"At target={at_target_temp} "
+ f"At max cooling={cooling_at_maximum} "
+ f"Temperature is stable={temp_is_stable}")
+ return temp_is_stable
else:
return False
@property
def is_exposing(self):
""" True if an exposure is currently under way, otherwise False. """
- return self._is_exposing
+ return self._is_exposing_event.is_set()
@property
def readiness(self):
@@ -300,9 +309,8 @@ def readiness(self):
if self.is_cooled_camera:
current_readiness['temperature_stable'] = self.is_temperature_stable
# Check all the subcomponents too, e.g. make sure filterwheel/focuser aren't moving.
- for sub_name in self._subcomponent_names:
- if getattr(self, sub_name):
- current_readiness['sub_name'] = getattr(self, sub_name).is_ready
+ for sub_name, subcomponent in self.subcomponents.items():
+ current_readiness[sub_name] = subcomponent.is_ready
# Make sure there isn't an exposure already in progress.
current_readiness['not_exposing'] = not self.is_exposing
@@ -319,7 +327,7 @@ def is_ready(self):
self.logger.warning(f"Camera {self} not ready: unstable temperature.")
# Check all the subcomponents too, e.g. make sure filterwheel/focuser aren't moving.
- for sub_name in self._subcomponent_names:
+ for sub_name, subcomponent in self.subcomponents.items():
if not current_readiness.get(sub_name, True):
self.logger.warning(f"Camera {self} not ready: {sub_name} not ready.")
@@ -329,6 +337,22 @@ def is_ready(self):
return all(current_readiness.values())
+ @property
+ def can_take_internal_darks(self):
+ """ True if the camera can take internal dark exposures.
+ This will be true of cameras that have an internal mechanical shutter and can
+ be commanded to keep that shutter closed during the exposure. For cameras that
+ either lack a mechanical shutter or lack the option to keep it closed light must
+ be kept out of the camera during dark exposures by other means, e.g. an opaque
+ blank in a filterwheel, a lens cap, etc.
+ """
+ return self._internal_darks
+
+ @property
+ def exposure_error(self):
+ """ Error message from the most recent exposure or None, if there was no error."""
+ return self._exposure_error
+
##################################################################################################
# Methods
##################################################################################################
@@ -337,7 +361,7 @@ def is_ready(self):
def connect(self):
raise NotImplementedError # pragma: no cover
- def take_observation(self, observation, headers=None, filename=None, **kwargs):
+ def take_observation(self, observation, headers=None, filename=None, blocking=False, **kwargs):
"""Take an observation
Gathers various header information, sets the file path, and calls
@@ -351,15 +375,15 @@ def take_observation(self, observation, headers=None, filename=None, **kwargs):
describing the observation
headers (dict, optional): Header data to be saved along with the file.
filename (str, optional): pass a filename for the output FITS file to
- overrride the default file naming system
+ override the default file naming system.
+ blocking (bool): If method should wait for observation event to be complete
+ before returning, default False.
**kwargs (dict): Optional keyword arguments (`exptime`, dark)
Returns:
threading.Event: An event to be set when the image is done processing
"""
- # To be used for marking when exposure is complete (see `process_exposure`)
observation_event = threading.Event()
-
# Setup the observation
exptime, file_path, image_id, metadata = self._setup_observation(observation,
headers,
@@ -370,23 +394,29 @@ def take_observation(self, observation, headers=None, filename=None, **kwargs):
exptime = kwargs.pop('exptime', observation.exptime.value)
# start the exposure
- exposure_event = self.take_exposure(seconds=exptime, filename=file_path, **kwargs)
+ self.take_exposure(seconds=exptime, filename=file_path, blocking=blocking, **kwargs)
# Add most recent exposure to list
if self.is_primary:
- if 'POINTING' in headers:
+ if 'POINTING' in metadata:
observation.pointing_images[image_id] = file_path
else:
observation.exposure_list[image_id] = file_path
# Process the exposure once readout is complete
+ # To be used for marking when exposure is complete (see `process_exposure`)
t = threading.Thread(
+ name=f'Thread-{image_id}',
target=self.process_exposure,
- args=(metadata, observation_event, exposure_event),
+ args=(metadata, observation_event),
daemon=True)
- t.name = f'{self.name}Thread'
t.start()
+ if blocking:
+ while not observation_event.is_set():
+ self.logger.trace(f'Waiting for observation event')
+ time.sleep(0.5)
+
return observation_event
def take_exposure(self,
@@ -394,6 +424,7 @@ def take_exposure(self,
filename=None,
dark=False,
blocking=False,
+ timeout=None,
*args,
**kwargs):
"""Take an exposure for given number of seconds and saves to provided filename.
@@ -408,133 +439,182 @@ def take_exposure(self,
value 'Dark Frame' instead of 'Light Frame'. Set dark to None to disable the
`IMAGETYP` keyword entirely.
blocking (bool, optional): If False (default) returns immediately after starting
- the exposure, if True will block until it completes.
-
+ the exposure, if True will block until it completes and file exists.
+ timeout (astropy.Quantity): The timeout to use for the exposure. If None, will be
+ calculated automatically.
Returns:
- threading.Event: Event that will be set when exposure is complete.
-
+ threading.Thread: The readout thread, which joins when readout has finished.
"""
- assert self.is_connected, self.logger.error("Camera must be connected for take_exposure!")
+ self._exposure_error = None
+
+ if not self.is_connected:
+ err = AssertionError("Camera must be connected for take_exposure!")
+ self.logger.error(str(err))
+ self._exposure_error = repr(err)
+ raise err
- assert filename is not None, self.logger.error("Must pass filename for take_exposure")
+ if not filename:
+ err = AssertionError("Must pass filename for take_exposure")
+ self.logger.error(str(err))
+ self._exposure_error = repr(err)
+ raise err
+
+ if not self.can_take_internal_darks:
+ if dark:
+ try:
+ # Can't take internal dark, so try using an opaque filter in a filterwheel
+ self.filterwheel.move_to_dark_position(blocking=True)
+ self.logger.debug("Taking dark exposure using filter: " +
+ self.filterwheel.filter_name(self.filterwheel._dark_position))
+ except (AttributeError, error.NotFound):
+ # No filterwheel, or no opaque filter (dark_position not set)
+ self.logger.warning("Taking dark exposure without shutter or opaque filter."
+ " Is the lens cap on?")
+ else:
+ with suppress(AttributeError, error.NotFound):
+ # Ignoring exceptions from no filterwheel, or no last light position
+ self.filterwheel.move_to_light_position(blocking=True)
# Check that the camera (and subcomponents) is ready
if not self.is_ready:
# Work out why the camera isn't ready.
- current_readiness = self.readiness
- problems = []
- if not current_readiness.get('temperature_stable', True):
- problems.append("unstable temperature")
-
- for sub_name in self._subcomponent_names:
- if not current_readiness.get(sub_name, True):
- problems.append(f"{sub_name} not ready")
-
- if not current_readiness['not_exposing']:
- problems.append("exposure in progress")
-
- problems_string = ", ".join(problems)
- msg = f"Attempt to start exposure on {self} while not ready: {problems_string}."
- raise error.PanError(msg)
+ self.logger.warning(f'Cameras not ready: {self.readiness!r}')
+ raise error.PanError(f"Attempt to start exposure on {self} while not ready.")
if not isinstance(seconds, u.Quantity):
seconds = seconds * u.second
- self.logger.debug(f'Taking {seconds} exposure on {self.name}: {filename}')
+ self.logger.debug(
+ f'Taking seconds={seconds!r} exposure on {self.name}: filename={filename!r}')
header = self._create_fits_header(seconds, dark)
- if not self._exposure_event.is_set():
- msg = f"Attempt to take exposure on {self} while one already in progress."
- raise error.PanError(msg)
-
- # Clear event now to prevent any other exposures starting before this one is finished.
- self._exposure_event.clear()
+ if self.is_exposing:
+ err = error.PanError(
+ f"Attempt to take exposure on {self} while one already in progress.")
+ self._exposure_error = repr(err)
+ raise err
try:
# Camera type specific exposure set up and start
+ self._is_exposing_event.set()
readout_args = self._start_exposure(seconds, filename, dark, header, *args, *kwargs)
- except (RuntimeError, ValueError, error.PanError) as err:
- self._exposure_event.set()
- raise error.PanError("Error starting exposure on {}: {}".format(self, err))
+ except Exception as err:
+ err = error.PanError(f"Error starting exposure on {self}: {err!r}")
+ self._exposure_error = repr(err)
+ self._is_exposing_event.clear()
+ raise err
# Start polling thread that will call camera type specific _readout method when done
- readout_thread = threading.Timer(interval=get_quantity_value(seconds, unit=u.second),
- function=self._poll_exposure,
- args=(readout_args,))
+ readout_thread = threading.Thread(target=self._poll_exposure,
+ args=(readout_args, seconds),
+ kwargs=dict(timeout=timeout))
readout_thread.start()
if blocking:
- self.logger.debug("Blocking on exposure event for {}".format(self))
- self._exposure_event.wait()
-
- return self._exposure_event
-
- def process_exposure(self, info, observation_event, exposure_event=None):
- """
- Processes the exposure.
+ self.logger.debug(f"Blocking on exposure event for {self}")
+ readout_thread.join()
+ while self.is_exposing:
+ time.sleep(0.5)
+ self.logger.trace(f'Exposure blocking complete, waiting for file to exist')
+ while not os.path.exists(filename):
+ time.sleep(0.1)
+ self.logger.debug(f"Blocking complete on {self} for filename={filename!r}")
+
+ return readout_thread
+
+ def process_exposure(self,
+ metadata,
+ observation_event,
+ compress_fits=None,
+ record_observations=None,
+ make_pretty_images=None):
+ """ Processes the exposure.
+
+ Performs the following steps:
+
+ 1. First checks to make sure that the file exists on the file system.
+ 2. Calls `_process_fits` with the filename and info, which is specific to each camera.
+ 3. Makes pretty images if requested.
+ 4. Records observation metadata if requested.
+ 5. Compresses FITS files if requested.
+ 6. Sets the observation_event.
If the camera is a primary camera, extract the jpeg image and save metadata to database
`current` collection. Saves metadata to `observations` collection for all images.
Args:
- info (dict): Header metadata saved for the image
+ metadata (dict): Header metadata saved for the image
observation_event (threading.Event): An event that is set signifying that the
camera is done with this exposure
- exposure_event (threading.Event, optional): An event that should be set
- when the exposure is complete, triggering the processing.
+ compress_fits (bool or None): If FITS files should be fpacked into .fits.fz.
+ If None (default), checks the `observations.compress_fits` config-server key.
+ record_observations (bool or None): If observation metadata should be saved.
+ If None (default), checks the `observations.record_observations`
+ config-server key.
+ make_pretty_images (bool or None): If should make a jpg from raw image.
+ If None (default), checks the `observations.make_pretty_images`
+ config-server key.
+
+ Raises:
+ FileNotFoundError: If the FITS file isn't at the specified location.
"""
- # If passed an Event that signals the end of the exposure wait for it to be set
- if exposure_event is not None:
- exposure_event.wait()
+ # Wait for exposure to complete. Timeout handled by exposure thread.
+ while self.is_exposing:
+ time.sleep(1)
- image_id = info['image_id']
- seq_id = info['sequence_id']
- file_path = info['file_path']
- exptime = info['exptime']
- field_name = info['field_name']
+ self.logger.debug(f'Starting exposure processing for {observation_event}')
- image_title = '{} [{}s] {} {}'.format(field_name,
- exptime,
- seq_id.replace('_', ' '),
- current_time(pretty=True))
+ if compress_fits is None:
+ compress_fits = self.get_config('observations.compress_fits', default=False)
- try:
- self.logger.debug("Making pretty image for {}".format(file_path))
- link_path = None
- if info['is_primary']:
- # This should be in the config somewhere.
- link_path = os.path.expandvars('$PANDIR/images/latest.jpg')
-
- img_utils.make_pretty_image(file_path,
- title=image_title,
- link_path=link_path)
- except Exception as e: # pragma: no cover
- self.logger.warning('Problem with extracting pretty image: {}'.format(e))
+ if make_pretty_images is None:
+ make_pretty_images = self.get_config('observations.make_pretty_images', default=False)
+
+ image_id = metadata['image_id']
+ seq_id = metadata['sequence_id']
+ file_path = metadata['file_path']
+ exptime = metadata['exptime']
+ field_name = metadata['field_name']
+
+ # Make sure image exists.
+ if not os.path.exists(file_path):
+ observation_event.set()
+ raise FileNotFoundError(
+ f"Expected image at file_path={file_path!r} does not exist or " +
+ "cannot be accessed, cannot process.")
self.logger.debug(f'Starting FITS processing for {file_path}')
- file_path = self._process_fits(file_path, info)
+ file_path = self._process_fits(file_path, metadata)
self.logger.debug(f'Finished FITS processing for {file_path}')
- with suppress(Exception):
- info['exptime'] = info['exptime'].value
- if info['is_primary']:
- self.logger.debug("Adding current observation to db: {}".format(image_id))
+ # TODO make this async and take it out of camera.
+ if make_pretty_images:
try:
- self.db.insert_current('observations', info, store_permanently=False)
- except Exception as e:
- self.logger.error('Problem adding observation to db: {}'.format(e))
- else:
- self.logger.debug('Compressing {}'.format(file_path))
- fits_utils.fpack(file_path)
+ image_title = f'{field_name} [{exptime}s] {seq_id}'
- self.logger.debug("Adding image metadata to db: {}".format(image_id))
+ self.logger.debug(f"Making pretty image for file_path={file_path!r}")
+ link_path = None
+ if metadata['is_primary']:
+ # This should be in the config somewhere.
+ link_path = os.path.expandvars('$PANDIR/images/latest.jpg')
- self.db.insert('observations', {
- 'data': info,
- 'date': current_time(datetime=True),
- 'sequence_id': seq_id,
- })
+ img_utils.make_pretty_image(file_path,
+ title=image_title,
+ link_path=link_path)
+ except Exception as e: # pragma: no cover
+ self.logger.warning(f'Problem with extracting pretty image: {e!r}')
+
+ metadata['exptime'] = get_quantity_value(metadata['exptime'], unit='seconds')
+
+ if record_observations:
+ self.logger.debug(f"Adding current observation to db: {image_id}")
+ self.db.insert_current('observations', metadata)
+
+ if compress_fits:
+ self.logger.debug(f'Compressing file_path={file_path!r}')
+ compressed_file_path = fits_utils.fpack(file_path)
+ self.logger.debug(f'Compressed {compressed_file_path}')
# Mark the event as done
observation_event.set()
@@ -543,14 +623,14 @@ def autofocus(self,
seconds=None,
focus_range=None,
focus_step=None,
- thumbnail_size=None,
+ cutout_size=None,
keep_files=None,
take_dark=None,
merit_function='vollath_F4',
- merit_function_kwargs={},
+ merit_function_kwargs=None,
mask_dilations=None,
coarse=False,
- make_plots=False,
+ make_plots=None,
blocking=False,
*args, **kwargs):
"""
@@ -565,7 +645,7 @@ def autofocus(self,
encoder units. Specify to override values from config.
focus_step (2-tuple, optional): Coarse & fine focus sweep steps, in
encoder units. Specify to override values from config.
- thumbnail_size (int, optional): Size of square central region of image
+ cutout_size (int, optional): Size of square central region of image
to use, default 500 x 500 pixels.
keep_files (bool, optional): If True will keep all images taken
during focusing. If False (default) will delete all except the
@@ -575,14 +655,14 @@ def autofocus(self,
pixel masking, default True.
merit_function (str/callable, optional): Merit function to use as a
focus metric, default vollath_F4.
- merit_function_kwargs (dict, optional): Dictionary of additional
+ merit_function_kwargs (dict or None, optional): Dictionary of additional
keyword arguments for the merit function.
mask_dilations (int, optional): Number of iterations of dilation to perform on the
saturated pixel mask (determine size of masked regions), default 10
coarse (bool, optional): Whether to perform a coarse focus, otherwise will perform
a fine focus. Default False.
make_plots (bool, optional: Whether to write focus plots to images folder, default
- False.
+ behaviour is to check the focuser autofocus_make_plots attribute.
blocking (bool, optional): Whether to block until autofocus complete, default False.
Returns:
@@ -600,7 +680,7 @@ def autofocus(self,
focus_step=focus_step,
keep_files=keep_files,
take_dark=take_dark,
- thumbnail_size=thumbnail_size,
+ cutout_size=cutout_size,
merit_function=merit_function,
merit_function_kwargs=merit_function_kwargs,
mask_dilations=mask_dilations,
@@ -609,32 +689,61 @@ def autofocus(self,
blocking=blocking,
*args, **kwargs)
- def get_thumbnail(self, seconds, file_path, thumbnail_size, keep_file=False, *args, **kwargs):
+ def get_cutout(self, seconds, file_path, cutout_size, keep_file=False, *args, **kwargs):
"""
- Takes an image and returns a thumbnail.
+ Takes an image and returns a thumbnail cutout.
Takes an image, grabs the data, deletes the FITS file and
- returns a thumbnail from the centre of the image.
+ returns a cutout from the centre of the image.
Args:
seconds (astropy.units.Quantity): exposure time, Quantity or numeric type in seconds.
file_path (str): path to (temporarily) save the image file to.
- thumbnail_size (int): size of the square region of the centre of the image to return.
+ cutout_size (int): size of the square region of the centre of the image to return.
keep_file (bool, optional): if True the image file will be deleted, if False it will
be kept.
- *args, **kwargs: passed to the take_exposure() method
+ *args, **kwargs: passed to the `take_exposure` method
"""
- exposure = self.take_exposure(seconds, filename=file_path, *args, **kwargs)
- exposure.wait()
+ kwargs['blocking'] = True
+ self.take_exposure(seconds, filename=file_path, *args, **kwargs)
+ if self.exposure_error is not None:
+ raise error.PanError(self.exposure_error)
image = fits.getdata(file_path)
if not keep_file:
os.unlink(file_path)
- thumbnail = None
- try:
- thumbnail = img_utils.crop_data(image, box_width=thumbnail_size)
- except Exception as e:
- self.logger.warning(f'Problem getting thumbnail: {e!r}')
- return thumbnail
+
+ # Make sure cutout is not bigger than image.
+ actual_size = min(cutout_size, *image.shape)
+ if actual_size != cutout_size: # noqa
+ self.logger.warning(f'Requested cutout size is larger than image, using {actual_size}')
+
+ return img_utils.crop_data(image, box_width=cutout_size)
+
+ @abstractmethod
+ def _set_target_temperature(self, target):
+ """Camera-specific function to set the target temperature.
+
+ Note:
+ Each sub-class is required to implement this abstract method. The derived
+ method should at a minimum implement the described parameters.
+
+ Args:
+ target (astropy.units.Quantity): The target temperature.
+ """
+ raise NotImplementedError
+
+ @abstractmethod
+ def _set_cooling_enabled(self, enable):
+ """Camera-specific function to set cooling enabled.
+
+ Note:
+ Each sub-class is required to implement this abstract method. The derived
+ method should at a minimum implement the described parameters.
+
+ Args:
+ enable (bool): Enable camera cooling?
+ """
+ raise NotImplementedError
@abstractmethod
def _start_exposure(self, seconds=None, filename=None, dark=False, header=None, *args,
@@ -675,23 +784,39 @@ def _readout(self, filename=None, **kwargs):
"""
pass # pragma: no cover
- def _poll_exposure(self, readout_args):
- timer = CountdownTimer(duration=self._timeout)
+ def _poll_exposure(self, readout_args, exposure_time, timeout=None, interval=0.01):
+ """ Wait until camera is no longer exposing or the timeout is reached. If the timeout is
+ reached, an `error.Timeout` is raised.
+ """
+ if timeout is None:
+ timer_duration = self._timeout + self._readout_time + exposure_time.to_value(u.second)
+ else:
+ timer_duration = timeout
+ self.logger.debug(f"Polling exposure with timeout of {timer_duration} seconds.")
+ timer = CountdownTimer(duration=timer_duration)
try:
while self.is_exposing:
if timer.expired():
- msg = f"Timeout waiting for exposure on {self} to complete"
+ msg = f"Timeout (timer.duration={timer.duration!r}) waiting for exposure on"
+ f" {self} to complete"
raise error.Timeout(msg)
- time.sleep(0.01)
- except (RuntimeError, error.PanError) as err:
+ time.sleep(interval)
+ except Exception as err:
# Error returned by driver at some point while polling
self.logger.error(f'Error while waiting for exposure on {self}: {err!r}')
+ self._exposure_error = repr(err)
raise err
else:
# Camera type specific readout function
- self._readout(*readout_args)
+ try:
+ self._readout(*readout_args)
+ except Exception as err:
+ self.logger.error(f"Error during readout on {self}: {err!r}")
+ self._exposure_error = repr(err)
+ raise err
finally:
- self._exposure_event.set() # Make sure this gets set regardless of readout errors
+ # Make sure this gets set regardless of any errors
+ self._is_exposing_event.clear()
def _create_fits_header(self, seconds, dark=None):
header = fits.Header()
@@ -723,10 +848,8 @@ def _create_fits_header(self, seconds, dark=None):
header.set('CAM-NAME', self.name, 'Camera name')
header.set('CAM-MOD', self.model, 'Camera model')
- for sub_name in self._subcomponent_names:
- subcomponent = getattr(self, sub_name)
- if subcomponent:
- header = subcomponent._add_fits_keywords(header)
+ for sub_name, subcomponent in self.subcomponents.items():
+ header = subcomponent._add_fits_keywords(header)
return header
@@ -818,24 +941,60 @@ def _setup_observation(self, observation, headers, filename, **kwargs):
metadata['filter_request'] = observation.filter_name
if headers is not None:
+ self.logger.trace(f'Updating {file_path} metadata with provided headers')
metadata.update(headers)
self.logger.debug(
- f'Observation setup: exptime={exptime} file_path={file_path} image_id={image_id} '
- f'metadata={metadata}')
+ f'Observation setup: exptime={exptime!r} file_path={file_path!r} image_id={image_id!r} metadata='
+ f'{metadata!r}')
+
return exptime, file_path, image_id, metadata
- def _process_fits(self, file_path, info):
+ def _process_fits(self, file_path, metadata):
"""
- Add FITS headers from info the same as images.cr2_to_fits()
+ Add FITS headers from metadata the same as images.cr2_to_fits()
"""
- self.logger.debug(f"Updating FITS headers: {file_path}")
- fits_utils.update_observation_headers(file_path, info)
- self.logger.debug(f"Finished FITS headers: {file_path}")
+ # TODO (wtgee) I don't like this one bit.
+ fields = {
+ 'image_id': {'keyword': 'IMAGEID'},
+ 'sequence_id': {'keyword': 'SEQID'},
+ 'field_name': {'keyword': 'FIELD'},
+ 'ra_mnt': {'keyword': 'RA-MNT', 'comment': 'Degrees'},
+ 'ha_mnt': {'keyword': 'HA-MNT', 'comment': 'Degrees'},
+ 'dec_mnt': {'keyword': 'DEC-MNT', 'comment': 'Degrees'},
+ 'equinox': {'keyword': 'EQUINOX', 'default': 2000.},
+ 'airmass': {'keyword': 'AIRMASS', 'comment': 'Sec(z)'},
+ 'filter': {'keyword': 'FILTER'},
+ 'latitude': {'keyword': 'LAT-OBS', 'comment': 'Degrees'},
+ 'longitude': {'keyword': 'LONG-OBS', 'comment': 'Degrees'},
+ 'elevation': {'keyword': 'ELEV-OBS', 'comment': 'Meters'},
+ 'moon_separation': {'keyword': 'MOONSEP', 'comment': 'Degrees'},
+ 'moon_fraction': {'keyword': 'MOONFRAC'},
+ 'creator': {'keyword': 'CREATOR', 'comment': 'POCS Software version'},
+ 'camera_uid': {'keyword': 'INSTRUME', 'comment': 'Camera ID'},
+ 'observer': {'keyword': 'OBSERVER', 'comment': 'PANOPTES Unit ID'},
+ 'origin': {'keyword': 'ORIGIN'},
+ 'tracking_rate_ra': {'keyword': 'RA-RATE', 'comment': 'RA Tracking Rate'},
+ }
+
+ self.logger.debug(f"Updating FITS headers: {file_path} with metadata={metadata!r}")
+ with fits.open(file_path, 'update') as f:
+ hdu = f[0]
+ for metadata_key, field_info in fields.items():
+ fits_key = field_info['keyword']
+ fits_comment = field_info.get('comment', '')
+ # Get the value from either the metadata, the default, or use blank string.
+ fits_value = metadata.get(metadata_key, field_info.get('default', ''))
+
+ self.logger.trace(
+ f'Setting fits_key={fits_key!r} = fits_value={fits_value!r} fits_comment={fits_comment!r}')
+ hdu.header.set(fits_key, fits_value, fits_comment)
+
+ self.logger.debug(f"Finished FITS headers: {file_path}")
return file_path
- def _create_subcomponent(self, subcomponent, class_name):
+ def _create_subcomponent(self, class_path, subcomponent):
"""
Creates a subcomponent as an attribute of the camera. Can do this from either an instance
of the appropriate subcomponent class, or from a dictionary of keyword arguments for the
@@ -844,213 +1003,85 @@ def _create_subcomponent(self, subcomponent, class_name):
Args:
subcomponent (instance of sub_name | dict): the subcomponent object, or the keyword
arguments required to create it.
- class_name (str): name of the subcomponent class, e.g. 'Focuser'. Lower cased version
- will be used as the attribute name, and must also match the name of the
- corresponding POCS submodule for this subcomponent, e.g. `panoptes.pocs.focuser`.
- """
- class_name_lower = class_name.casefold()
- if subcomponent:
- base_module_name = "panoptes.pocs.{0}.{0}".format(class_name_lower)
- try:
- base_module = load_module(base_module_name)
- except error.NotFound as err:
- self.logger.critical(
- f"Couldn't import {class_name} base class module {base_module_name}!")
- raise err
- base_class = getattr(base_module, f"Abstract{class_name}")
-
- if isinstance(subcomponent, base_class):
- self.logger.debug(f"{class_name} received: {subcomponent}")
- setattr(self, class_name_lower, subcomponent)
- getattr(self, class_name_lower).camera = self
- elif isinstance(subcomponent, dict):
- module_name = 'panoptes.pocs.{}.{}'.format(class_name_lower, subcomponent['model'])
- try:
- module = load_module(module_name)
- except error.NotFound as err:
- self.logger.critical(f"Couldn't import {class_name} module {module_name}!")
- raise err
- subcomponent_kwargs = copy.deepcopy(subcomponent)
- subcomponent_kwargs.update({'camera': self})
- setattr(self,
- class_name_lower,
- getattr(module, class_name)(**subcomponent_kwargs))
- else:
- # Should have been passed either an instance of base_class or dict with subcomponent
- # configuration. Got something else...
- self.logger.error("Expected either a {} instance or dict, got {}".format(
- class_name, subcomponent))
- setattr(self, class_name_lower, None)
- else:
- setattr(self, class_name_lower, None)
-
- def __str__(self):
- try:
- name = self.name
- if self.is_primary:
- name += ' [Primary]'
-
- s = f"{name} ({self.uid}) on {self.port}"
-
- sub_count = 0
- for sub_name in self._subcomponent_names:
- subcomponent = getattr(self, sub_name)
- if subcomponent:
- if sub_count == 0:
- s += f" with {subcomponent.name}"
- else:
- s += f" & {subcomponent.name}"
- sub_count += 1
- except Exception as e:
- self.logger.warning(f'Unable to stringify camera: {e=}')
- s = str(self.__class__)
-
- return s
-
-
-class AbstractGPhotoCamera(AbstractCamera): # pragma: no cover
-
- """ Abstract camera class that uses gphoto2 interaction
-
- Args:
- config(Dict): Config key/value pairs, defaults to empty dict.
- """
-
- def __init__(self, *arg, **kwargs):
- super().__init__(*arg, **kwargs)
+ class_path (str): Full namespace of the subcomponent, e.g.
+ 'panoptes.pocs.focuser.focuser.AbstractFocuser'.
- self.properties = None
-
- self._gphoto2 = shutil.which('gphoto2')
- assert self._gphoto2 is not None, error.PanError("Can't find gphoto2")
-
- self.logger.debug('GPhoto2 camera {} created on {}'.format(self.name, self.port))
-
- # Setup a holder for the process
- self._proc = None
-
- @AbstractCamera.uid.getter
- def uid(self):
- """ A six-digit serial number for the camera """
- return self._serial_number[0:6]
-
- def command(self, cmd):
- """ Run gphoto2 command """
-
- # Test to see if there is a running command already
- if self._proc and self._proc.poll():
- raise error.InvalidCommand("Command already running")
- else:
- # Build the command.
- run_cmd = [self._gphoto2, '--port', self.port]
- run_cmd.extend(listify(cmd))
-
- self.logger.debug("gphoto2 command: {}".format(run_cmd))
-
- try:
- self._proc = subprocess.Popen(
- run_cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- universal_newlines=True,
- shell=False
- )
- except OSError as e:
- raise error.InvalidCommand(
- "Can't send command to gphoto2. {} \t {}".format(
- e, run_cmd))
- except ValueError as e:
- raise error.InvalidCommand(
- "Bad parameters to gphoto2. {} \t {}".format(e, run_cmd))
- except Exception as e:
- raise error.PanError(e)
-
- def get_command_result(self, timeout=10):
- """ Get the output from the command """
-
- self.logger.debug("Getting output from proc {}".format(self._proc.pid))
-
- try:
- outs, errs = self._proc.communicate(timeout=timeout)
- except subprocess.TimeoutExpired:
- self.logger.debug("Timeout while waiting. Killing process {}".format(self._proc.pid))
- self._proc.kill()
- outs, errs = self._proc.communicate()
-
- self._proc = None
-
- return outs
-
- def wait_for_command(self, timeout=10):
- """ Wait for the given command to end
+ Returns:
+ object: an instance of the subcomponent object.
- This method merely waits for a subprocess to complete but doesn't attempt to communicate
- with the process (see `get_command_result` for that).
+ Raises:
+ panoptes.utils.error.NotFound: Not found error.
"""
- self.logger.debug("Waiting for proc {}".format(self._proc.pid))
-
+ # Load the module for the subcomponent.
+ sub_parts = class_path.split('.')
+ base_class_name = sub_parts.pop()
+ base_module_name = '.'.join(sub_parts)
+ self.logger.debug(f'Loading base_module_name={base_module_name!r}')
try:
- self._proc.wait(timeout=timeout)
- except subprocess.TimeoutExpired:
- self.logger.warning("Timeout expired for PID {}".format(self._proc.pid))
-
- self._proc = None
-
- def set_property(self, prop, val):
- """ Set a property on the camera """
- set_cmd = ['--set-config', '{}={}'.format(prop, val)]
-
- self.command(set_cmd)
+ base_module = load_module(base_module_name)
+ except error.NotFound as err:
+ self.logger.critical(f"Couldn't import base_module_name={base_module_name!r}")
+ raise err
- # Forces the command to wait
- self.get_command_result()
+ # Get the base class from the loaded module.
+ self.logger.debug(f'Trying to get base_class_name={base_class_name!r} from {base_module}')
+ base_class = getattr(base_module, base_class_name)
- def set_properties(self, prop2index, prop2value):
- """ Sets a number of properties all at once, by index or value.
+ # If we get an instance, just use it.
+ if isinstance(subcomponent, base_class):
+ self.logger.debug(
+ f"subcomponent={subcomponent!r} is already a base_class={base_class!r} instance")
+ # If we get a dict, use them as params to create instance.
+ elif isinstance(subcomponent, dict):
+ try:
+ model = subcomponent['model']
+ self.logger.debug(
+ f"subcomponent={subcomponent!r} is a dict but has model={model!r} keyword, "
+ f"trying to create a base_class={base_class!r} instance")
+ base_class = load_module(model)
+ except (KeyError, error.NotFound) as err:
+ raise error.NotFound(
+ f"Can't create a class_path={class_path!r} from subcomponent={subcomponent!r}")
- Args:
- prop2index (dict): A dict with keys corresponding to the property to
- be set and values corresponding to the index option
- prop2value (dict): A dict with keys corresponding to the property to
- be set and values corresponding to the literal value
- """
- set_cmd = list()
- for prop, val in prop2index.items():
- set_cmd.extend(['--set-config-index', '{}={}'.format(prop, val)])
- for prop, val in prop2value.items():
- set_cmd.extend(['--set-config-value', '{}={}'.format(prop, val)])
+ self.logger.debug(f'Creating the base_class_name={base_class_name!r} object from dict')
- self.command(set_cmd)
+ # Copy dict creation items and add the camera.
+ subcomponent_kwargs = copy.deepcopy(subcomponent)
+ subcomponent_kwargs.update({'camera': self})
- # Forces the command to wait
- self.get_command_result()
+ try:
+ subcomponent = base_class(**subcomponent_kwargs)
+ except TypeError:
+ raise error.NotFound(f'base_class={base_class!r} is not a callable class. '
+ f'Please specify full path to class (not module).')
+ self.logger.success(f'{subcomponent} created for {base_class_name}')
+ else:
+ # Should have been passed either an instance of base_class or dict with subcomponent
+ # configuration. Got something else...
+ raise error.NotFound(
+ f"Expected either a {base_class_name} instance or dict, got {subcomponent!r}")
- def get_property(self, prop):
- """ Gets a property from the camera """
- set_cmd = ['--get-config', '{}'.format(prop)]
+ # Give the subcomponent a reference back to the camera.
+ setattr(subcomponent, 'camera', self)
+ return subcomponent
- self.command(set_cmd)
- result = self.get_command_result()
+ def __str__(self):
+ s = f'{self.name}'
+ try:
+ if self.is_primary:
+ s += ' [Primary]'
- output = ''
- for line in result.split('\n'):
- match = re.match(r'Current:\s*(.*)', line)
- if match:
- output = match.group(1)
+ s += f" ({self.uid})"
- return output
+ if self.port:
+ s += f" port={self.port}"
- def load_properties(self):
- ''' Load properties from the camera
- Reads all the configuration properties available via gphoto2 and populates
- a local list with these entries.
- '''
- self.logger.debug('Get All Properties')
- command = ['--list-all-config']
+ sub_names = '& '.join(list(self.subcomponents.keys()))
+ if sub_names != '':
+ s += f" with {sub_names}"
- self.properties = parse_config(self.command(command))
+ except Exception as e:
+ self.logger.warning(f'Unable to stringify camera: e={e!r}')
+ s = str(self.__class__)
- if self.properties:
- self.logger.debug(' Found {} properties'.format(len(self.properties)))
- else:
- self.logger.warning(' Could not determine properties.')
+ return s
diff --git a/src/panoptes/pocs/camera/fli.py b/src/panoptes/pocs/camera/fli.py
index 56d51c5be..7c0eca08c 100644
--- a/src/panoptes/pocs/camera/fli.py
+++ b/src/panoptes/pocs/camera/fli.py
@@ -40,7 +40,7 @@ def temperature(self):
"""
return self._driver.FLIGetTemperature(self._handle)
- @property
+ @AbstractSDKCamera.target_temperature.getter
def target_temperature(self):
"""
Current value of the target temperature for the camera's image sensor cooling control.
@@ -49,14 +49,6 @@ def target_temperature(self):
"""
return self._target_temperature
- @target_temperature.setter
- def target_temperature(self, target):
- if not isinstance(target, u.Quantity):
- target = target * u.Celsius
- self.logger.debug("Setting {} cooling set point to {}".format(self, target))
- self._driver.FLISetTemperature(self._handle, target)
- self._target_temperature = target
-
@property
def cooling_enabled(self):
"""
@@ -106,6 +98,14 @@ def connect(self):
# Private Methods
+ def _set_target_temperature(self, target):
+ self._driver.FLISetTemperature(self._handle, target)
+ # Check for success?
+ self._target_temperature = target
+
+ def _set_cooling_enabled():
+ raise NotImplementedError
+
def _start_exposure(self, seconds, filename, dark, header, *args, **kwargs):
self._driver.FLISetExposureTime(self._handle, exposure_time=seconds)
@@ -153,7 +153,9 @@ def _readout(self, filename, width, height, header):
self, image_data.shape[0], rows_got, err)
raise error.PanError(message)
else:
- fits_utils.write_fits(image_data, header, filename)
+ fits_utils.write_fits(data=image_data,
+ header=header,
+ filename=filename)
def _create_fits_header(self, seconds, dark):
header = super()._create_fits_header(seconds, dark)
diff --git a/src/panoptes/pocs/camera/gphoto/__init__.py b/src/panoptes/pocs/camera/gphoto/__init__.py
new file mode 100644
index 000000000..1d7a19e40
--- /dev/null
+++ b/src/panoptes/pocs/camera/gphoto/__init__.py
@@ -0,0 +1 @@
+from panoptes.pocs.camera.gphoto.base import AbstractGPhotoCamera
diff --git a/src/panoptes/pocs/camera/gphoto/base.py b/src/panoptes/pocs/camera/gphoto/base.py
new file mode 100644
index 000000000..e181ab58b
--- /dev/null
+++ b/src/panoptes/pocs/camera/gphoto/base.py
@@ -0,0 +1,200 @@
+import re
+import shutil
+import subprocess
+
+from panoptes.pocs.camera import AbstractCamera
+from panoptes.utils import error
+from panoptes.utils.serializers import from_yaml
+from panoptes.utils.utils import listify
+
+
+def parse_config(lines): # pragma: no cover
+ yaml_string = ''
+ for line in lines:
+ IsID = len(line.split('/')) > 1
+ IsLabel = re.match(r'^Label:\s*(.*)', line)
+ IsType = re.match(r'^Type:\s*(.*)', line)
+ IsCurrent = re.match(r'^Current:\s*(.*)', line)
+ IsChoice = re.match(r'^Choice:\s*(\d+)\s*(.*)', line)
+ IsPrintable = re.match(r'^Printable:\s*(.*)', line)
+ IsHelp = re.match(r'^Help:\s*(.*)', line)
+ if IsLabel or IsType or IsCurrent:
+ line = f' {line}'
+ elif IsChoice:
+ if int(IsChoice.group(1)) == 0:
+ line = ' Choices:\n {}: {:d}'.format(IsChoice.group(2), int(IsChoice.group(1)))
+ else:
+ line = ' {}: {:d}'.format(IsChoice.group(2), int(IsChoice.group(1)))
+ elif IsPrintable:
+ line = ' {}'.format(line)
+ elif IsHelp:
+ line = ' {}'.format(line)
+ elif IsID:
+ line = '- ID: {}'.format(line)
+ elif line == '':
+ continue
+ else:
+ print(f'Line not parsed: {line}')
+ yaml_string += f'{line}\n'
+ properties_list = from_yaml(yaml_string)
+ if isinstance(properties_list, list):
+ properties = {}
+ for property in properties_list:
+ if property['Label']:
+ properties[property['Label']] = property
+ else:
+ properties = properties_list
+ return properties
+
+
+class AbstractGPhotoCamera(AbstractCamera): # pragma: no cover
+
+ """ Abstract camera class that uses gphoto2 interaction
+
+ Args:
+ config(Dict): Config key/value pairs, defaults to empty dict.
+ """
+
+ def __init__(self, *arg, **kwargs):
+ super().__init__(*arg, **kwargs)
+
+ self.properties = None
+
+ self._gphoto2 = shutil.which('gphoto2')
+ assert self._gphoto2 is not None, error.PanError("Can't find gphoto2")
+
+ self.logger.debug('GPhoto2 camera {} created on {}'.format(self.name, self.port))
+
+ # Setup a holder for the process
+ self._proc = None
+
+ # Explicitly set holders for some of the hardware subcomponents until
+ # TODO fix the setting of the attribute.
+ self.focuser = None
+ self.filterwheel = None
+
+ @AbstractCamera.uid.getter
+ def uid(self):
+ """ A six-digit serial number for the camera """
+ return self._serial_number[0:6]
+
+ def command(self, cmd):
+ """ Run gphoto2 command """
+
+ # Test to see if there is a running command already
+ if self._proc and self._proc.poll():
+ raise error.InvalidCommand("Command already running")
+ else:
+ # Build the command.
+ run_cmd = [self._gphoto2, '--port', self.port]
+ run_cmd.extend(listify(cmd))
+
+ self.logger.debug("gphoto2 command: {}".format(run_cmd))
+
+ try:
+ self._proc = subprocess.Popen(
+ run_cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ shell=False
+ )
+ except OSError as e:
+ raise error.InvalidCommand(
+ "Can't send command to gphoto2. {} \t {}".format(
+ e, run_cmd))
+ except ValueError as e:
+ raise error.InvalidCommand(
+ "Bad parameters to gphoto2. {} \t {}".format(e, run_cmd))
+ except Exception as e:
+ raise error.PanError(e)
+
+ def get_command_result(self, timeout=10):
+ """ Get the output from the command """
+
+ self.logger.debug("Getting output from proc {}".format(self._proc.pid))
+
+ try:
+ outs, errs = self._proc.communicate(timeout=timeout)
+ except subprocess.TimeoutExpired:
+ self.logger.debug("Timeout while waiting. Killing process {}".format(self._proc.pid))
+ self._proc.kill()
+ outs, errs = self._proc.communicate()
+
+ self._proc = None
+
+ return outs
+
+ def wait_for_command(self, timeout=10):
+ """ Wait for the given command to end
+
+ This method merely waits for a subprocess to complete but doesn't attempt to communicate
+ with the process (see `get_command_result` for that).
+ """
+ self.logger.debug("Waiting for proc {}".format(self._proc.pid))
+
+ try:
+ self._proc.wait(timeout=timeout)
+ except subprocess.TimeoutExpired:
+ self.logger.warning("Timeout expired for PID {}".format(self._proc.pid))
+
+ self._proc = None
+
+ def set_property(self, prop, val):
+ """ Set a property on the camera """
+ set_cmd = ['--set-config', '{}={}'.format(prop, val)]
+
+ self.command(set_cmd)
+
+ # Forces the command to wait
+ self.get_command_result()
+
+ def set_properties(self, prop2index, prop2value):
+ """ Sets a number of properties all at once, by index or value.
+
+ Args:
+ prop2index (dict): A dict with keys corresponding to the property to
+ be set and values corresponding to the index option
+ prop2value (dict): A dict with keys corresponding to the property to
+ be set and values corresponding to the literal value
+ """
+ set_cmd = list()
+ for prop, val in prop2index.items():
+ set_cmd.extend(['--set-config-index', '{}={}'.format(prop, val)])
+ for prop, val in prop2value.items():
+ set_cmd.extend(['--set-config-value', '{}={}'.format(prop, val)])
+
+ self.command(set_cmd)
+
+ # Forces the command to wait
+ self.get_command_result()
+
+ def get_property(self, prop):
+ """ Gets a property from the camera """
+ set_cmd = ['--get-config', '{}'.format(prop)]
+
+ self.command(set_cmd)
+ result = self.get_command_result()
+
+ output = ''
+ for line in result.split('\n'):
+ match = re.match(r'Current:\s*(.*)', line)
+ if match:
+ output = match.group(1)
+
+ return output
+
+ def load_properties(self):
+ ''' Load properties from the camera
+ Reads all the configuration properties available via gphoto2 and populates
+ a local list with these entries.
+ '''
+ self.logger.debug('Get All Properties')
+ command = ['--list-all-config']
+
+ self.properties = parse_config(self.command(command))
+
+ if self.properties:
+ self.logger.debug(' Found {} properties'.format(len(self.properties)))
+ else:
+ self.logger.warning(' Could not determine properties.')
diff --git a/src/panoptes/pocs/camera/canon_gphoto2.py b/src/panoptes/pocs/camera/gphoto/canon.py
similarity index 93%
rename from src/panoptes/pocs/camera/canon_gphoto2.py
rename to src/panoptes/pocs/camera/gphoto/canon.py
index ef1af2dbb..4a712a590 100644
--- a/src/panoptes/pocs/camera/canon_gphoto2.py
+++ b/src/panoptes/pocs/camera/gphoto/canon.py
@@ -1,16 +1,16 @@
import os
import subprocess
from abc import ABC
-
from threading import Event
from threading import Timer
-from panoptes.utils import current_time
-from panoptes.utils import CountdownTimer
+from astropy import units as u
+from panoptes.pocs.camera.gphoto import AbstractGPhotoCamera
from panoptes.utils import error
-from panoptes.utils import get_quantity_value
from panoptes.utils.images import cr2 as cr2_utils
-from panoptes.pocs.camera import AbstractGPhotoCamera
+from panoptes.utils.time import CountdownTimer
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import get_quantity_value
class Camera(AbstractGPhotoCamera, ABC):
@@ -25,7 +25,11 @@ def __init__(self, *args, **kwargs):
self.logger.debug("Connecting GPhoto2 camera")
self.connect()
- self.logger.debug("{} connected".format(self.name))
+ self.logger.debug(f"{self.name} connected")
+
+ @property
+ def bit_depth(self):
+ return 12 * u.bit
def connect(self):
"""Connect to Canon DSLR
@@ -116,7 +120,7 @@ def take_observation(self, observation, headers=None, filename=None, *args, **kw
# Process the image after a set amount of time
wait_time = exptime + self.readout_time
- t = Timer(wait_time, self.process_exposure, (metadata, observation_event, exposure_event))
+ t = Timer(wait_time, self.process_exposure, (metadata, observation_event))
t.name = f'{self.name}Thread'
t.start()
@@ -144,7 +148,7 @@ def _start_exposure(self, seconds=None, filename=None, dark=None, header=None, *
# Take Picture
try:
- self._is_exposing = True
+ self._is_exposing_event.set()
self._exposure_proc = subprocess.Popen(run_cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -191,6 +195,5 @@ def _poll_exposure(self, readout_args):
self._readout(*readout_args)
finally:
self.logger.debug(f'Setting exposure event for {self.name}')
- self._is_exposing = False
self._exposure_proc = None
- self._exposure_event.set() # Make sure this gets set regardless of readout errors
+ self._is_exposing_event.clear() # Make sure this gets set regardless of readout errors
diff --git a/src/panoptes/pocs/camera/libasi.py b/src/panoptes/pocs/camera/libasi.py
index 1a4bcfc45..b8bc8097a 100644
--- a/src/panoptes/pocs/camera/libasi.py
+++ b/src/panoptes/pocs/camera/libasi.py
@@ -3,10 +3,10 @@
import numpy as np
from astropy import units as u
-
from panoptes.pocs.camera.sdk import AbstractSDKDriver
from panoptes.utils import error
-from panoptes.utils import get_quantity_value
+from panoptes.utils.utils import get_quantity_value
+
####################################################################################################
#
@@ -41,6 +41,7 @@ def __init__(self, library_path=None, **kwargs):
"""
super().__init__(name='ASICamera2', library_path=library_path, **kwargs)
self._product_ids = self.get_product_ids() # Supported camera models
+
# Methods
def get_SDK_version(self):
diff --git a/src/panoptes/pocs/camera/libfli.py b/src/panoptes/pocs/camera/libfli.py
index 5da936ba1..9bce3f26f 100644
--- a/src/panoptes/pocs/camera/libfli.py
+++ b/src/panoptes/pocs/camera/libfli.py
@@ -8,11 +8,10 @@
import numpy as np
from astropy import units as u
-
-from panoptes.pocs.camera.sdk import AbstractSDKDriver
from panoptes.pocs.camera import libfliconstants as c
+from panoptes.pocs.camera.sdk import AbstractSDKDriver
from panoptes.utils import error
-from panoptes.utils import get_quantity_value
+from panoptes.utils.utils import get_quantity_value
valid_values = {'interface type': (c.FLIDOMAIN_PARALLEL_PORT,
c.FLIDOMAIN_USB,
@@ -31,6 +30,7 @@
c.FLI_FRAME_TYPE_FLOOD,
c.FLI_FRAME_TYPE_RBI_FLUSH)}
+
################################################################################
# Main SBIGDriver class
################################################################################
diff --git a/src/panoptes/pocs/camera/sbig.py b/src/panoptes/pocs/camera/sbig.py
index ba9a9534d..49efed095 100644
--- a/src/panoptes/pocs/camera/sbig.py
+++ b/src/panoptes/pocs/camera/sbig.py
@@ -41,7 +41,7 @@ def temperature(self):
temp_status = self._driver.query_temp_status(self._handle)
return temp_status['imaging_ccd_temperature']
- @property
+ @AbstractSDKCamera.target_temperature.getter
def target_temperature(self):
"""
Current value of the target temperature for the camera's image sensor cooling control.
@@ -51,15 +51,7 @@ def target_temperature(self):
temp_status = self._driver.query_temp_status(self._handle)
return temp_status['ccd_set_point']
- @target_temperature.setter
- def target_temperature(self, target):
- if not isinstance(target, u.Quantity):
- target = target * u.Celsius
- self.logger.debug("Setting {} cooling set point to {}".format(self, target))
- enabled = self.cooling_enabled
- self._driver.set_temp_regulation(self._handle, target, enabled)
-
- @property
+ @AbstractSDKCamera.cooling_enabled.getter
def cooling_enabled(self):
"""
Current status of the camera's image sensor cooling system (enabled/disabled).
@@ -69,12 +61,6 @@ def cooling_enabled(self):
temp_status = self._driver.query_temp_status(self._handle)
return temp_status['cooling_enabled']
- @cooling_enabled.setter
- def cooling_enabled(self, enable):
- self.logger.debug("Setting {} cooling enabled to {}".format(self.name, enable))
- target = self.target_temperature
- self._driver.set_temp_regulation(self._handle, target, enable)
-
@property
def cooling_power(self):
"""
@@ -137,6 +123,14 @@ def connect(self):
# Private methods
+ def _set_target_temperature(self, target):
+ self._driver.set_temp_regulation(self._handle, target, self.cooling_enabled)
+ self._target_temperature = target
+
+ def _set_cooling_enabled(self, enable):
+ target = self.target_temperature
+ self._driver.set_temp_regulation(self._handle, target, enable)
+
def _start_exposure(self, seconds, filename, dark, header, *args, **kwargs):
readout_mode = 'RM_1X1' # Unbinned mode
top = 0 # Unwindowed too
@@ -175,10 +169,10 @@ def _readout(self, filename, readout_mode, top, left, height, width, header):
except RuntimeError as err:
raise error.PanError('Readout error on {}, {}'.format(self, err))
else:
- fits_utils.write_fits(image_data,
- header,
- filename,
- self.logger)
+ fits_utils.write_fits(data=image_data,
+ header=header,
+ filename=filename)
+
elif exposure_status == 'CS_IDLE':
raise error.PanError("Exposure missing on {}".format(self))
else:
diff --git a/src/panoptes/pocs/camera/sbigudrv.py b/src/panoptes/pocs/camera/sbigudrv.py
index 804615404..4a0936a0c 100644
--- a/src/panoptes/pocs/camera/sbigudrv.py
+++ b/src/panoptes/pocs/camera/sbigudrv.py
@@ -9,18 +9,18 @@
and call the single command function (SBIGDriver._send_command()).
"""
import ctypes
-import time
-import threading
import enum
+import threading
+import time
import numpy as np
-from numpy.ctypeslib import as_ctypes
from astropy import units as u
-
+from numpy.ctypeslib import as_ctypes
from panoptes.pocs.camera.sdk import AbstractSDKDriver
from panoptes.utils import error
-from panoptes.utils import CountdownTimer
-from panoptes.utils import get_quantity_value
+from panoptes.utils.time import CountdownTimer
+from panoptes.utils.utils import get_quantity_value
+
################################################################################
# Main SBIGDriver class
@@ -527,7 +527,7 @@ def cfw_goto(self, handle, position, model='AUTO', cfw_event=None, timeout=10 *
return self._cfw_parse_results(cfw_goto_results)
-# Private methods
+ # Private methods
def _cfw_poll(self, handle, position, model='AUTO', cfw_event=None, timeout=None):
"""
@@ -819,7 +819,6 @@ def _send_command(self, command, params=None, results=None):
# command code.
commands = {code: command for command, code in command_codes.items()}
-
# Camera error messages
errors = {0: 'CE_NO_ERROR',
1: 'CE_CAMERA_NOT_FOUND',
@@ -937,7 +936,6 @@ class QueryUSBResults3(ctypes.Structure):
# Reverse dictionary
camera_type_codes = {camera: code for code, camera in camera_types.items()}
-
#################################################################################
# Open Device, Establish Link, Get Link status related
#################################################################################
@@ -1346,7 +1344,6 @@ class StartExposureParams2(ctypes.Structure):
readout_mode_codes = {mode: code for code, mode in readout_modes.items()}
-
# Command status codes and corresponding messages as returned by
# Query Command Status
statuses = {0: "CS_IDLE",
diff --git a/src/panoptes/pocs/camera/sdk.py b/src/panoptes/pocs/camera/sdk.py
index ae6a52713..bd13bef67 100644
--- a/src/panoptes/pocs/camera/sdk.py
+++ b/src/panoptes/pocs/camera/sdk.py
@@ -8,6 +8,13 @@
from panoptes.utils.library import load_c_library
from panoptes.pocs.utils.logger import get_logger
+# Would usually use self.logger but that won't exist until after calling super().__init__(),
+# and don't want to do that until after the serial number and port have both been determined
+# in order to avoid log entries with misleading values. To enable logging during the device
+# scanning phase use get_logger() instead.
+# TODO figure out how to remove this.
+logger = get_logger()
+
class AbstractSDKDriver(PanBase, metaclass=ABCMeta):
def __init__(self, name, library_path=None, *args, **kwargs):
@@ -28,6 +35,8 @@ def __init__(self, name, library_path=None, *args, **kwargs):
locate the library.
OSError: raises if the ctypes.CDLL loader cannot load the library.
"""
+ # Most SDK cameras can take internal darks so set to True by default.
+ kwargs['internal_darks'] = kwargs.get('internal_darks', True)
super().__init__(**kwargs)
self._CDLL = load_c_library(name=name, path=library_path)
self._version = self.get_SDK_version()
@@ -54,7 +63,7 @@ def get_devices(self):
class AbstractSDKCamera(AbstractCamera):
_driver = None
- _cameras = {}
+ _cameras = dict()
_assigned_cameras = set()
def __init__(self,
@@ -64,18 +73,13 @@ def __init__(self,
filter_type=None,
target_temperature=None,
*args, **kwargs):
- # Would usually use self.logger but that won't exist until after calling super().__init__(),
- # and don't want to do that until after the serial number and port have both been determined
- # in order to avoid log entries with misleading values. To enable logging during the device
- # scanning phase use get_logger() instead.
- logger = get_logger()
# The SDK cameras don't generally have a 'port', they are identified by a serial_number,
# which is some form of unique ID readable via the camera SDK.
kwargs['port'] = None
serial_number = kwargs.get('serial_number')
if not serial_number:
- msg = "Must specify serial_number for {}.".format(name)
+ msg = f"Must specify serial_number for {name}."
logger.error(msg)
raise ValueError(msg)
@@ -86,28 +90,32 @@ def __init__(self,
# Initialise the driver if it hasn't already been done
my_class._driver = driver(library_path=library_path)
- logger.debug("Looking for {} with UID '{}'.".format(name, serial_number))
+ logger.debug(f"Looking for name={name!r} with UID 'serial_number={serial_number!r}'.")
if not my_class._cameras:
# No cached camera details, need to probe for connected cameras
# This will raise a PanError if there are no cameras.
my_class._cameras = my_class._driver.get_devices()
- logger.debug("Connected {}s: {}".format(name, my_class._cameras))
+
+ logger.debug(f"Connected {name} devices: {my_class._cameras}")
if serial_number in my_class._cameras:
- logger.debug(f"Found {name} with UID '{serial_number}' at {my_class._cameras[serial_number]}.")
+ logger.debug(f"Found {name} with UID serial_number={serial_number!r} at {my_class._cameras[serial_number]}.")
else:
- raise error.PanError(f"Could not find {name} with UID '{serial_number}'.")
+ raise error.InvalidConfig(f"No config information found for "
+ f"name={name!r} with serial_number={serial_number!r} in {my_class._cameras}")
if serial_number in my_class._assigned_cameras:
- raise error.PanError(f"{name} with UID '{serial_number}' already in use.")
+ raise error.PanError(f"{name} with UID serial_number={serial_number!r} already in use.")
+ else:
+ my_class._assigned_cameras.add(serial_number)
- my_class._assigned_cameras.add(serial_number)
+ self._info = dict()
super().__init__(name, *args, **kwargs)
self._address = my_class._cameras[self.uid]
self.connect()
if not self.is_connected:
- raise error.PanError("Could not connect to {}.".format(self))
+ raise error.PanError(f"Could not connect to {self}.")
if filter_type:
# connect() will have set this based on camera info, but that doesn't know about filters
@@ -117,8 +125,11 @@ def __init__(self,
if target_temperature is not None:
if self.is_cooled_camera:
self.target_temperature = target_temperature
+
+ # Setting this will call _check_temperature_stability
self.cooling_enabled = True
- # Allow for cooling
+
+ # Block here while camera temperature stabilises
while self.is_temperature_stable is False:
time.sleep(0.5)
@@ -129,10 +140,12 @@ def __init__(self,
self.logger.warning(msg)
def __del__(self):
- """ Attempt some clean up """
- with suppress(AttributeError):
- uid = self.uid
- type(self)._assigned_cameras.discard(uid)
+ """ Attempt some clean up. """
+ if hasattr(self, 'uid'):
+ logger.trace(f'Removing {self.uid} from {type(self)._assigned_cameras}')
+ type(self)._assigned_cameras.discard(self.uid)
+
+ logger.trace(f'Assigned cameras after removing {type(self)._assigned_cameras}')
# Properties
@@ -143,21 +156,24 @@ def properties(self):
# Methods
- def _create_fits_header(self, seconds, dark):
- header = super()._create_fits_header(seconds, dark)
+ def _create_fits_header(self, seconds, dark=None):
+ header = super()._create_fits_header(seconds, dark=dark)
header.set('CAM-SDK', type(self)._driver.version, 'Camera SDK version')
return header
def __str__(self):
# SDK cameras don't have a port so just include the serial number in the string
# representation.
- s = "{} ({})".format(self.name, self.uid)
+ s = f"{self.name} ({self.uid})"
- if self.focuser:
- s += ' with {}'.format(self.focuser.name)
+ with suppress(AttributeError):
+ if self.focuser:
+ s += f' with {self.focuser.name}'
+ if self.filterwheel:
+ s += f' & {self.filterwheel.name}'
+
+ with suppress(AttributeError):
if self.filterwheel:
- s += ' & {}'.format(self.filterwheel.name)
- elif self.filterwheel:
- s += ' with {}'.format(self.filterwheel.name)
+ s += f' with {self.filterwheel.name}'
return s
diff --git a/src/panoptes/pocs/camera/simulator/__init__.py b/src/panoptes/pocs/camera/simulator/__init__.py
index 19b9acd72..e69de29bb 100644
--- a/src/panoptes/pocs/camera/simulator/__init__.py
+++ b/src/panoptes/pocs/camera/simulator/__init__.py
@@ -1 +0,0 @@
-from panoptes.pocs.camera.simulator.dslr import Camera
diff --git a/src/panoptes/pocs/camera/simulator_sdk/ccd.py b/src/panoptes/pocs/camera/simulator/ccd.py
similarity index 74%
rename from src/panoptes/pocs/camera/simulator_sdk/ccd.py
rename to src/panoptes/pocs/camera/simulator/ccd.py
index b38b46cdf..5b8a182eb 100644
--- a/src/panoptes/pocs/camera/simulator_sdk/ccd.py
+++ b/src/panoptes/pocs/camera/simulator/ccd.py
@@ -6,8 +6,10 @@
from contextlib import suppress
import astropy.units as u
-from panoptes.pocs.camera.simulator import Camera
+from panoptes.pocs.camera.simulator.dslr import Camera as SimCamera
from panoptes.pocs.camera.sdk import AbstractSDKDriver, AbstractSDKCamera
+from panoptes.utils.config.client import get_config
+from panoptes.utils.logging import logger
class SDKDriver(AbstractSDKDriver):
@@ -19,13 +21,19 @@ def get_SDK_version(self):
return "Simulated SDK Driver v0.001"
def get_devices(self):
- cameras = {'SSC007': 'DEV_USB0',
- 'SSC101': 'DEV_USB1',
- 'SSC999': 'DEV_USB2'}
- return cameras
+ logger.debug(f'Getting camera device connection config for {self}')
+ camera_devices = dict()
+ for cam_info in get_config('cameras.devices'):
+ name = cam_info.get('name') or cam_info.get('model')
+ port = cam_info.get('port') or cam_info.get('serial_number')
+ camera_devices[name] = port
+ logger.trace(f'camera_devices={camera_devices!r}')
-class Camera(AbstractSDKCamera, Camera, ABC):
+ return camera_devices
+
+
+class Camera(AbstractSDKCamera, SimCamera, ABC):
def __init__(self,
name='Simulated SDK camera',
driver=SDKDriver,
@@ -34,30 +42,14 @@ def __init__(self,
kwargs.update({'target_temperature': target_temperature})
super().__init__(name, driver, *args, **kwargs)
- @property
+ @AbstractSDKCamera.cooling_enabled.getter
def cooling_enabled(self):
return self._cooling_enabled
- @cooling_enabled.setter
- def cooling_enabled(self, enable):
- self._last_temp = self.temperature
- self._last_time = time.monotonic()
- self._cooling_enabled = bool(enable)
-
- @property
+ @AbstractSDKCamera.target_temperature.getter
def target_temperature(self):
return self._target_temperature
- @target_temperature.setter
- def target_temperature(self, target):
- # Upon init the camera won't have an existing temperature.
- with suppress(AttributeError):
- self._last_temp = self.temperature
- self._last_time = time.monotonic()
- if not isinstance(target, u.Quantity):
- target = target * u.Celsius
- self._target_temperature = target.to(u.Celsius)
-
@property
def temperature(self):
now = time.monotonic()
@@ -72,7 +64,7 @@ def temperature(self):
temperature = limit_temp - delta_temp * math.exp(-delta_time)
add_temp = random.uniform(-self._temp_var / 2, self._temp_var / 2)
temperature += random.uniform(-self._temp_var / 2, self._temp_var / 2)
- self.logger.trace(f"Temp adding {add_temp:.02f} \t Total: {temperature:.02f}")
+ self.logger.trace(f"Temp adding {add_temp:.02f} \t Total: {temperature:.02f} for {self}")
return temperature
@@ -80,14 +72,14 @@ def temperature(self):
def cooling_power(self):
if self.cooling_enabled:
return 100.0 * float((self._max_temp - self.temperature) /
- (self._max_temp - self._min_temp))
+ (self._max_temp - self._min_temp)) * u.percent
else:
- return 0.0
+ return 0.0 * u.percent
def connect(self):
self._is_cooled_camera = True
self._cooling_enabled = False
- self._temperature = 25 * u.Celsius
+ self._temperature = 5 * u.Celsius
self._max_temp = 25 * u.Celsius
self._min_temp = -15 * u.Celsius
self._temp_var = 0.05 * u.Celsius
@@ -95,3 +87,17 @@ def connect(self):
self._last_temp = 25 * u.Celsius
self._last_time = time.monotonic()
self._connected = True
+
+ def _set_target_temperature(self, target):
+ # Upon init the camera won't have an existing temperature.
+ with suppress(AttributeError):
+ self._last_temp = self.temperature
+ self._last_time = time.monotonic()
+ if not isinstance(target, u.Quantity):
+ target = target * u.Celsius
+ self._target_temperature = target.to(u.Celsius)
+
+ def _set_cooling_enabled(self, enable):
+ self._last_temp = self.temperature
+ self._last_time = time.monotonic()
+ self._cooling_enabled = bool(enable)
diff --git a/src/panoptes/pocs/camera/simulator/dslr.py b/src/panoptes/pocs/camera/simulator/dslr.py
index 11e8751ff..13f7008bf 100644
--- a/src/panoptes/pocs/camera/simulator/dslr.py
+++ b/src/panoptes/pocs/camera/simulator/dslr.py
@@ -1,24 +1,24 @@
import os
import random
-import time
-from abc import ABC
-
from threading import Timer
import numpy as np
-
from astropy import units as u
from astropy.io import fits
-
from panoptes.pocs.camera import AbstractCamera
from panoptes.utils.images import fits as fits_utils
-from panoptes.utils import get_quantity_value
+from panoptes.utils.time import CountdownTimer
+from panoptes.utils.utils import get_quantity_value
-class Camera(AbstractCamera, ABC):
+class Camera(AbstractCamera):
+
+ @property
+ def bit_depth(self):
+ return 12 * u.bit
def __init__(self, name='Simulated Camera', *args, **kwargs):
- kwargs['timeout'] = kwargs.get('timeout', 0.5 * u.second)
+ kwargs['timeout'] = kwargs.get('timeout', 1.5 * u.second)
kwargs['readout_time'] = kwargs.get('readout_time', 1.0 * u.second)
super().__init__(name=name, *args, **kwargs)
self.connect()
@@ -49,23 +49,22 @@ def take_observation(self, observation, headers=None, filename=None, *args, **kw
**kwargs)
def _end_exposure(self):
- self._is_exposing = False
+ self._is_exposing_event.clear()
- def _start_exposure(self, seconds=None, filename=None, dark=False, header=None, *args, **kwargs):
- exposure_thread = Timer(interval=get_quantity_value(seconds, unit=u.second) + 0.05,
+ def _start_exposure(self, seconds=None, filename=None, dark=False, header=None, *args,
+ **kwargs):
+ self._is_exposing_event.set()
+ exposure_thread = Timer(interval=get_quantity_value(seconds, unit=u.second),
function=self._end_exposure)
- self._is_exposing = True
exposure_thread.start()
readout_args = (filename, header)
return readout_args
def _readout(self, filename=None, header=None):
+ self.logger.debug(f'Calling _readout for {self}')
+ timer = CountdownTimer(duration=self.readout_time)
# Get example FITS file from test data directory
- file_path = os.path.join(
- os.environ['POCS'],
- 'tests', 'data',
- 'unsolved.fits'
- )
+ file_path = os.path.join(os.environ['POCS'], 'tests', 'data', 'unsolved.fits')
fake_data = fits.getdata(file_path)
if header.get('IMAGETYP') == 'Dark Frame':
@@ -73,11 +72,14 @@ def _readout(self, filename=None, header=None):
fake_data = np.random.randint(low=975, high=1026,
size=fake_data.shape,
dtype=fake_data.dtype)
- time.sleep(self.readout_time)
+ self.logger.debug(f'Writing filename={filename!r} for {self}')
fits_utils.write_fits(fake_data, header, filename)
- def _process_fits(self, file_path, info):
- file_path = super()._process_fits(file_path, info)
+ # Sleep for the remainder of the readout time.
+ timer.sleep()
+
+ def _process_fits(self, file_path, metadata):
+ file_path = super()._process_fits(file_path, metadata)
self.logger.debug('Overriding mount coordinates for camera simulator')
# TODO get the path as package data or something better.
solved_path = os.path.join(
@@ -95,3 +97,9 @@ def _process_fits(self, file_path, info):
self.logger.debug("Headers updated for simulated image.")
return file_path
+
+ def _set_target_temperature(self, target):
+ raise False
+
+ def _set_cooling_enabled(self, enable):
+ raise False
diff --git a/src/panoptes/pocs/camera/simulator_sdk/__init__.py b/src/panoptes/pocs/camera/simulator_sdk/__init__.py
deleted file mode 100644
index 78475f6dc..000000000
--- a/src/panoptes/pocs/camera/simulator_sdk/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from panoptes.pocs.camera.simulator_sdk.ccd import Camera
diff --git a/src/panoptes/pocs/camera/zwo.py b/src/panoptes/pocs/camera/zwo.py
index 1bb1ecd54..877d677d4 100644
--- a/src/panoptes/pocs/camera/zwo.py
+++ b/src/panoptes/pocs/camera/zwo.py
@@ -1,20 +1,18 @@
-import time
import threading
+import time
from contextlib import suppress
import numpy as np
from astropy import units as u
from astropy.time import Time
-
-from panoptes.pocs.camera.sdk import AbstractSDKCamera
from panoptes.pocs.camera.libasi import ASIDriver
-from panoptes.utils.images import fits as fits_utils
+from panoptes.pocs.camera.sdk import AbstractSDKCamera
from panoptes.utils import error
-from panoptes.utils import get_quantity_value
+from panoptes.utils.images import fits as fits_utils
+from panoptes.utils.utils import get_quantity_value
class Camera(AbstractSDKCamera):
-
_driver = None # Class variable to store the ASI driver interface
_cameras = [] # Cache of camera string IDs
_assigned_cameras = set() # Camera string IDs already in use.
@@ -44,7 +42,9 @@ def __init__(self,
its serial number or, if it doesn't have one, by the user set ID.
"""
kwargs['readout_time'] = kwargs.get('readout_time', 0.1)
- kwargs['timeout'] = kwargs.get('timeout', 0.5)
+ kwargs['timeout'] = kwargs.get('timeout', 5)
+ # ZWO cameras cannot take internal darks (not even supported in the API yet).
+ kwargs['internal_darks'] = kwargs.get('internal_darks', False)
self._video_event = threading.Event()
@@ -102,7 +102,7 @@ def temperature(self):
""" Current temperature of the camera's image sensor """
return self._control_getter('TEMPERATURE')[0]
- @property
+ @AbstractSDKCamera.target_temperature.getter
def target_temperature(self):
""" Current value of the target temperature for the camera's image sensor cooling control.
@@ -110,22 +110,11 @@ def target_temperature(self):
"""
return self._control_getter('TARGET_TEMP')[0]
- @target_temperature.setter
- def target_temperature(self, target):
- if not isinstance(target, u.Quantity):
- target = target * u.Celsius
- self.logger.debug("Setting {} cooling set point to {}".format(self, target))
- self._control_setter('TARGET_TEMP', target)
-
- @property
+ @AbstractSDKCamera.cooling_enabled.getter
def cooling_enabled(self):
""" Current status of the camera's image sensor cooling system (enabled/disabled) """
return self._control_getter('COOLER_ON')[0]
- @cooling_enabled.setter
- def cooling_enabled(self, enable):
- self._control_setter('COOLER_ON', enable)
-
@property
def cooling_power(self):
""" Current power level of the camera's image sensor cooling system (as a percentage). """
@@ -218,6 +207,13 @@ def stop_video(self):
# Private methods
+ def _set_target_temperature(self, target):
+ self._control_setter('TARGET_TEMP', target)
+ self._target_temperature = target
+
+ def _set_cooling_enabled(self, enable):
+ self._control_setter('COOLER_ON', enable)
+
def _video_readout(self,
width,
height,
@@ -298,10 +294,9 @@ def _readout(self, filename, width, height, header):
pad_bits = 16 - int(get_quantity_value(self.bit_depth, u.bit))
image_data = np.right_shift(image_data, pad_bits)
- fits_utils.write_fits(image_data,
- header,
- filename,
- self.logger)
+ fits_utils.write_fits(data=image_data,
+ header=header,
+ filename=filename)
elif exposure_status == 'FAILED':
raise error.PanError("Exposure failed on {}".format(self))
elif exposure_status == 'IDLE':
diff --git a/src/panoptes/pocs/core.py b/src/panoptes/pocs/core.py
index 61e4d07e9..ff5210bde 100644
--- a/src/panoptes/pocs/core.py
+++ b/src/panoptes/pocs/core.py
@@ -9,9 +9,9 @@
from panoptes.pocs.base import PanBase
from panoptes.pocs.observatory import Observatory
from panoptes.pocs.state.machine import PanStateMachine
-from panoptes.utils import current_time
-from panoptes.utils import get_free_space
-from panoptes.utils import CountdownTimer
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import get_free_space
+from panoptes.utils.time import CountdownTimer
class POCS(PanStateMachine, PanBase):
@@ -30,7 +30,7 @@ class POCS(PanStateMachine, PanBase):
observatory(Observatory): An instance of a `pocs.observatory.Observatory`
class. POCS will call the `initialize` method of the observatory.
state_machine_file(str): Filename of the state machine to use, defaults to
- 'simple_state_table'.
+ 'panoptes'.
simulators(list): A list of the different modules that can run in simulator mode. Possible
modules include: all, mount, camera, weather, night. Defaults to an empty list.
@@ -51,7 +51,7 @@ def __init__(
PanBase.__init__(self, *args, **kwargs)
if simulators:
- self.logger.warning(f'Using {simulators=}')
+ self.logger.warning(f'Using simulators={simulators!r}')
self.set_config('simulator', simulators)
assert isinstance(observatory, Observatory)
@@ -61,7 +61,7 @@ def __init__(
self.logger.info(f'Initializing PANOPTES unit - {self.name} - {location}')
if state_machine_file is None:
- state_machine_file = self.get_config('state_machine', default='simple_state_table')
+ state_machine_file = self.get_config('state_machine', default='panoptes')
self.logger.info(f'Making a POCS state machine from {state_machine_file}')
PanStateMachine.__init__(self, state_machine_file, **kwargs)
@@ -278,26 +278,34 @@ def reset_observing_run(self):
# Safety Methods
##################################################################################################
- def is_safe(self, no_warning=False, horizon='observe'):
+ def is_safe(self, no_warning=False, horizon='observe', ignore=None, park_if_not_safe=True):
"""Checks the safety flag of the system to determine if safe.
This will check the weather station as well as various other environmental
aspects of the system in order to determine if conditions are safe for operation.
Note:
- This condition is called by the state machine during each transition
+ This condition is called by the state machine during each transition.
Args:
no_warning (bool, optional): If a warning message should show in logs,
defaults to False.
horizon (str, optional): For night time check use given horizon,
default 'observe'.
+ ignore (abc.Iterable, optional): A list of safety checks to ignore when deciding
+ whether it is safe or not. Valid list entries are: 'ac_power', 'is_dark',
+ 'good_weather', 'free_space_root' and 'free_space_images'. Useful e.g. when
+ the state machine needs to wait for dark to enter the next state.
+ park_if_not_safe (bool, optional): If True (default), will go to park if safety check
+ fails. Set to False if you want to check the safety without sending the state
+ machine to parking.
Returns:
bool: Latest safety flag.
-
"""
if not self.connected:
return False
+ if ignore is None:
+ ignore = list()
is_safe_values = dict()
@@ -321,7 +329,13 @@ def is_safe(self, no_warning=False, horizon='observe'):
images_dir = self.get_config('directories.images')
is_safe_values['free_space_images'] = self.has_free_space(images_dir)
- safe = all(is_safe_values.values())
+ # Check overall safety, ignoring some checks if necessary
+ missing_keys = [k for k in ignore if k not in is_safe_values.keys()]
+ if missing_keys:
+ self.logger.warning("Found the following invalid checks to ignore in "
+ f"is_safe: {missing_keys}. Valid keys are: "
+ f"{list(is_safe_values.keys())}.")
+ safe = all([v for k, v in is_safe_values.items() if k not in ignore])
# Insert safety reading
self.db.insert_current('safety', is_safe_values)
@@ -331,14 +345,8 @@ def is_safe(self, no_warning=False, horizon='observe'):
self.logger.warning(f'Unsafe conditions: {is_safe_values}')
# These states are already "parked" so don't send to parking.
- safe_states = [
- 'parked',
- 'parking',
- 'sleeping',
- 'housekeeping',
- 'ready'
- ]
- if self.state not in safe_states:
+ state_always_safe = self.get_state(self.state).is_always_safe
+ if not state_always_safe and park_if_not_safe:
self.logger.warning('Safety failed so sending to park')
self.park()
@@ -443,9 +451,11 @@ def has_free_space(self, directory=None, required_space=0.25 * u.gigabyte, low_s
has_space = bool(self._free_space.value >= req_space.value)
if not has_space:
- self.logger.error(f'No disk space for {directory=}: Free {self._free_space:.02f}\t {req_space=:.02f}')
+ self.logger.error(f'No disk space for directory={directory!r}: '
+ f'Free {self._free_space:.02f}\t req_space={req_space:.02f}')
elif space_is_low: # pragma: no cover
- self.logger.warning(f'Low disk space for {directory=}: Free {self._free_space:.02f}\t {req_space=:.02f}')
+ self.logger.warning(f'Low disk space for directory={directory!r}: '
+ f'Free {self._free_space:.02f}\t req_space={req_space:.02f}')
return has_space
diff --git a/src/panoptes/pocs/dome/__init__.py b/src/panoptes/pocs/dome/__init__.py
index faf846257..e3b887c55 100644
--- a/src/panoptes/pocs/dome/__init__.py
+++ b/src/panoptes/pocs/dome/__init__.py
@@ -26,7 +26,7 @@ def create_dome_from_config(*args, **kwargs):
brand = dome_config['brand']
driver = dome_config['driver']
- logger.debug(f'Creating dome: {brand=}, {driver=}')
+ logger.debug(f'Creating dome: brand={brand!r}, driver={driver!r}')
module = load_module(f'panoptes.pocs.dome.{driver}')
dome = module.Dome(*args, **kwargs)
logger.info(f'Created dome driver: brand={brand}, driver={driver}')
@@ -40,11 +40,11 @@ def create_dome_simulator(*args, **kwargs):
brand = dome_config['brand']
driver = dome_config['driver']
- logger.debug(f'Creating dome simulator: {brand=}, {driver=}')
+ logger.debug(f'Creating dome simulator: brand={brand!r}, driver={driver!r}')
module = load_module(f'panoptes.pocs.dome.{driver}')
dome = module.Dome(*args, **kwargs)
- logger.info(f'Created dome driver: {brand=}, {driver=}')
+ logger.info(f'Created dome driver: brand={brand!r}, driver={driver!r}')
return dome
diff --git a/src/panoptes/pocs/dome/astrohaven.py b/src/panoptes/pocs/dome/astrohaven.py
index d756aac86..04e040e27 100644
--- a/src/panoptes/pocs/dome/astrohaven.py
+++ b/src/panoptes/pocs/dome/astrohaven.py
@@ -150,7 +150,7 @@ def _read_state_until_stable(self):
c = chr(data[-1])
if c in Protocol.STABLE_STATES:
return c
- self.logger.debug(f'_read_state_until_stable not yet stable: {data=!r}')
+ self.logger.debug(f'_read_state_until_stable not yet stable: data={data!r}')
if time.time() < end_by:
continue
pass
@@ -188,7 +188,7 @@ def _full_move(self, send, target_feedback, feedback_countdown=1):
c = chr(data[-1])
if c == target_feedback:
feedback_countdown -= 1
- self.logger.debug(f'Got target_feedback, {feedback_countdown=}')
+ self.logger.debug(f'Got target_feedback, feedback_countdown={feedback_countdown!r}')
if feedback_countdown <= 0:
# Woot! Moved the dome and got the desired response.
return True
@@ -199,12 +199,12 @@ def _full_move(self, send, target_feedback, feedback_countdown=1):
# we start seeing the echo of `send`.
pass
else: # pragma: no cover
- self.logger.warning(f'Unexpected value from dome! {send=!r} {target_feedback=!r} {data=!r}')
+ self.logger.warning(f'Unexpected value from dome! send={send!r} target_feedback={target_feedback!r} data={data!r}')
if time.time() < end_by:
continue
self.logger.error(
f'Timed out moving the dome. Check for hardware or communications problem. '
- f'{send=!r} {target_feedback=!r} {data=!r}')
+ f'send={send!r} target_feedback={target_feedback!r} data={data!r}')
return False
finally:
self.serial.ser.timeout = saved_timeout
diff --git a/src/panoptes/pocs/dome/bisque.py b/src/panoptes/pocs/dome/bisque.py
index 0ec21e0ee..d7f2131eb 100644
--- a/src/panoptes/pocs/dome/bisque.py
+++ b/src/panoptes/pocs/dome/bisque.py
@@ -5,7 +5,7 @@
from panoptes.pocs import dome
from panoptes.utils import error
-from panoptes.utils import theskyx
+from panoptes.pocs.utils import theskyx
from panoptes.utils.serializers import from_json
diff --git a/src/panoptes/pocs/filterwheel/filterwheel.py b/src/panoptes/pocs/filterwheel/filterwheel.py
index 359042768..d1a5d1cac 100644
--- a/src/panoptes/pocs/filterwheel/filterwheel.py
+++ b/src/panoptes/pocs/filterwheel/filterwheel.py
@@ -1,11 +1,12 @@
import threading
-from abc import ABCMeta, abstractmethod
+from abc import ABCMeta
+from abc import abstractmethod
+from contextlib import suppress
from astropy import units as u
-
from panoptes.pocs.base import PanBase
-from panoptes.utils import listify
from panoptes.utils import error
+from panoptes.utils.utils import listify
class AbstractFilterWheel(PanBase, metaclass=ABCMeta):
@@ -21,6 +22,9 @@ class AbstractFilterWheel(PanBase, metaclass=ABCMeta):
a Quantity with time units. If a numeric type without units is given seconds will be
assumed. Default is None (no timeout).
serial_number (str, optional): serial number of the filter wheel, default 'XXXXXX'
+ dark_position (int or str, optional): used to specify either a filter wheel position or
+ a filter name that should be used when taking dark exposures with a camera that is
+ not able to take internal darks.
"""
def __init__(self,
@@ -30,6 +34,7 @@ def __init__(self,
filter_names=None,
timeout=None,
serial_number='XXXXXX',
+ dark_position=None,
*args, **kwargs):
super().__init__(*args, **kwargs)
@@ -43,11 +48,18 @@ def __init__(self,
self.logger.error(msg)
raise ValueError(msg)
self._n_positions = len(filter_names)
- if isinstance(timeout, u.Quantity):
- self._timeout = timeout.to(u.second).value
- else:
+ try:
+ self._timeout = timeout.to_value(unit=u.second)
+ except AttributeError:
self._timeout = timeout
self._serial_number = serial_number
+ if dark_position is not None:
+ # Will raise ValueError is dark_position is not a valid position for this filterwheel
+ self._dark_position = self._parse_position(dark_position)
+ else:
+ self._dark_position = None
+
+ self._last_light_position = None
self._connected = False
# Some filter wheels needs this to track whether they are moving or not.
@@ -56,9 +68,9 @@ def __init__(self,
self.logger.debug('Filter wheel created: {}'.format(self))
-##################################################################################################
-# Properties
-##################################################################################################
+ ##################################################################################################
+ # Properties
+ ##################################################################################################
@property
def model(self):
@@ -101,10 +113,10 @@ def camera(self):
@camera.setter
def camera(self, camera):
- if self._camera:
- self.logger.warning("{} assigned to {}, skipping attempted assignment to {}!",
- self, self.camera, camera)
- else:
+ if self._camera and self._camera.uid != camera.uid:
+ self.logger.warning(f"{self} assigned to {self.camera.name}, "
+ f"skipping attempted assignment to {camera.name}!")
+ elif self._camera:
self._camera = camera
@property
@@ -131,8 +143,8 @@ def position(self, position):
def current_filter(self):
""" Name of the filter in the current position """
try:
- filter_name = self.filter_names[self.position - 1] # 1 based numbering
- except (IndexError, TypeError):
+ filter_name = self.filter_name(self.position)
+ except ValueError:
# Some filter wheels sometimes cannot return their current position
filter_name = "UNKNOWN"
return filter_name
@@ -145,16 +157,22 @@ def current_filter(self, filter_name):
def is_unidirectional(self):
raise NotImplementedError
-##################################################################################################
-# Methods
-##################################################################################################
+ ##################################################################################################
+ # Methods
+ ##################################################################################################
@abstractmethod
def connect(self):
""" Connect to filter wheel """
raise NotImplementedError
- def move_to(self, position, blocking=False):
+ def filter_name(self, position):
+ """ Name of the filter in the given integer position. """
+ # Validate input by passing it through _parse_position(), may raise ValueError
+ int_position = self._parse_position(position)
+ return self.filter_names[int_position - 1]
+
+ def move_to(self, new_position, blocking=False):
"""
Move the filter wheel to the given position.
@@ -164,13 +182,16 @@ def move_to(self, position, blocking=False):
of the names in the filter_names list, provided that this produces only one match.
Args:
- position (int or str): position to move to.
+ new_position (int or str): position to move to.
blocking (bool, optional): If False (default) return immediately, if True block until
the filter wheel move has been completed.
Returns:
threading.Event: Event that will be set to signal when the move has completed
+ Raise:
+ ValueError: if new_position is not a valid position specifier for this filterwheel.
+
Examples:
Substring matching is useful when the filter names contain both the type of filter
and a serial number, e.g. the following selects a g band filter without having to
@@ -196,28 +217,56 @@ def move_to(self, position, blocking=False):
self.logger.error(msg)
raise error.PanError(msg)
- position = self._parse_position(position)
- self.logger.info("Moving {} to position {} ({})".format(
- self, position, self.filter_names[position - 1]))
+ # Will raise a ValueError at this point if new_position is not a valid position
+ new_position = self._parse_position(new_position)
- if position == self.position:
+ if new_position == self.position:
# Already at requested position, don't go nowhere.
+ self.logger.debug(f"{self} already at position {new_position}"
+ f" ({self.filter_name(new_position)})")
return self._move_event
+ # Store current position so we can revert back with move_to_light_position()
+ if new_position == self._dark_position:
+ self._last_light_position = self.position
+ else:
+ self._last_light_position = new_position
+
+ self.logger.info("Moving {} to position {} ({})".format(
+ self, new_position, self.filter_name(new_position)))
self._move_event.clear()
- self._move_to(position) # Private method to actually perform the move.
+ self._move_to(new_position) # Private method to actually perform the move.
if blocking:
self._move_event.wait()
return self._move_event
-##################################################################################################
-# Private methods
-##################################################################################################
+ def move_to_dark_position(self, blocking=False):
+ """ Move to filterwheel position for taking darks. """
+ try:
+ self.logger.debug(f"Ensuring filterwheel {self} is at dark position.")
+ return self.move_to(self._dark_position, blocking=blocking)
+ except ValueError:
+ msg = f"Request to move to dark position but {self} has no dark_position set."
+ raise error.NotFound(msg)
+
+ def move_to_light_position(self, blocking=False):
+ """ Return to last filterwheel position from before taking darks. """
+ try:
+ self.logger.debug(f"Ensuring filterwheel {self} is not at dark position.")
+ return self.move_to(self._last_light_position, blocking=blocking)
+ except ValueError:
+ msg = f"Request to revert to last light position but {self} has" + \
+ "no light position stored."
+ raise error.NotFound(msg)
+
+ ##################################################################################################
+ # Private methods
+ ##################################################################################################
@abstractmethod
- def _move_to(self, position, move_event):
+ def _move_to(self, position):
raise NotImplementedError
def _parse_position(self, position):
@@ -226,7 +275,7 @@ def _parse_position(self, position):
If position is a string it will search the list of filter names for one that begins with
that string and return the corresponding integer position, otherwise (or if there is no
- match) it will do an explicity case to an integer.
+ match) it will do an explicit cast to an integer.
"""
int_position = None
if isinstance(position, str):
@@ -246,8 +295,8 @@ def _parse_position(self, position):
# Not a string or no match. Try to use as an integer position number.
try:
int_position = int(position)
- except ValueError:
- msg = "No match for '{}' in filter_names, & not an integer either".format(position)
+ except (ValueError, TypeError):
+ msg = f"No match for '{position}' in filter_names and not an integer either"
self.logger.error(msg)
raise ValueError(msg)
@@ -266,12 +315,13 @@ def _add_fits_keywords(self, header):
return header
def __str__(self):
+ s = f'{self.name} ({self.uid})'
+
try:
- if self.camera:
- s = "{} ({}) on {}".format(self.name, self.uid, self.camera.uid)
- else:
- s = "{} ({})".format(self.name, self.uid)
- except Exception:
+ with suppress(AttributeError):
+ s += f' [Camera: {self.camera.name}]'
+ except Exception as e: # noqa
+ self.logger.warning(f'Unable to stringify filterwheel: e={e!r}')
s = str(self.__class__)
return s
diff --git a/src/panoptes/pocs/filterwheel/libefw.py b/src/panoptes/pocs/filterwheel/libefw.py
index 09a6d64be..07cf86875 100644
--- a/src/panoptes/pocs/filterwheel/libefw.py
+++ b/src/panoptes/pocs/filterwheel/libefw.py
@@ -6,7 +6,7 @@
from panoptes.pocs.camera.sdk import AbstractSDKDriver
from panoptes.utils import error
from panoptes.utils.library import load_c_library
-from panoptes.utils import CountdownTimer
+from panoptes.utils.time import CountdownTimer
class EFWDriver(AbstractSDKDriver):
diff --git a/src/panoptes/pocs/filterwheel/simulator.py b/src/panoptes/pocs/filterwheel/simulator.py
index 52361f558..c635099c0 100644
--- a/src/panoptes/pocs/filterwheel/simulator.py
+++ b/src/panoptes/pocs/filterwheel/simulator.py
@@ -30,7 +30,7 @@ class FilterWheel(AbstractFilterWheel):
def __init__(self,
name='Simulated Filter Wheel',
- model='simulator',
+ model='panoptes.pocs.filterwheel.simulator.FilterWheel',
camera=None,
filter_names=None,
timeout=10 * u.second,
diff --git a/src/panoptes/pocs/focuser/astromechanics.py b/src/panoptes/pocs/focuser/astromechanics.py
new file mode 100644
index 000000000..4e426555f
--- /dev/null
+++ b/src/panoptes/pocs/focuser/astromechanics.py
@@ -0,0 +1,168 @@
+from panoptes.pocs.focuser.serial import AbstractSerialFocuser
+
+
+class Focuser(AbstractSerialFocuser):
+ """
+ Focuser class for control of a Canon DSLR lens via an Astromechanics Engineering Canon EF/EF-S adapter.
+
+ Args:
+ name (str, optional): default 'Astromechanics Focuser'
+ model (str, optional): default 'Canon EF/EF-S'
+
+ Additional positonal and keyword arguments are passed to the base class, AbstractSerialFocuser. See
+ that class' documentation for a complete list.
+
+ Min/max commands do not exist for the astromechanics controller, as well as
+ other commands to get serial numbers and library/hardware versions. However,
+ as they are marked with the decorator @abstractmethod, we have to override them.
+ """
+
+ def __init__(self,
+ name='Astromechanics Focuser Controller',
+ model='astromechanics',
+ *args, **kwargs):
+ super().__init__(name=name, model=model, *args, **kwargs)
+ self.logger.debug('Initialising Astromechanics Lens Controller')
+
+ ##################################################################################################
+ # Properties
+ ##################################################################################################
+
+ @AbstractSerialFocuser.position.getter
+ def position(self):
+ """
+ Returns current focus position in the lens focus encoder units.
+ """
+ response = self._send_command("P#").rstrip("#")
+ return int(response)
+
+ @property
+ def min_position(self):
+ """
+ Returns position of close limit of focus travel, in encoder units.
+ """
+ return self._min_position
+
+ @property
+ def max_position(self):
+ """
+ Returns position of far limit of focus travel, in encoder units.
+ """
+ return None
+
+ ##################################################################################################
+ # Public Methods
+ ##################################################################################################
+
+ def connect(self, port):
+
+ self._connect(port, baudrate=38400)
+
+ # Return string that makes it clear there is no serial number
+ return "no_serial_number_available"
+
+ def move_to(self, new_position):
+ """
+ Moves focuser to a new position.
+
+ Args:
+ position (int): new focuser position, in encoder units
+
+ Returns:
+ int: focuser position following the move, in encoder units.
+
+ Does not do any checking of the requested position but will warn if the lens reports
+ hitting a stop.
+ """
+ self._is_moving = True
+ try:
+ self._send_command(f'M{int(new_position):d}#')
+ finally:
+ # Focuser move commands block until the move is finished, so if the command has
+ # returned then the focuser is no longer moving.
+ self._is_moving = False
+
+ self.logger.debug(f"Moved to encoder position {self.position}")
+ return self.position
+
+ def move_by(self, increment):
+ """
+ Move focuser by a given amount.
+
+ Args:
+ increment (int): distance to move the focuser, in encoder units.
+
+ Returns:
+ int: distance moved, in encoder units.
+ """
+ self._is_moving = True
+ try:
+ new_pos = self.position + increment
+ self._send_command(f'M{int(new_pos):d}#')
+ finally:
+ # Focuser move commands block until the move is finished, so if the command has
+ # returned then the focuser is no longer moving.
+ self._is_moving = False
+
+ self.logger.debug(f"Moved by {increment} encoder units. Current position is {new_pos}")
+ return new_pos
+
+ ##################################################################################################
+ # Private Methods
+ ##################################################################################################
+
+ def _send_command(self, command):
+ """
+ Sends a command to the focuser adaptor and retrieves the response.
+
+ Args:
+ command (string): command string to send (without newline), e.g. 'P#'
+ response length (integer, optional, default=None): number of lines of response expected.
+ For most commands this should be 0 or 1. If None readlines() will be called to
+ capture all responses. As this will block until the timeout expires it should only
+ be used if the number of lines expected is not known (e.g. 'ds' command).
+
+ Returns:
+ string: containing the '\r' terminated lines of the response from the adaptor.
+ """
+ if not self.is_connected:
+ self.logger.critical("Attempt to send command to {} when not connected!".format(self))
+ return
+
+ # Clear the input buffer in case there's anything left over in there.
+ self._serial_port.reset_input_buffer()
+
+ # Send command
+ self._serial_io.write(command + '\r')
+
+ return self._serial_io.readline()
+
+ def _initialise(self):
+ self._is_moving = True
+ try:
+ # Initialise the aperture motor. This also has the side effect of fully opening the iris.
+ self._initialise_aperture()
+
+ # Initalise focus. First move the focus to the close stop.
+ self._move_zero()
+ self._min_position = 0
+
+ self.logger.info(f'{self} initialised')
+ finally:
+ self._is_moving = False
+
+ def _initialise_aperture(self):
+ self.logger.debug('Initialising aperture motor')
+ self._send_command('A00#')
+ self.logger.debug('Aperture initialised')
+
+ def _move_zero(self):
+ self.logger.debug('Setting focus encoder zero point')
+ self._is_moving = True
+ try:
+ # Set focuser to 0 position
+ self._send_command('M0#')
+
+ self.logger.debug('Moved to encoder position 0')
+ finally:
+ self._is_moving = False
diff --git a/src/panoptes/pocs/focuser/birger.py b/src/panoptes/pocs/focuser/birger.py
index 2140807bd..732ddb024 100644
--- a/src/panoptes/pocs/focuser/birger.py
+++ b/src/panoptes/pocs/focuser/birger.py
@@ -1,11 +1,9 @@
-import io
import re
import serial
import glob
from warnings import warn
-from contextlib import suppress
-from panoptes.pocs.focuser import AbstractFocuser
+from panoptes.pocs.focuser.serial import AbstractSerialFocuser
from panoptes.utils import error
# Birger adaptor serial numbers should be 5 digits
@@ -42,7 +40,7 @@
'Distance stops not supported by the lens')
-class Focuser(AbstractFocuser):
+class Focuser(AbstractSerialFocuser):
"""
Focuser class for control of a Canon DSLR lens via a Birger Engineering Canon EF-232 adapter.
@@ -60,19 +58,16 @@ class Focuser(AbstractFocuser):
that class' documentation for a complete list.
"""
- # Class variable to cache the device node scanning results
- _birger_nodes = None
-
- # Class variable to store the device nodes already in use. Prevents scanning known Birgers &
- # acts as a check against Birgers assigned to incorrect ports.
- _assigned_nodes = []
-
def __init__(self,
name='Birger Focuser',
model='Canon EF-232',
initial_position=None,
dev_node_pattern='/dev/tty.USA49*.?',
+ max_command_retries=5,
*args, **kwargs):
+
+ self._max_command_retries = max_command_retries
+
super().__init__(name=name, model=model, *args, **kwargs)
self.logger.debug('Initialising Birger focuser')
@@ -80,10 +75,10 @@ def __init__(self,
# Have been given a serial number
self.logger.debug('Looking for {} ({})...'.format(self.name, self.port))
- if Focuser._birger_nodes is None:
+ if Focuser._adaptor_nodes is None:
# No cached device nodes scanning results, need to scan.
self.logger.debug('Getting serial numbers for all connected Birger focusers')
- Focuser._birger_nodes = {}
+ Focuser._adaptor_nodes = {}
# Find nodes matching pattern
device_nodes = glob.glob(dev_node_pattern)
@@ -91,24 +86,24 @@ def __init__(self,
for device_node in device_nodes:
try:
serial_number = self.connect(device_node)
- Focuser._birger_nodes[serial_number] = device_node
+ Focuser._adaptor_nodes[serial_number] = device_node
except (serial.SerialException, serial.SerialTimeoutException, AssertionError):
# No Birger focuser on this node.
pass
finally:
self._serial_port.close()
- if not Focuser._birger_nodes:
+ if not Focuser._adaptor_nodes:
message = 'No Birger focuser devices found!'
self.logger.error(message)
warn(message)
return
else:
- self.logger.debug('Connected Birger focusers: {}'.format(Focuser._birger_nodes))
+ self.logger.debug('Connected Birger focusers: {}'.format(Focuser._adaptor_nodes))
# Search in cached device node scanning results for serial number
try:
- device_node = Focuser._birger_nodes[self.port]
+ device_node = Focuser._adaptor_nodes[self.port]
except KeyError:
message = 'Could not find {} ({})!'.format(self.name, self.port)
self.logger.error(message)
@@ -117,53 +112,14 @@ def __init__(self,
self.logger.debug('Found {} ({}) on {}'.format(self.name, self.port, device_node))
self.port = device_node
- # Check that this node hasn't already been assigned to another Birgers
- if self.port in Focuser._assigned_nodes:
- message = 'Device node {} already in use!'.format(self.port)
- self.logger.error(message)
- warn(message)
- return
-
- try:
- self.connect(self.port)
- except (serial.SerialException,
- serial.SerialTimeoutException,
- AssertionError) as err:
- message = 'Error connecting to {} on {}: {}'.format(self.name, self.port, err)
- self.logger.error(message)
- warn(message)
- return
-
- Focuser._assigned_nodes.append(self.port)
- self._is_moving = False
- self._initialise()
if initial_position is not None:
self.position = initial_position
- def __del__(self):
- with suppress(AttributeError):
- device_node = self.port
- Focuser._assigned_nodes.remove(device_node)
- self.logger.debug('Removed {} from assigned nodes list'.fomat(device_node))
- with suppress(AttributeError):
- self._serial_port.close()
- self.logger.debug('Closed serial port {}'.format(self._port))
-
##################################################################################################
# Properties
##################################################################################################
- @property
- def is_connected(self):
- """
- Checks status of serial port to determine if connected.
- """
- connected = False
- if self._serial_port:
- connected = self._serial_port.isOpen()
- return connected
-
- @AbstractFocuser.position.getter
+ @AbstractSerialFocuser.position.getter
def position(self):
"""
Returns current focus position in the lens focus encoder units.
@@ -206,46 +162,13 @@ def hardware_version(self):
"""
return self._hardware_version
- @property
- def is_moving(self):
- """ True if the focuser is currently moving. """
- return self._is_moving
-
##################################################################################################
# Public Methods
##################################################################################################
def connect(self, port):
- try:
- # Configure serial port.
- # Settings copied from Bob Abraham's birger.c
- self._serial_port = serial.Serial()
- self._serial_port.port = port
- self._serial_port.baudrate = 115200
- self._serial_port.bytesize = serial.EIGHTBITS
- self._serial_port.parity = serial.PARITY_NONE
- self._serial_port.stopbits = serial.STOPBITS_ONE
- self._serial_port.timeout = 2.0
- self._serial_port.xonxoff = False
- self._serial_port.rtscts = False
- self._serial_port.dsrdtr = False
- self._serial_port.write_timeout = None
- self._inter_byte_timeout = None
-
- # Establish connection
- self._serial_port.open()
-
- except serial.SerialException as err:
- self._serial_port = None
- self.logger.critical('Could not open {}!'.format(port))
- raise err
-
- # Want to use a io.TextWrapper in order to have a readline() method with universal newlines
- # (Birger sends '\r', not '\n'). The line_buffering option causes an automatic flush() when
- # a write contains a newline character.
- self._serial_io = io.TextIOWrapper(io.BufferedRWPair(self._serial_port, self._serial_port),
- newline='\r', encoding='ascii', line_buffering=True)
- self.logger.debug('Established serial connection to {} on {}.'.format(self.name, port))
+
+ self._connect(port, baudrate=115200)
# Set 'verbose' and 'legacy' response modes. The response from this depends on
# what the current mode is... but after a power cycle it should be 'rm1,0', 'OK'
@@ -308,9 +231,9 @@ def move_by(self, increment):
# Private Methods
##################################################################################################
- def _send_command(self, command, response_length=None, ignore_response=False):
+ def _send_command(self, command, response_length=None):
"""
- Sends a command to the Birger adaptor and retrieves the response.
+ Sends a command to the focuser adaptor and retrieves the response.
Args:
command (string): command string to send (without newline), e.g. 'fa1000', 'pf'
@@ -327,42 +250,52 @@ def _send_command(self, command, response_length=None, ignore_response=False):
self.logger.critical("Attempt to send command to {} when not connected!".format(self))
return
- # Clear the input buffer in case there's anything left over in there.
- self._serial_port.reset_input_buffer()
+ # Depending on which command was sent there may or may not be any further response.
+ response = []
+
+ # Success variable to verify that the command sent is read by the focuser.
+ success = False
- # Send command
- self._serial_io.write(command + '\r')
+ for i in range(self._max_command_retries):
+ # Clear the input buffer in case there's anything left over in there.
+ self._serial_port.reset_input_buffer()
- if ignore_response:
- return
+ # Send the command
+ self._serial_io.write(command + '\r')
- # In verbose mode adaptor will first echo the command
- echo = self._serial_io.readline().rstrip()
- assert echo == command, self.logger.warning("echo != command: {} != {}".format(
- echo, command))
+ # In verbose mode adaptor will first echo the command
+ echo = self._serial_io.readline().rstrip()
- # Adaptor should then send 'OK', even if there was an error.
- ok = self._serial_io.readline().rstrip()
- assert ok == 'OK'
+ if echo != command:
+ self.logger.warning(f'echo != command: {echo!r} != {command!r}. Retrying command.')
+ continue
- # Depending on which command was sent there may or may not be any further
- # response.
- response = []
+ # Adaptor should then send 'OK', even if there was an error.
+ ok = self._serial_io.readline().rstrip()
+ if ok != 'OK':
+ self.logger.warning(f"ok != 'OK': {ok!r} != 'OK'. Retrying command.")
+ continue
- if response_length == 0:
- # Not expecting any further response. Should check the buffer anyway in case an error
- # message has been sent.
- if self._serial_port.in_waiting:
- response.append(self._serial_io.readline())
+ if response_length == 0:
+ # Not expecting any further response. Should check the buffer anyway in case an
+ # error message has been sent.
+ if self._serial_port.in_waiting:
+ response.append(self._serial_io.readline())
- elif response_length > 0:
- # Expecting some number of lines of response. Attempt to read that many lines.
- for i in range(response_length):
- response.append(self._serial_io.readline())
+ elif response_length > 0:
+ # Expecting some number of lines of response. Attempt to read that many lines.
+ for i in range(response_length):
+ response.append(self._serial_io.readline())
- else:
- # Don't know what to expect. Call readlines() to get whatever is there.
- response.append(self._serial_io.readlines())
+ else:
+ # Don't know what to expect. Call readlines() to get whatever is there.
+ response.extend(self._serial_io.readlines())
+
+ success = True
+ break
+
+ if not success:
+ raise error.PanError(f'Failed command {command!r} on {self}')
# Check for an error message in response
if response:
@@ -461,12 +394,12 @@ def _initialise_aperture(self):
self.logger.debug('Initialising aperture motor')
response = self._send_command('in', response_length=1)[0].rstrip()
if response != 'DONE':
- self.logger.error(f"{self} got {response=}, expected 'DONE'!")
+ self.logger.error(f"{self} got response={response!r}, expected 'DONE'!")
def _move_zero(self):
response = self._send_command('mz', response_length=1)[0].rstrip()
if response[:4] != 'DONE':
- self.logger.error(f"{self} got {response=}, expected 'DONENNNNN,1'!")
+ self.logger.error(f"{self} got response={response!r}, expected 'DONENNNNN,1'!")
else:
r = response[4:].rstrip()
self.logger.debug(f"Moved {r[:-2]} encoder units to close stop")
@@ -480,12 +413,12 @@ def _learn_focus_range(self):
self.logger.debug('Learning absolute focus range')
response = self._send_command('la', response_length=1)[0].rstrip()
if response != 'DONE:LA':
- self.logger.error(f"{self} got {response=}, expected 'DONE:LA'!")
+ self.logger.error(f"{self} got response={response!r}, expected 'DONE:LA'!")
def _move_inf(self):
response = self._send_command('mi', response_length=1)[0].rstrip()
if response[:4] != 'DONE':
- self.logger.error(f"{self} got {response=}, expected 'DONENNNNN,1'!")
+ self.logger.error(f"{self} got response={response!r}, expected 'DONENNNNN,1'!")
else:
r = response[4:].rstrip()
self.logger.debug(f"Moved {r[:-2]} encoder units to far stop")
diff --git a/src/panoptes/pocs/focuser/focuser.py b/src/panoptes/pocs/focuser/focuser.py
index 354821f01..c221a219a 100644
--- a/src/panoptes/pocs/focuser/focuser.py
+++ b/src/panoptes/pocs/focuser/focuser.py
@@ -4,20 +4,17 @@
from threading import Event
from threading import Thread
-from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
-from matplotlib.figure import Figure
-import matplotlib.colors as colours
-
import numpy as np
from scipy.ndimage import binary_dilation
from astropy.modeling import models
from astropy.modeling import fitting
from panoptes.pocs.base import PanBase
-from panoptes.utils import current_time
+from panoptes.utils.time import current_time
from panoptes.utils.images import focus as focus_utils
from panoptes.utils.images import mask_saturated
-from panoptes.utils.images.plot import get_palette
+
+from panoptes.pocs.utils.plotting import make_autofocus_plot
class AbstractFocuser(PanBase, metaclass=ABCMeta):
@@ -28,6 +25,7 @@ class AbstractFocuser(PanBase, metaclass=ABCMeta):
model (str, optional): model of the focuser
port (str, optional): port the focuser is connected to, e.g. a device node
camera (pocs.camera.Camera, optional): camera that this focuser is associated with.
+ timeout (int, optional): time to wait for response from focuser.
initial_position (int, optional): if given the focuser will move to this position
following initialisation.
autofocus_range ((int, int) optional): Coarse & fine focus sweep range, in encoder units
@@ -45,6 +43,8 @@ class AbstractFocuser(PanBase, metaclass=ABCMeta):
for the merit function.
autofocus_mask_dilations (int, optional): Number of iterations of dilation to perform on the
saturated pixel mask (determine size of masked regions), default 10
+ autofocus_make_plots (bool, optional: Whether to write focus plots to images folder,
+ default False.
"""
def __init__(self,
@@ -52,6 +52,7 @@ def __init__(self,
model='simulator',
port=None,
camera=None,
+ timeout=5,
initial_position=None,
autofocus_range=None,
autofocus_step=None,
@@ -62,6 +63,7 @@ def __init__(self,
autofocus_merit_function=None,
autofocus_merit_function_kwargs=None,
autofocus_mask_dilations=None,
+ autofocus_make_plots=False,
*args, **kwargs):
super().__init__(*args, **kwargs)
@@ -73,6 +75,8 @@ def __init__(self,
self._connected = False
self._serial_number = 'XXXXXX'
+ self.timeout = timeout
+
if initial_position is None:
self._position = None
else:
@@ -86,7 +90,9 @@ def __init__(self,
autofocus_take_dark,
autofocus_merit_function,
autofocus_merit_function_kwargs,
- autofocus_mask_dilations)
+ autofocus_mask_dilations,
+ autofocus_make_plots)
+ self._autofocus_error = None
self._camera = camera
@@ -127,8 +133,9 @@ def camera(self):
@camera.setter
def camera(self, camera):
if self._camera:
- self.logger.warning("{} assigned to {}, skipping attempted assignment to {}!",
- self, self.camera, camera)
+ if self._camera != camera:
+ self.logger.warning(f"{self} already assigned to {self._camera}, "
+ f"skipping attempted assignment to {camera}!")
else:
self._camera = camera
@@ -152,6 +159,11 @@ def is_ready(self):
# A focuser is 'ready' if it is not currently moving.
return not self.is_moving
+ @property
+ def autofocus_error(self):
+ """ Error message from the most recent autofocus or None, if there was no error."""
+ return self._autofocus_error
+
##################################################################################################
# Methods
##################################################################################################
@@ -169,14 +181,14 @@ def autofocus(self,
seconds=None,
focus_range=None,
focus_step=None,
- thumbnail_size=None,
+ cutout_size=None,
keep_files=None,
take_dark=None,
merit_function=None,
merit_function_kwargs=None,
mask_dilations=None,
coarse=False,
- make_plots=False,
+ make_plots=None,
blocking=False):
"""
Focuses the camera using the specified merit function. Optionally performs
@@ -190,7 +202,7 @@ def autofocus(self,
encoder units. Specify to override values from config.
focus_step (2-tuple, optional): Coarse & fine focus sweep steps, in
encoder units. Specify to override values from config.
- thumbnail_size (int, optional): Size of square central region of image
+ cutout_size (int, optional): Size of square central region of image
to use, default 500 x 500 pixels.
keep_files (bool, optional): If True will keep all images taken
during focusing. If False (default) will delete all except the
@@ -206,8 +218,9 @@ def autofocus(self,
saturated pixel mask (determine size of masked regions), default 10
coarse (bool, optional): Whether to perform a coarse focus, otherwise will perform
a fine focus. Default False.
- make_plots (bool, optional: Whether to write focus plots to images folder, default
- False.
+ make_plots (bool, optional): Whether to write focus plots to images folder. If not
+ given will fall back on value of `autofocus_make_plots` set on initialisation,
+ and if it wasn't set then will default to False.
blocking (bool, optional): Whether to block until autofocus complete, default False.
Returns:
@@ -242,9 +255,9 @@ def autofocus(self,
raise ValueError(
"No focus exposure time specified, aborting autofocus of {}!", self._camera)
- if not thumbnail_size:
+ if not cutout_size:
if self.autofocus_size:
- thumbnail_size = self.autofocus_size
+ cutout_size = self.autofocus_size
else:
raise ValueError(
"No focus thumbnail size specified, aborting autofocus of {}!", self._camera)
@@ -279,13 +292,16 @@ def autofocus(self,
else:
mask_dilations = 10
+ if make_plots is None:
+ make_plots = self.autofocus_make_plots
+
# Set up the focus parameters
focus_event = Event()
focus_params = {
'seconds': seconds,
'focus_range': focus_range,
'focus_step': focus_step,
- 'thumbnail_size': thumbnail_size,
+ 'cutout_size': cutout_size,
'keep_files': keep_files,
'take_dark': take_dark,
'merit_function': merit_function,
@@ -295,8 +311,10 @@ def autofocus(self,
'make_plots': make_plots,
'focus_event': focus_event,
}
+
focus_thread = Thread(target=self._autofocus, kwargs=focus_params)
focus_thread.start()
+
if blocking:
focus_event.wait()
@@ -306,7 +324,7 @@ def _autofocus(self,
seconds,
focus_range,
focus_step,
- thumbnail_size,
+ cutout_size,
keep_files,
take_dark,
merit_function,
@@ -326,44 +344,51 @@ def _autofocus(self,
focus_type = 'coarse'
initial_focus = self.position
- self.logger.debug("Beginning {} autofocus of {} - initial position: {}",
- focus_type, self._camera, initial_focus)
+ self.logger.debug(f"Beginning {focus_type} autofocus of {self._camera} - "
+ f"initial position: {initial_focus}")
# Set up paths for temporary focus files, and plots if requested.
image_dir = self.get_config('directories.images')
start_time = current_time(flatten=True)
- file_path_root = os.path.join(image_dir,
- 'focus',
- self._camera.uid,
- start_time)
+ file_path_root = os.path.join(image_dir, 'focus', self._camera.uid, start_time)
+
+ self._autofocus_error = None
- dark_thumb = None
+ dark_cutout = None
if take_dark:
- dark_path = os.path.join(file_path_root,
- '{}.{}'.format('dark', self._camera.file_extension))
- self.logger.debug('Taking dark frame {} on camera {}'.format(dark_path, self._camera))
+ dark_path = os.path.join(file_path_root, f'dark.{self._camera.file_extension}')
+ self.logger.debug(f'Taking dark frame {dark_path} on camera {self._camera}')
try:
- dark_thumb = self._camera.get_thumbnail(seconds,
- dark_path,
- thumbnail_size,
- keep_file=True,
- dark=True)
+ dark_cutout = self._camera.get_cutout(seconds,
+ dark_path,
+ cutout_size,
+ keep_file=True,
+ dark=True)
# Mask 'saturated' with a low threshold to remove hot pixels
- dark_thumb = mask_saturated(dark_thumb, threshold=0.3)
- except TypeError:
- self.logger.warning("Camera {} does not support dark frames!".format(self._camera))
- except Exception as e:
- self.logger.warning(f'Problem getting dark: {e!r}')
-
- # Take an image before focusing, grab a thumbnail from the centre and add it to the plot
- initial_fn = "{}_{}_{}.{}".format(initial_focus,
- focus_type,
- "initial",
- self._camera.file_extension)
+ dark_cutout = mask_saturated(dark_cutout,
+ threshold=0.3,
+ bit_depth=self.camera.bit_depth)
+ except Exception as err:
+ self.logger.error(f"Error taking dark frame: {err!r}")
+ self._autofocus_error = repr(err)
+ focus_event.set()
+ raise err
+
+ # Take an image before focusing, grab a cutout from the centre and add it to the plot
+ initial_fn = f"{initial_focus}-{focus_type}-initial.{self._camera.file_extension}"
initial_path = os.path.join(file_path_root, initial_fn)
- initial_thumbnail = self._camera.get_thumbnail(
- seconds, initial_path, thumbnail_size, keep_file=True)
+ try:
+ initial_cutout = self._camera.get_cutout(seconds, initial_path, cutout_size,
+ keep_file=True)
+ initial_cutout = mask_saturated(initial_cutout, bit_depth=self.camera.bit_depth)
+ if dark_cutout is not None:
+ initial_cutout = initial_cutout.astype(np.int32) - dark_cutout
+ except Exception as err:
+ self.logger.error(f"Error taking initial image: {err!r}")
+ self._autofocus_error = repr(err)
+ focus_event.set()
+ raise err
# Set up encoder positions for autofocus sweep, truncating at focus travel
# limits if required.
@@ -374,59 +399,72 @@ def _autofocus(self,
focus_range = focus_range[0]
focus_step = focus_step[0]
+ # Get focus steps.
focus_positions = np.arange(max(initial_focus - focus_range / 2, self.min_position),
min(initial_focus + focus_range / 2, self.max_position) + 1,
focus_step, dtype=np.int)
n_positions = len(focus_positions)
- thumbnails = np.zeros((n_positions, thumbnail_size, thumbnail_size),
- dtype=initial_thumbnail.dtype)
- masks = np.empty((n_positions, thumbnail_size, thumbnail_size), dtype=np.bool)
- metric = np.empty(n_positions)
+ # Set up empty array holders
+ cutouts = np.zeros((n_positions, cutout_size, cutout_size), dtype=initial_cutout.dtype)
+ masks = np.empty((n_positions, cutout_size, cutout_size), dtype=np.bool)
+ metrics = np.empty(n_positions)
# Take and store an exposure for each focus position.
for i, position in enumerate(focus_positions):
# Move focus, updating focus_positions with actual encoder position after move.
focus_positions[i] = self.move_to(position)
- # Take exposure
- focus_fn = "{}_{:02d}.{}".format(focus_positions[i], i, self._camera.file_extension)
+ focus_fn = f"{focus_positions[i]}-{i:02d}.{self._camera.file_extension}"
file_path = os.path.join(file_path_root, focus_fn)
- thumbnail = self._camera.get_thumbnail(
- seconds, file_path, thumbnail_size, keep_file=keep_files)
- masks[i] = mask_saturated(thumbnail).mask
- if dark_thumb is not None:
- thumbnail = thumbnail - dark_thumb
- thumbnails[i] = thumbnail
+ # Take exposure.
+ try:
+ cutouts[i] = self._camera.get_cutout(seconds, file_path, cutout_size,
+ keep_file=keep_files)
+ except Exception as err:
+ self.logger.error(f"Error taking image {i + 1}: {err!r}")
+ self._autofocus_error = repr(err)
+ focus_event.set()
+ raise err
+
+ masks[i] = mask_saturated(cutouts[i], bit_depth=self.camera.bit_depth).mask
+ self.logger.debug(f'Making master mask with binary dilation for {self._camera}')
master_mask = masks.any(axis=0)
master_mask = binary_dilation(master_mask, iterations=mask_dilations)
# Apply the master mask and then get metrics for each frame.
- for i, thumbnail in enumerate(thumbnails):
- thumbnail = np.ma.array(thumbnail, mask=master_mask)
- metric[i] = focus_utils.focus_metric(
- thumbnail, merit_function, **merit_function_kwargs)
-
+ for i, cutout in enumerate(cutouts):
+ self.logger.debug(f'Applying focus metric to cutout {i:02d}')
+ if dark_cutout is not None:
+ cutout = cutout.astype(np.float32) - dark_cutout
+ cutout = np.ma.array(cutout, mask=np.ma.mask_or(master_mask, np.ma.getmask(cutout)))
+ metrics[i] = focus_utils.focus_metric(cutout, merit_function, **merit_function_kwargs)
+ self.logger.debug(f'Focus metric for cutout {i:02d}: {metrics[i]}')
+
+ # Only fit a fine focus.
fitted = False
+ fitting_indices = [None, None]
- # Find maximum values
- imax = metric.argmax()
+ # Find maximum metric values.
+ imax = metrics.argmax()
if imax == 0 or imax == (n_positions - 1):
# TODO: have this automatically switch to coarse focus mode if this happens
- self.logger.warning(
- "Best focus outside sweep range, aborting autofocus on {}!".format(self._camera))
+ self.logger.warning(f"Best focus outside sweep range, stopping focus and using"
+ f" {focus_positions[imax]}")
best_focus = focus_positions[imax]
elif not coarse:
# Fit data around the maximum value to determine best focus position.
# Initialise models
shift = models.Shift(offset=-focus_positions[imax])
+ # Small initial coeffs with expected sign. Helps fitting start in the right direction.
poly = models.Polynomial1D(degree=4, c0=1, c1=0, c2=-1e-2, c3=0, c4=-1e-4,
fixed={'c0': True, 'c1': True, 'c3': True})
- scale = models.Scale(factor=metric[imax])
+ scale = models.Scale(factor=metrics[imax])
+ # https://docs.astropy.org/en/stable/modeling/compound-models.html?#model-composition
reparameterised_polynomial = shift | poly | scale
# Initialise fitter
@@ -438,100 +476,76 @@ def _autofocus(self,
# Fit models to data
fit = fitter(reparameterised_polynomial,
focus_positions[fitting_indices[0]:fitting_indices[1] + 1],
- metric[fitting_indices[0]:fitting_indices[1] + 1])
+ metrics[fitting_indices[0]:fitting_indices[1] + 1])
- best_focus = -fit.offset_0
+ # Get the encoder position of the best focus.
+ best_focus = np.abs(fit.offset_0)
fitted = True
- # Guard against fitting failures, force best focus to stay within sweep range
+ # Guard against fitting failures, force best focus to stay within sweep range.
min_focus = focus_positions[0]
max_focus = focus_positions[-1]
if best_focus < min_focus:
- self.logger.warning("Fitting failure: best focus {} below sweep limit {}",
- best_focus,
- min_focus)
-
+ self.logger.warning(f"Fitting failure: best focus {best_focus} below sweep limit"
+ f" {min_focus}")
best_focus = focus_positions[1]
if best_focus > max_focus:
- self.logger.warning("Fitting failure: best focus {} above sweep limit {}",
- best_focus,
- max_focus)
-
+ self.logger.warning(f"Fitting failure: best focus {best_focus} above sweep limit"
+ f" {max_focus}")
best_focus = focus_positions[-2]
else:
# Coarse focus, just use max value.
best_focus = focus_positions[imax]
+ # Move the focuser to best focus position.
final_focus = self.move_to(best_focus)
- final_fn = "{}_{}_{}.{}".format(final_focus,
- focus_type,
- "final",
- self._camera.file_extension)
+ # Get final cutout.
+ final_fn = f"{final_focus}-{focus_type}-final.{self._camera.file_extension}"
file_path = os.path.join(file_path_root, final_fn)
- final_thumbnail = self._camera.get_thumbnail(
- seconds, file_path, thumbnail_size, keep_file=True)
+ try:
+ final_cutout = self._camera.get_cutout(seconds, file_path, cutout_size,
+ keep_file=True)
+ final_cutout = mask_saturated(final_cutout, bit_depth=self.camera.bit_depth)
+ if dark_cutout is not None:
+ final_cutout = final_cutout.astype(np.int32) - dark_cutout
+ except Exception as err:
+ self.logger.error(f"Error taking final image: {err!r}")
+ self._autofocus_error = repr(err)
+ focus_event.set()
+ raise err
if make_plots:
- initial_thumbnail = mask_saturated(initial_thumbnail)
- final_thumbnail = mask_saturated(final_thumbnail)
- if dark_thumb is not None:
- initial_thumbnail = initial_thumbnail - dark_thumb
- final_thumbnail = final_thumbnail - dark_thumb
-
- fig = Figure()
- FigureCanvas(fig)
- fig.set_size_inches(9, 18)
-
- ax1 = fig.add_subplot(3, 1, 1)
- im1 = ax1.imshow(initial_thumbnail, interpolation='none',
- cmap=get_palette(), norm=colours.LogNorm())
- fig.colorbar(im1)
- ax1.set_title('Initial focus position: {}'.format(initial_focus))
-
- ax2 = fig.add_subplot(3, 1, 2)
- ax2.plot(focus_positions, metric, 'bo', label='{}'.format(merit_function))
+ line_fit = None
if fitted:
- fs = np.arange(focus_positions[fitting_indices[0]],
- focus_positions[fitting_indices[1]] + 1)
- ax2.plot(fs, fit(fs), 'b-', label='Polynomial fit')
-
- ax2.set_xlim(focus_positions[0] - focus_step / 2, focus_positions[-1] + focus_step / 2)
- u_limit = 1.10 * metric.max()
- l_limit = min(0.95 * metric.min(), 1.05 * metric.min())
- ax2.set_ylim(l_limit, u_limit)
- ax2.vlines(initial_focus, l_limit, u_limit, colors='k', linestyles=':',
- label='Initial focus')
- ax2.vlines(best_focus, l_limit, u_limit, colors='k', linestyles='--',
- label='Best focus')
-
- ax2.set_xlabel('Focus position')
- ax2.set_ylabel('Focus metric')
-
- ax2.set_title('{} {} focus at {}'.format(self._camera, focus_type, start_time))
- ax2.legend(loc='best')
-
- ax3 = fig.add_subplot(3, 1, 3)
- im3 = ax3.imshow(final_thumbnail, interpolation='none',
- cmap=get_palette(), norm=colours.LogNorm())
- fig.colorbar(im3)
- ax3.set_title('Final focus position: {}'.format(final_focus))
- plot_path = os.path.join(file_path_root, '{}_focus.png'.format(focus_type))
-
- fig.tight_layout()
- fig.savefig(plot_path, transparent=False)
-
- # explicitly close and delete figure
- fig.clf()
- del fig
-
- self.logger.info('{} focus plot for camera {} written to {}'.format(
- focus_type.capitalize(), self._camera, plot_path))
-
- self.logger.debug(
- 'Autofocus of {} complete - final focus position: {}', self._camera, final_focus)
+ focus_range = np.arange(focus_positions[fitting_indices[0]],
+ focus_positions[fitting_indices[1]] + 1)
+ fit_line = fit(focus_range)
+ line_fit = [focus_range, fit_line]
+
+ plot_title = f'{self._camera} {focus_type} focus at {start_time}'
+
+ # Make the plots
+ plot_path = os.path.join(file_path_root, f'{focus_type}-focus.png')
+ plot_path = make_autofocus_plot(plot_path,
+ initial_cutout,
+ final_cutout,
+ initial_focus,
+ final_focus,
+ focus_positions,
+ metrics,
+ merit_function,
+ plot_title=plot_title,
+ line_fit=line_fit
+ )
+
+ self.logger.info(f"{focus_type.capitalize()} focus plot for {self._camera} written to "
+ f" {plot_path}")
+
+ self.logger.debug(f"Autofocus of {self._camera} complete - final focus"
+ f" position: {final_focus}")
if focus_event:
focus_event.set()
@@ -547,7 +561,8 @@ def _set_autofocus_parameters(self,
autofocus_take_dark,
autofocus_merit_function,
autofocus_merit_function_kwargs,
- autofocus_mask_dilations):
+ autofocus_mask_dilations,
+ autofocus_make_plots):
# Moved to a separate private method to make it possible to override.
if autofocus_range:
self.autofocus_range = (int(autofocus_range[0]), int(autofocus_range[1]))
@@ -566,6 +581,7 @@ def _set_autofocus_parameters(self,
self.autofocus_merit_function = autofocus_merit_function
self.autofocus_merit_function_kwargs = autofocus_merit_function_kwargs
self.autofocus_mask_dilations = autofocus_mask_dilations
+ self.autofocus_make_plots = bool(autofocus_make_plots)
def _add_fits_keywords(self, header):
header.set('FOC-NAME', self.name, 'Focuser name')
diff --git a/src/panoptes/pocs/focuser/serial.py b/src/panoptes/pocs/focuser/serial.py
new file mode 100644
index 000000000..33c1d0735
--- /dev/null
+++ b/src/panoptes/pocs/focuser/serial.py
@@ -0,0 +1,107 @@
+import io
+import serial
+from warnings import warn
+from contextlib import suppress
+
+from panoptes.pocs.focuser import AbstractFocuser
+
+
+class AbstractSerialFocuser(AbstractFocuser):
+
+ # Class variable to cache the device node scanning results
+ _adaptor_nodes = None
+
+ # Class variable to store the device nodes already in use. Prevents scanning
+ # known focuser devices & acts as a check against adaptors assigned to incorrect ports.
+ _assigned_nodes = []
+
+ def __init__(self, *args, **kwargs):
+ """Initialize an AbstractSerialMount for the port defined in the config.
+ Opens a connection to the serial device, if it is valid.
+ """
+
+ super().__init__(*args, **kwargs)
+
+ # Check that this node hasn't already been assigned to another focuser device
+ if self.port in AbstractSerialFocuser._assigned_nodes:
+ message = 'Device node {} already in use!'.format(self.port)
+ self.logger.error(message)
+ warn(message)
+ return
+
+ try:
+ self.connect(self.port)
+ except (serial.SerialException,
+ serial.SerialTimeoutException,
+ AssertionError) as err:
+ message = 'Error connecting to {} on {}: {}'.format(self.name, self.port, err)
+ self.logger.error(message)
+ warn(message)
+ return
+
+ AbstractSerialFocuser._assigned_nodes.append(self.port)
+ self._is_moving = False
+ self._initialise()
+
+ def __del__(self):
+ with suppress(AttributeError):
+ device_node = self.port
+ AbstractSerialFocuser._assigned_nodes.remove(device_node)
+ self.logger.debug(f'Removed {device_node} from assigned nodes list')
+ with suppress(AttributeError):
+ self._serial_port.close()
+ self.logger.debug(f'Closed serial port {self._port}')
+
+ ##################################################################################################
+ # Properties
+ ##################################################################################################
+
+ @property
+ def is_connected(self):
+ """
+ Checks status of serial port to determine if connected.
+ """
+ connected = False
+ if self._serial_port:
+ connected = self._serial_port.isOpen()
+ return connected
+
+ @property
+ def is_moving(self):
+ """ True if the focuser is currently moving. """
+ return self._is_moving
+
+ ##################################################################################################
+ # Private Methods
+ ##################################################################################################
+
+ def _connect(self, port, baudrate):
+ try:
+ # Configure serial port.
+ self._serial_port = serial.Serial()
+ self._serial_port.port = port
+ self._serial_port.baudrate = baudrate
+ self._serial_port.bytesize = serial.EIGHTBITS
+ self._serial_port.parity = serial.PARITY_NONE
+ self._serial_port.stopbits = serial.STOPBITS_ONE
+ self._serial_port.timeout = self.timeout
+ self._serial_port.xonxoff = False
+ self._serial_port.rtscts = False
+ self._serial_port.dsrdtr = False
+ self._serial_port.write_timeout = None
+ self._inter_byte_timeout = None
+
+ # Establish connection
+ self._serial_port.open()
+
+ except serial.SerialException as err:
+ self._serial_port = None
+ self.logger.critical('Could not open {}!'.format(port))
+ raise err
+
+ # Want to use a io.TextWrapper in order to have a readline() method with universal newlines
+ # (focuser adaptors usually send '\r', not '\n'). The line_buffering option causes an automatic flush() when
+ # a write contains a newline character.
+ self._serial_io = io.TextIOWrapper(io.BufferedRWPair(self._serial_port, self._serial_port),
+ newline='\r', encoding='ascii', line_buffering=True)
+ self.logger.debug('Established serial connection to {} on {}.'.format(self.name, port))
diff --git a/src/panoptes/pocs/mount/bisque.py b/src/panoptes/pocs/mount/bisque.py
index cdc49fe74..ecf627611 100644
--- a/src/panoptes/pocs/mount/bisque.py
+++ b/src/panoptes/pocs/mount/bisque.py
@@ -1,15 +1,14 @@
import json
import os
import time
+from string import Template
+from threading import Lock
from astropy import units as u
from astropy.coordinates import SkyCoord
-from string import Template
-
-from panoptes.utils import error
-from panoptes.utils import theskyx
-
from panoptes.pocs.mount import AbstractMount
+from panoptes.pocs.utils import theskyx
+from panoptes.utils import error
from panoptes.utils.serializers import from_yaml
@@ -20,6 +19,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.theskyx = theskyx.TheSkyX()
+ self._command_lock = Lock()
+
template_dir = self.get_config('mount.template_dir')
if template_dir.startswith('/') is False:
template_dir = os.path.join(os.environ['POCS'], template_dir)
@@ -29,6 +30,40 @@ def __init__(self, *args, **kwargs):
self.template_dir = template_dir
+ ##########################################################################
+ # Properties
+ ##########################################################################
+
+ @property
+ def is_parked(self):
+ """ bool: Mount parked status. """
+ self._update_status()
+ return self._is_parked
+
+ @property
+ def is_home(self):
+ """ bool: Mount home status. """
+ self._update_status()
+ return self._is_home
+
+ @property
+ def is_tracking(self):
+ """ bool: Mount tracking status. """
+ self._update_status()
+ return self._is_tracking
+
+ @property
+ def is_slewing(self):
+ """ bool: Mount slewing status. """
+ self._update_status()
+ return self._is_slewing
+
+ @property
+ def at_mount_park(self):
+ """ bool: Mount slewing status. """
+ self._update_status()
+ return self._at_mount_park
+
##########################################################################
# Methods
##########################################################################
@@ -88,10 +123,18 @@ def initialize(self, unpark=False, *args, **kwargs):
return self.is_initialized
+ def query(self, *args, **kwargs):
+ """ Override the query method to use the command lock.
+
+ This is required because TheSkyX cannot handle simulataneous commands. This function will
+ block until the lock is released.
+ """
+ with self._command_lock:
+ return super().query(*args, **kwargs)
+
def _update_status(self):
""" """
status = self.query('get_status')
- self.logger.debug(f"Status: {status}")
try:
self._at_mount_park = status['parked']
@@ -101,8 +144,8 @@ def _update_status(self):
except KeyError:
self.logger.warning("Problem with status, key not found")
- if not self.is_parked:
- status.update(self.query('get_coordinates'))
+ status.update(self.query('get_coordinates'))
+ self.logger.debug(f"Mount status: {status}")
return status
@@ -172,16 +215,16 @@ def slew_to_target(self, timeout=120, **kwargs):
mount_coords = self._skycoord_to_mount_coord(self._target_coordinates)
# Send coordinates to mount
+ self.logger.info(f"Slewing to target coordinates: {mount_coords}")
try:
response = self.query('slew_to_coordinates', {
- 'ra': mount_coords[0],
- 'dec': mount_coords[1],
- }, timeout=timeout)
+ 'ra': mount_coords[0], 'dec': mount_coords[1]}, timeout=timeout)
success = response['success']
if success:
while self.is_slewing:
time.sleep(2)
-
+ else:
+ raise error.PanError(f"Slewing was unsuccessful: {response['response']}")
except Exception as e:
self.logger.warning(f"Problem slewing to mount coordinates: {mount_coords} {e}")
@@ -334,7 +377,8 @@ def _setup_commands(self, commands):
commands.update(from_yaml(f.read()))
self.logger.debug(f"Mount commands updated from {conf_file}")
except OSError as err:
- self.logger.warning(f'Cannot load commands config file: {conf_file} \n {err}')
+ self.logger.warning(
+ f'Cannot load commands config file: {conf_file} \n {err}')
except Exception:
self.logger.warning("Problem loading mount command file")
else:
diff --git a/src/panoptes/pocs/mount/ioptron.py b/src/panoptes/pocs/mount/ioptron.py
index b0c1b0dfa..727624be5 100644
--- a/src/panoptes/pocs/mount/ioptron.py
+++ b/src/panoptes/pocs/mount/ioptron.py
@@ -4,7 +4,7 @@
from astropy import units as u
from astropy.coordinates import SkyCoord
-from panoptes.utils import current_time
+from panoptes.utils.time import current_time
from panoptes.utils import error as error
from panoptes.pocs.mount.serial import AbstractSerialMount
diff --git a/src/panoptes/pocs/mount/mount.py b/src/panoptes/pocs/mount/mount.py
index cb0fc486d..7ad4cadae 100644
--- a/src/panoptes/pocs/mount/mount.py
+++ b/src/panoptes/pocs/mount/mount.py
@@ -7,9 +7,9 @@
from panoptes.pocs.base import PanBase
-from panoptes.utils import current_time
+from panoptes.utils.time import current_time
from panoptes.utils import error
-from panoptes.utils import CountdownTimer
+from panoptes.utils.time import CountdownTimer
class AbstractMount(PanBase):
@@ -170,6 +170,11 @@ def is_parked(self):
""" bool: Mount parked status. """
return self._is_parked
+ @property
+ def at_mount_park(self):
+ """ bool: True if mount is at park position. """
+ return self._at_mount_park
+
@property
def is_home(self):
""" bool: Mount home status. """
@@ -481,9 +486,9 @@ def slew_to_coordinates(self, coords, ra_rate=15.0, dec_rate=0.0, *args, **kwarg
Returns:
bool: indicating success
"""
- assert isinstance(coords, tuple), self.logger.warning(
- 'slew_to_coordinates expects RA-Dec coords')
-
+ if not isinstance(coords, SkyCoord):
+ raise TypeError("coords should be an instance of astropy.coordinates.SkyCoord,"
+ f" got {type(coords)}.")
response = 0
if not self.is_parked:
@@ -631,7 +636,7 @@ def park(self, *args, **kwargs):
else:
self.logger.warning('Problem with slew_to_park')
- while not self._at_mount_park:
+ while not self.at_mount_park:
self.status
time.sleep(2)
diff --git a/src/panoptes/pocs/mount/simulator.py b/src/panoptes/pocs/mount/simulator.py
index 16e05f303..1bdc94f4a 100644
--- a/src/panoptes/pocs/mount/simulator.py
+++ b/src/panoptes/pocs/mount/simulator.py
@@ -3,7 +3,7 @@
from astropy import units as u
-from panoptes.utils import current_time
+from panoptes.utils.time import current_time
from panoptes.utils import error
from panoptes.pocs.mount import AbstractMount
diff --git a/src/panoptes/pocs/observatory.py b/src/panoptes/pocs/observatory.py
index 9c22c5ccd..2a7b9bf70 100644
--- a/src/panoptes/pocs/observatory.py
+++ b/src/panoptes/pocs/observatory.py
@@ -15,7 +15,7 @@
from panoptes.pocs.scheduler import BaseScheduler
from panoptes.pocs.utils.location import create_location_from_config
-from panoptes.utils import current_time
+from panoptes.utils.time import current_time
from panoptes.utils import error
@@ -116,16 +116,16 @@ def primary_camera(self):
"""Return primary camera.
Note:
- If no camera has been marked as primary this will set and return
- the first camera in the OrderedDict as primary.
+ If no camera has been marked as primary this will return the first
+ camera in the OrderedDict as primary.
Returns:
`pocs.camera.Camera`: The primary camera.
"""
if not self._primary_camera and self.has_cameras:
- self._primary_camera = self.cameras[list(self.cameras.keys())[0]]
-
- return self._primary_camera
+ return self.cameras[list(self.cameras.keys())[0]]
+ else:
+ return self._primary_camera
@primary_camera.setter
def primary_camera(self, cam):
@@ -246,7 +246,7 @@ def _set_hardware(self, new_hardware, hw_type, hw_class):
setattr(self, hw_type, new_hardware)
elif new_hardware is None:
if hw_attr is not None:
- self.logger.success(f'Removing {hw_attr=}')
+ self.logger.success(f'Removing hw_attr={hw_attr!r}')
setattr(self, hw_type, None)
else:
raise TypeError(f"{hw_type.title()} is not an instance of {str(hw_class)} class")
@@ -464,22 +464,22 @@ def observe(self):
# List of camera events to wait for to signal exposure is done
# processing
- camera_events = dict()
+ observing_events = dict()
# Take exposure with each camera
for cam_name, camera in self.cameras.items():
- self.logger.debug("Exposing for camera: {}".format(cam_name))
+ self.logger.debug(f"Exposing for camera: {cam_name}")
try:
# Start the exposures
- cam_event = camera.take_observation(self.current_observation, headers)
+ camera_observe_event = camera.take_observation(self.current_observation, headers)
- camera_events[cam_name] = cam_event
+ observing_events[cam_name] = camera_observe_event
except Exception as e:
- self.logger.error("Problem waiting for images: {}".format(e))
+ self.logger.error(f"Problem waiting for images: {e!r}")
- return camera_events
+ return observing_events
def analyze_recent(self):
"""Analyze the most recent exposure
diff --git a/src/panoptes/pocs/scheduler/__init__.py b/src/panoptes/pocs/scheduler/__init__.py
index 691052824..8b4d8010c 100644
--- a/src/panoptes/pocs/scheduler/__init__.py
+++ b/src/panoptes/pocs/scheduler/__init__.py
@@ -71,6 +71,6 @@ def create_scheduler_from_config(observer=None, *args, **kwargs):
except error.NotFound as e:
raise error.NotFound(msg=e)
else:
- raise error.NotFound(msg=f"Fields file does not exist: {fields_file=}")
+ raise error.NotFound(msg=f"Fields file does not exist: fields_file={fields_file!r}")
return scheduler
diff --git a/src/panoptes/pocs/scheduler/dispatch.py b/src/panoptes/pocs/scheduler/dispatch.py
index 56dc103d4..1a7838766 100644
--- a/src/panoptes/pocs/scheduler/dispatch.py
+++ b/src/panoptes/pocs/scheduler/dispatch.py
@@ -1,6 +1,6 @@
-from panoptes.utils import current_time
-from panoptes.utils import listify
from panoptes.pocs.scheduler import BaseScheduler
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import listify
class Scheduler(BaseScheduler):
diff --git a/src/panoptes/pocs/scheduler/scheduler.py b/src/panoptes/pocs/scheduler/scheduler.py
index 0c439aa7f..ff91bb096 100644
--- a/src/panoptes/pocs/scheduler/scheduler.py
+++ b/src/panoptes/pocs/scheduler/scheduler.py
@@ -1,24 +1,23 @@
import os
-
from collections import OrderedDict
from contextlib import suppress
from astroplan import Observer
from astropy import units as u
from astropy.coordinates import get_moon
-
from panoptes.pocs.base import PanBase
-from panoptes.utils import error
-from panoptes.utils import current_time
-from panoptes.utils import get_quantity_value
-from panoptes.utils.serializers import from_yaml
from panoptes.pocs.scheduler.field import Field
from panoptes.pocs.scheduler.observation import Observation
+from panoptes.utils import error
+from panoptes.utils.serializers import from_yaml
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import get_quantity_value
class BaseScheduler(PanBase):
- def __init__(self, observer, fields_list=None, fields_file=None, constraints=None, *args, **kwargs):
+ def __init__(self, observer, fields_list=None, fields_file=None, constraints=None, *args,
+ **kwargs):
"""Loads `~pocs.scheduler.field.Field`s from a field
Note:
@@ -217,23 +216,24 @@ def add_observation(self, field_config):
field_config (dict): Configuration items for `Observation`
"""
with suppress(KeyError):
- field_config['exptime'] = float(get_quantity_value(field_config['exptime'], unit=u.second)) * u.second
+ field_config['exptime'] = float(
+ get_quantity_value(field_config['exptime'], unit=u.second)) * u.second
- self.logger.debug(f"Adding {field_config=} to scheduler")
+ self.logger.debug(f"Adding field_config={field_config!r} to scheduler")
field = Field(field_config['name'], field_config['position'])
- self.logger.debug(f"Created {field.name=}")
+ self.logger.debug(f"Created field.name={field.name!r}")
try:
self.logger.debug(f"Creating observation for {field_config!r}")
obs = Observation(field, **field_config)
- self.logger.debug(f"Observation created for {field.name=}")
+ self.logger.debug(f"Observation created for field.name={field.name!r}")
except Exception as e:
raise error.InvalidObservation(f"Skipping invalid field: {field_config!r} {e!r}")
else:
if field.name in self._observations:
- self.logger.debug(f"Overriding existing entry for {field.name=}")
+ self.logger.debug(f"Overriding existing entry for field.name={field.name!r}")
self._observations[field.name] = obs
- self.logger.debug(f"{obs=} added")
+ self.logger.debug(f"obs={obs!r} added")
def remove_observation(self, field_name):
"""Removes an `Observation` from the scheduler
diff --git a/src/panoptes/pocs/sensors/__init__.py b/src/panoptes/pocs/sensors/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/panoptes/pocs/sensors/arduino_io.py b/src/panoptes/pocs/sensors/arduino_io.py
deleted file mode 100644
index be4c8a2ef..000000000
--- a/src/panoptes/pocs/sensors/arduino_io.py
+++ /dev/null
@@ -1,302 +0,0 @@
-# Supports reading from and writing to Arduinos attached via serial
-# devices. Each line of output from the Arduinos must be a single
-# JSON encoded object, one of whose fields is called "name" with the
-# value the (unique) name of the board; e.g. "camera_board" or
-# "telemetry_board".
-
-import collections
-import copy
-import serial
-import threading
-import traceback
-
-from panoptes.utils.error import ArduinoDataError
-from panoptes.pocs.utils.logger import get_logger
-from panoptes.utils import CountdownTimer
-from panoptes.utils import rs232
-
-
-def auto_detect_arduino_devices(ports=None):
- """Returns a list of tuples of (board_name, port)."""
- if ports is None:
- ports = get_arduino_ports()
- result = []
- for port in ports:
- board_name = detect_board_on_port(port)
- if board_name:
- result.append((board_name, port))
- return result
-
-
-# Note: get_arduino_ports is modified by test_arduino_io.py, so if changing
-# this import, the test will also need to be updated.
-def get_arduino_ports():
- """Find ports (device paths or URLs) that appear to be Arduinos.
-
- Returns:
- a list of strings; device paths (e.g. /dev/ttyACM1) or URLs (e.g. rfc2217://host:port
- arduino_simulator://?board=camera).
- """
- ports = rs232.get_serial_port_info()
- return [
- p.device for p in ports
- if 'arduino' in p.description.lower() or 'arduino' in p.manufacturer.lower()
- ]
-
-
-def detect_board_on_port(port):
- """Determine which type of board is attached to the specified port.
-
- Returns: Name of the board (e.g. 'camera_board') if we can read a
- line of JSON from the port, parse it and find a 'name'
- attribute in the top-level object. Else returns None.
- """
- logger = get_logger()
- logger.debug(f'Attempting to connect to serial port: {port}')
- serial_reader = None
- try:
- # First open a connection to the device.
- try:
- serial_reader = open_serial_device(port)
- if not serial_reader.is_connected:
- serial_reader.connect()
- logger.debug(f'Connected to {port=}')
- except Exception:
- logger.warning(f'Could not connect to {port=}')
- return None
- try:
- reading = serial_reader.get_and_parse_reading(retry_limit=3)
- if not reading:
- return None
- (ts, data) = reading
- if isinstance(data, dict) and 'name' in data and isinstance(data['name'], str):
- return data['name']
- logger.warning(f'Unable to find board name in {reading=}')
- return None
- except Exception as e: # pragma: no cover
- logger.error(f'Exception while auto-detecting {port=}: {e!r}')
- finally:
- if serial_reader:
- serial_reader.disconnect()
-
-
-def open_serial_device(port, serial_config=None, **kwargs):
- """Creates an rs232.SerialData for port, assumed to be an Arduino.
-
- Default parameters are provided when creating the SerialData
- instance, but may be overridden by serial_config or kwargs.
-
- Args:
- serial_config:
- dictionary (or None) with serial settings from config file,
- suitable for passing to SerialData or to a PySerial
- instance.
- **kwargs:
- Any other parameters to be passed to SerialData. These have
- higher priority than the serial_config parameter.
- """
- # Using a long timeout (2 times the report interval) rather than
- # retries which can just break a JSON line into two unparseable
- # fragments.
- defaults = dict(baudrate=9600, retry_limit=1, retry_delay=0, timeout=4.0, name=port)
- params = collections.ChainMap(dict(port=port), kwargs, serial_config or {}, defaults)
- params = dict(**params)
- return rs232.SerialData(**params)
-
-
-class ArduinoIO(object):
- """Supports reading from and writing to Arduinos.
-
- The readings (python dictionaries) are recorded in a PanDB collection in
- the following form:
-
- ```
- {
- 'name': self.board,
- 'timestamp': t,
- 'data': reading
- }
- ```
-
- """
-
- def __init__(self, board, serial_data, db):
- """Initialize for board on device.
-
- Args:
- board: The name of the board, used as the name of the database
- table/collection to write to, and the name of the messaging
- topics for readings or relay commands.
- serial_data: A SerialData instance connected to the board.
- db: The PanDB instance in which to record reading.
- """
- self.board = board.lower()
- self.port = serial_data.port
- self._serial_data = serial_data
- self._db = db
- self._logger = get_logger()
- self._last_reading = None
- self._report_next_reading = True
- self._cmd_topic = "{}:commands".format(board)
- # Using threading.Event rather than just a boolean field so that any thread
- # can get and set the stop_running property.
- self._stop_running = threading.Event()
- self._logger.info(f'Created ArduinoIO instance for {self.board=}')
-
- @property
- def stop_running(self):
- return self._stop_running.is_set()
-
- @stop_running.setter
- def stop_running(self, value):
- if value:
- self._stop_running.set()
- else:
- self._stop_running.clear()
- self._logger.info(f'Updated ArduinoIO.stop_running to {self.stop_running!r}')
-
- def run(self):
- """Main loop for recording data and reading commands.
-
- This only ends if an Exception is unhandled or if a 'shutdown'
- command is received. The most likely exception is from
- SerialData.get_and_parse_reading() in the event that the device
- disconnects from USB.
- """
- while not self.stop_running:
- self.read_and_record()
- self.handle_commands()
-
- def read_and_record(self):
- """Try to get the next reading and, if successful, record it.
-
- Write the reading to the appropriate PanDB collections and
- to the appropriate message topic.
-
- If there is an interruption in success in reading from the device,
- we announce (log) the start and end of that situation.
- """
- reading = self.get_reading()
- if not reading:
- # Consider adding an error counter.
- if not self._report_next_reading:
- self._logger.warning(f'Unable to read from {self.port=}. Will report when next successful read.')
- self._report_next_reading = True
- return False
- if self._report_next_reading:
- self._logger.info(f'Succeeded in reading from {self.port=}; got:\n{reading}')
- self._report_next_reading = False
- self.handle_reading(reading)
- return True
-
- def connect(self):
- """Connect to the port."""
- if not self._serial_data.is_connected:
- self._serial_data.connect()
-
- def disconnect(self):
- """Disconnect from the port.
-
- Will re-open automatically if reading or writing occurs.
- """
- try:
- if self._serial_data.is_connected:
- self._serial_data.disconnect()
- except Exception as e:
- self._logger.error(f'Failed to disconnect from {self.port=} due to: {e!r}')
-
- def reconnect(self):
- """Disconnect from and connect to the serial port.
-
- This supports handling a SerialException, such as when the USB
- bus is reset.
- """
- try:
- self.disconnect()
- except Exception:
- self._logger.error(f'Unable to disconnect from {self.port=}')
- return False
- try:
- self.connect()
- return True
- except Exception:
- self._logger.error(f'Unable to reconnect to {self.port=}')
- return False
-
- def get_reading(self):
- """Reads and returns a single reading."""
- if not self._serial_data.is_connected:
- self._serial_data.connect()
- try:
- return self._serial_data.get_and_parse_reading(retry_limit=1)
- except serial.SerialException as e:
- self._logger.error(f'Exception raised while reading from {self.port=}')
- self._logger.error("\n".join(traceback.format_exc()))
- if self.reconnect():
- return None
- raise e
-
- def handle_reading(self, reading):
- """Saves a reading as the last_reading and writes to output_queue."""
- # TODO(jamessynge): Discuss with Wilfred changing the timestamp to a datetime object
- # instead of a string. Obviously it needs to be serialized eventually.
- timestamp, data = reading
- if data.get('name', self.board) != self.board:
- msg = f'Board reports {data["name"]}, expected {self.board}'
- self._logger.critical(msg)
- raise ArduinoDataError(msg)
- reading = dict(name=self.board, timestamp=timestamp, data=data)
- self._last_reading = copy.deepcopy(reading)
- if self._db:
- self._db.insert_current(self.board, reading)
-
- def handle_commands(self):
- """Read and process commands for up to 1 second.
-
- Returns when there are no more commands available from the
- command subscriber, or when a second has passed.
- The interval is 1 second because we expect at least 2 seconds
- between reports, and also expect that it probably doesn't take
- more than 1 second for each report to be read. We could make
- this configurable, or could dynamically adjust, such as by
- polling for input.
- """
- timer = CountdownTimer(1.0)
- while not timer.expired():
- topic, msg_obj = self._sub.receive_message(blocking=True, timeout_ms=0.05)
- if not topic:
- continue
- self._logger.debug(f'Received a message for {topic=}')
- if topic.lower() == self._cmd_topic:
- try:
- self.handle_command(msg_obj)
- except Exception as e:
- self._logger.error(f'Exception while handling command: {e!r}')
- self._logger.error(f'{msg_obj=}')
-
- def handle_command(self, msg):
- """Handle one relay command.
-
- TODO(jamessynge): Add support for 'set_relay', where we look
- up the relay name in self._last_reading to confirm that it
- exists on this device.
- """
- if msg['command'] == 'shutdown':
- self._logger.info(f'Received command to shutdown ArduinoIO for {self.board=}')
- self.stop_running = True
- elif msg['command'] == 'write_line':
- line = msg['line'].rstrip('\r\n')
- self._logger.debug(f'Sending line to {self.board=}: {line}')
- line = line + '\n'
- self.write(line)
- else:
- self._logger.error(f'Ignoring command: {msg}')
-
- def write(self, text):
- """Writes text (a string) to the port.
-
- Returns: the number of bytes written.
- """
- if not self._serial_data.is_connected:
- self._serial_data.connect()
- return self._serial_data.write(text)
diff --git a/src/panoptes/pocs/state/machine.py b/src/panoptes/pocs/state/machine.py
index afc981706..8c67c018b 100644
--- a/src/panoptes/pocs/state/machine.py
+++ b/src/panoptes/pocs/state/machine.py
@@ -4,7 +4,7 @@
from transitions.extensions.states import Tags as MachineState
from panoptes.utils import error
-from panoptes.utils import listify
+from panoptes.utils.utils import listify
from panoptes.utils.library import load_module
from panoptes.utils.serializers import from_yaml
@@ -29,7 +29,7 @@ def __init__(self, state_machine_table, **kwargs):
'transitions keyword required.')
self._state_table_name = state_machine_table.get('name', 'default')
- self._states_location = state_machine_table.get('location', 'pocs/state/states')
+ self._states_location = state_machine_table.get('location', 'panoptes.pocs.state.states')
# Setup Transitions.
_transitions = [self._load_transition(transition)
@@ -65,9 +65,9 @@ def __init__(self, state_machine_table, **kwargs):
self.logger.debug("State machine created")
- ##################################################################################################
+ ################################################################################################
# Properties
- ##################################################################################################
+ ################################################################################################
@property
def next_state(self):
@@ -78,9 +78,9 @@ def next_state(self, value):
""" Set the tracking rate """
self._next_state = value
- ##################################################################################################
+ ################################################################################################
# Methods
- ##################################################################################################
+ ################################################################################################
def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
"""Runs the state machine loop.
@@ -106,28 +106,44 @@ def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
_transition_iteration = 0
max_transition_attempts = self.get_config('max_transition_attempts', default=5)
+ check_delay = self.get_config('wait_delay', default=120)
self.logger.debug(f'Starting run loop')
while self.keep_running:
+
# BEFORE TRANSITION TO STATE
- self.logger.info(f'Run loop: {self.state=} {self.next_state=}')
-
- # Before moving to next state, check for required horizon level and wait if necessary.
- required_horizon = self._horizon_lookup.get(self.next_state, 'observe')
- self.logger.debug(f'Checking for {required_horizon=} for {self.next_state=}')
- while not self.is_safe(no_warning=True, horizon=required_horizon):
- self.logger.info(f'Waiting for {required_horizon=} for {self.next_state=}')
- check_delay = self.get_config('wait_delay',
- default=60 * 3) # Check every 3 minutes.
+ self.logger.info(f'Run loop: self.state={self.state!r}'
+ f'self.next_state={self.next_state!r}')
+
+ # Before moving to next state, wait for required horizon if necessary
+ while True:
+ # If not safe, go to park
+ self.is_safe(park_if_not_safe=True, ignore=['is_dark'])
+
+ # The state may have changed since the start of the while loop
+ # e.g. if self.park is called from self.is_safe
+ # So we need to check if the new state is always safe
+ if self.get_state(self.next_state).is_always_safe:
+ break
+
+ # Check the horizon here because next state may have changed in loop
+ required_horizon = self._horizon_lookup.get(self.next_state, 'observe')
+ if self.is_dark(horizon=required_horizon):
+ break
+ self.logger.info(f"Waiting for required_horizon={required_horizon!r} for "
+ f"self.next_state={self.next_state!r}")
+
+ # Sleep before checking again
self.wait(delay=check_delay)
- # TRANSITION TO STATE
- self.logger.info(f'Going to {self.next_state=}')
+ # TRANSITION TO STATE
+ self.logger.info(f'Going to self.next_state={self.next_state!r}')
try:
# The state's `on_enter` logic will be performed here.
state_changed = self.goto_next_state()
except Exception as e:
- self.logger.critical(f"Problem going from {self.state=} to {self.next_state=}"
+ self.logger.critical(f"Problem going from self.state={self.state!r} to "
+ f" self.next_state={self.next_state!r}"
f", exiting loop [{e!r}]")
# TODO should we automatically park here?
self.stop_states()
@@ -137,14 +153,16 @@ def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
# If we didn't successfully transition, wait a while then try again
if not state_changed:
- self.logger.warning(f"Failed to move from {self.state=} to {self.next_state=}")
+ self.logger.warning(f"Failed to move from self.state={self.state!r} to "
+ f"self.next_state={self.next_state!r}")
if self.is_safe() is False:
self.logger.warning(
"Conditions have become unsafe; setting next state to 'parking'")
self.next_state = 'parking'
elif _transition_iteration > max_transition_attempts:
self.logger.warning(
- f"Stuck in current state for {max_transition_attempts=}, parking")
+ f"Stuck in current state for "
+ f"max_transition_attempts={max_transition_attempts!r}, parking")
self.next_state = 'parking'
else:
_transition_iteration = _transition_iteration + 1
@@ -165,7 +183,7 @@ def run(self, exit_when_done=False, run_once=False, initial_next_state='ready'):
self.stop_states()
if exit_when_done:
- self.logger.info(f'Leaving run loop {exit_when_done=}')
+ self.logger.info(f'Leaving run loop exit_when_done={exit_when_done!r}')
break
def goto_next_state(self):
@@ -285,14 +303,12 @@ def after_state(self, event_data):
##################################################################################################
@classmethod
- def load_state_table(cls, state_table_name='simple_state_table'):
+ def load_state_table(cls, state_table_name='panoptes'):
""" Loads the state table
-
Args:
- state_table_name(str): Name of state table. Corresponds to file name in
- `$POCS/resources/state_table/` directory or to absolute path if
- starts with "/". Default 'simple_state_table'.
-
+ state_table_name(str): Name of state table. Corresponds to filename in
+ `$POCS/conf_files/state_table/` directory or to absolute path if
+ starts with "/". Default 'panoptes.yaml'.
Returns:
dict: Dictionary with `states` and `transitions` keys.
"""
@@ -300,7 +316,7 @@ def load_state_table(cls, state_table_name='simple_state_table'):
if not state_table_name.startswith('/'):
state_table_file = os.path.join(
os.getenv('POCS', default='/var/panoptes/POCS'),
- 'resources',
+ 'conf_files',
'state_table',
f'{state_table_name}.yaml'
)
@@ -337,11 +353,8 @@ def _update_status(self, event_data):
def _load_state(self, state, state_info=None):
self.logger.debug(f"Loading state: {state}")
try:
- state_module = load_module('panoptes.{}.{}.{}'.format(
- self._states_location.replace("/", "."),
- self._state_table_name,
- state
- ))
+ state_location = self._states_location.replace("/", ".")
+ state_module = load_module(f"{state_location}.{self._state_table_name}.{state}")
# Get the `on_enter` method
self.logger.debug(f"Checking {state_module}")
@@ -358,7 +371,7 @@ def _load_state(self, state, state_info=None):
self._horizon_lookup[state] = state_info['horizon']
del state_info['horizon']
- self.logger.debug(f"Creating {state=} with {state_info=}")
+ self.logger.debug(f"Creating state={state!r} with state_info={state_info!r}")
state_machine = MachineState(name=state, **state_info)
# Add default callbacks.
diff --git a/src/panoptes/pocs/state/states/default/observing.py b/src/panoptes/pocs/state/states/default/observing.py
index 8ecd598cd..52688a5f0 100644
--- a/src/panoptes/pocs/state/states/default/observing.py
+++ b/src/panoptes/pocs/state/states/default/observing.py
@@ -17,10 +17,11 @@ def on_enter(event_data):
maximum_duration = pocs.observatory.current_observation.exptime.value + MAX_EXTRA_TIME
# Start the observing.
- camera_events_info = pocs.observatory.observe()
- camera_events = list(camera_events_info.values())
+ observing_events = pocs.observatory.observe()
+ camera_events = list(observing_events.values())
def waiting_cb():
+ # TODO Check for dead camera here and potential remove from list?
pocs.logger.info(f'Waiting on an observation.')
wait_for_events(camera_events, timeout=maximum_duration, callback=waiting_cb, sleep_delay=11)
diff --git a/src/panoptes/pocs/tests/bisque/test_dome.py b/src/panoptes/pocs/tests/bisque/test_dome.py
index dcd1b1c9f..e1b7bb8be 100644
--- a/src/panoptes/pocs/tests/bisque/test_dome.py
+++ b/src/panoptes/pocs/tests/bisque/test_dome.py
@@ -1,8 +1,8 @@
import os
-import pytest
+import pytest
from panoptes.pocs.dome.bisque import Dome
-from panoptes.utils.theskyx import TheSkyX
+from panoptes.pocs.utils.theskyx import TheSkyX
pytestmark = pytest.mark.skipif(TheSkyX().is_connected is False, reason="TheSkyX is not connected")
diff --git a/src/panoptes/pocs/tests/bisque/test_mount.py b/src/panoptes/pocs/tests/bisque/test_mount.py
index 669f3340b..315ebf96e 100644
--- a/src/panoptes/pocs/tests/bisque/test_mount.py
+++ b/src/panoptes/pocs/tests/bisque/test_mount.py
@@ -1,14 +1,13 @@
import os
-import pytest
+import pytest
from astropy import units as u
from astropy.coordinates import EarthLocation
-
from panoptes.pocs.mount.bisque import Mount
+from panoptes.pocs.utils.theskyx import TheSkyX
from panoptes.utils.config.client import get_config
-from panoptes.utils import altaz_to_radec
-from panoptes.utils import current_time
-from panoptes.utils.theskyx import TheSkyX
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import altaz_to_radec
pytestmark = pytest.mark.skipif(TheSkyX().is_connected is False, reason="TheSkyX is not connected")
diff --git a/src/panoptes/pocs/tests/bisque/test_run.py b/src/panoptes/pocs/tests/bisque/test_run.py
index 26a5014cd..217e39dc4 100644
--- a/src/panoptes/pocs/tests/bisque/test_run.py
+++ b/src/panoptes/pocs/tests/bisque/test_run.py
@@ -1,14 +1,13 @@
import os
-import pytest
+import pytest
from astropy.coordinates import EarthLocation
-
from panoptes.pocs.core import POCS
from panoptes.pocs.dome.bisque import Dome
+from panoptes.pocs.utils.theskyx import TheSkyX
from panoptes.utils.config.client import get_config
-from panoptes.utils import altaz_to_radec
-from panoptes.utils import current_time
-from panoptes.utils.theskyx import TheSkyX
+from panoptes.utils.time import current_time
+from panoptes.utils.utils import altaz_to_radec
pytestmark = pytest.mark.skipif(TheSkyX().is_connected is False, reason="TheSkyX is not connected")
@@ -39,7 +38,8 @@ def pocs(target):
config = get_config()
- pocs = POCS(simulator=['weather', 'night', 'camera'], run_once=True, config=config, db='panoptes_testing')
+ pocs = POCS(simulator=['weather', 'night', 'camera'], run_once=True, config=config,
+ db='panoptes_testing')
pocs.observatory.scheduler.fields_list = [
{'name': 'Testing Target',
diff --git a/src/panoptes/pocs/tests/test_camera.py b/src/panoptes/pocs/tests/test_camera.py
index f488d4800..9041d66fa 100644
--- a/src/panoptes/pocs/tests/test_camera.py
+++ b/src/panoptes/pocs/tests/test_camera.py
@@ -8,9 +8,10 @@
import astropy.units as u
from astropy.io import fits
+import requests
-from panoptes.pocs.camera.simulator import Camera as SimCamera
-from panoptes.pocs.camera.simulator_sdk import Camera as SimSDKCamera
+from panoptes.pocs.camera.simulator.dslr import Camera as SimCamera
+from panoptes.pocs.camera.simulator.ccd import Camera as SimSDKCamera
from panoptes.pocs.camera.sbig import Camera as SBIGCamera
from panoptes.pocs.camera.sbigudrv import SBIGDriver, INVALID_HANDLE_VALUE
from panoptes.pocs.camera.fli import Camera as FLICamera
@@ -27,45 +28,34 @@
from panoptes.utils.config.client import set_config
from panoptes.pocs.camera import create_cameras_from_config
-from panoptes.pocs.camera import create_camera_simulator
-
-focuser_params = {
- 'model': 'simulator',
- 'focus_port': '/dev/ttyFAKE',
- 'initial_position': 20000,
- 'autofocus_range': (40, 80),
- 'autofocus_step': (10, 20),
- 'autofocus_seconds': 0.1,
- 'autofocus_size': 500,
- 'autofocus_keep_files': False
-}
-
-filterwheel_params = {
- 'model': 'simulator',
- 'filter_names': ['one', 'deux', 'drei', 'quattro'],
- 'move_time': 0.1,
- 'timeout': 0.5
-}
-
-serial_number_params = 'SSC101'
-
-
-@pytest.fixture(scope='function', params=[
+from panoptes.utils.serializers import to_json
+
+
+@pytest.fixture(scope='module', params=[
pytest.param([SimCamera, dict()]),
- pytest.param([SimCamera, dict(focuser=focuser_params)]),
- pytest.param([SimCamera, dict(filterwheel=filterwheel_params)]),
- pytest.param([SimSDKCamera, dict(serial_number=serial_number_params)]),
+ pytest.param([SimCamera, get_config('cameras.devices[0]')]),
+ pytest.param([SimCamera, get_config('cameras.devices[1]')]),
+ pytest.param([SimCamera, get_config('cameras.devices[2]')]),
+ pytest.param([SimSDKCamera, get_config('cameras.devices[3]')]),
pytest.param([SBIGCamera, 'sbig'], marks=[pytest.mark.with_camera]),
pytest.param([FLICamera, 'fli'], marks=[pytest.mark.with_camera]),
pytest.param([ZWOCamera, 'zwo'], marks=[pytest.mark.with_camera]),
], ids=[
- 'simulator', 'simulator_focuser', 'simulator_filterwheel', 'simulator_sdk',
- 'sbig', 'fli', 'zwo'
+ 'dslr',
+ 'dslr.00',
+ 'dslr.focuser.cooling.00',
+ 'dslr.filterwheel.cooling.00',
+ 'ccd.filterwheel.cooling.00',
+ 'sbig',
+ 'fli',
+ 'zwo'
])
def camera(request):
CamClass = request.param[0]
cam_params = request.param[1]
+ camera = None
+
if isinstance(cam_params, dict):
# Simulator
camera = CamClass(**cam_params)
@@ -76,43 +66,50 @@ def camera(request):
camera = CamClass(**cam_config)
break
- camera.logger.debug(f'Yielding camera {camera}')
+ # Wait for cooled camera
+ if camera.is_cooled_camera:
+ assert not camera.is_temperature_stable
+ # Wait for cooling
+ while not camera.is_temperature_stable:
+ time.sleep(2)
+ assert camera.is_temperature_stable
+
assert camera.is_ready
+ camera.logger.debug(f'Yielding camera {camera}')
yield camera
# simulator_sdk needs this explicitly removed for some reason.
+ # SDK Camera class destructor *should* be doing this when the fixture goes out of scope.
with suppress(AttributeError):
type(camera)._assigned_cameras.discard(camera.uid)
-@pytest.fixture(scope='function')
+@pytest.fixture(scope='module')
def counter(camera):
return {'value': 0}
-@pytest.fixture(scope='function')
+@pytest.fixture(scope='module')
def patterns(camera, images_dir):
- patterns = {'final': os.path.join(images_dir, 'focus', camera.uid, '*',
- ('*_final.' + camera.file_extension)),
- 'fine_plot': os.path.join(images_dir, 'focus', camera.uid, '*',
- 'fine_focus.png'),
- 'coarse_plot': os.path.join(images_dir, 'focus', camera.uid, '*',
- 'coarse_focus.png')}
+ base_dir = os.path.join(images_dir, 'focus', camera.uid, '*')
+ patterns = {
+ 'final': os.path.join(base_dir, ('*-final.' + camera.file_extension)),
+ 'fine_plot': os.path.join(base_dir, 'fine-focus.png'),
+ 'coarse_plot': os.path.join(base_dir, 'coarse-focus.png')
+ }
return patterns
-def test_create_camera_simulator():
- cameras = create_camera_simulator()
- assert len(cameras) == 2
-
- cameras = create_camera_simulator()
- assert len(cameras) == 2
-
- with pytest.raises(error.CameraNotFound):
- create_camera_simulator(num_cameras=0)
+def reset_conf(config_host, config_port):
+ url = f'http://{config_host}:{config_port}/reset-config'
+ response = requests.post(url,
+ data=to_json({'reset': True}),
+ headers={'Content-Type': 'application/json'}
+ )
+ assert response.ok
-def test_create_cameras_from_config_no_autodetect():
+def test_create_cameras_from_config_no_autodetect(config_host, config_port):
set_config('cameras.auto_detect', False)
set_config('cameras.devices', [
dict(model='canon_gphoto2', port='/dev/fake01'),
@@ -122,17 +119,21 @@ def test_create_cameras_from_config_no_autodetect():
with pytest.raises(error.CameraNotFound):
create_cameras_from_config()
+ reset_conf(config_host, config_port)
-def test_create_cameras_from_config_autodetect():
- set_config('cameras.auto_detect', True)
+
+def test_create_cameras_from_config_autodetect(config_host, config_port):
+ set_config('cameras.defaults.auto_detect', True)
with pytest.raises(error.CameraNotFound):
create_cameras_from_config()
+ reset_conf(config_host, config_port)
# Hardware independent tests, mostly use simulator:
def test_sim_create_focuser():
- sim_camera = SimCamera(focuser={'model': 'simulator', 'focus_port': '/dev/ttyFAKE'})
+ sim_camera = SimCamera(focuser={'model': 'panoptes.pocs.focuser.simulator.Focuser',
+ 'focus_port': '/dev/ttyFAKE'})
assert isinstance(sim_camera.focuser, Focuser)
@@ -143,21 +144,20 @@ def test_sim_passed_focuser():
def test_sim_bad_focuser():
- with pytest.raises((NotFound)):
+ with pytest.raises(NotFound):
SimCamera(focuser={'model': 'NOTAFOCUSER'})
def test_sim_worse_focuser():
- sim_camera = SimCamera(focuser='NOTAFOCUSER')
- # Will log an error but raise no exceptions
- assert sim_camera.focuser is None
+ with pytest.raises(NotFound):
+ sim_camera = SimCamera(focuser='NOTAFOCUSER')
def test_sim_string():
sim_camera = SimCamera()
- assert str(sim_camera) == 'Simulated Camera ({}) on None'.format(sim_camera.uid)
+ assert str(sim_camera) == f'Simulated Camera ({sim_camera.uid})'
sim_camera = SimCamera(name='Sim', port='/dev/ttyFAKE')
- assert str(sim_camera) == 'Sim ({}) on /dev/ttyFAKE'.format(sim_camera.uid)
+ assert str(sim_camera) == f'Sim ({sim_camera.uid}) port=/dev/ttyFAKE'
def test_sim_file_extension():
@@ -180,15 +180,16 @@ def test_sdk_no_serial_number():
def test_sdk_camera_not_found():
- with pytest.raises(error.PanError):
+ with pytest.raises(error.InvalidConfig):
SimSDKCamera(serial_number='SSC404')
def test_sdk_already_in_use():
- sim_camera = SimSDKCamera(serial_number='SSC999')
+ serial_number = get_config('cameras.devices[-1].serial_number')
+ sim_camera = SimSDKCamera(serial_number=serial_number)
assert sim_camera
with pytest.raises(error.PanError):
- SimSDKCamera(serial_number='SSC999')
+ SimSDKCamera(serial_number=serial_number)
# Hardware independent tests for SBIG camera
@@ -253,8 +254,8 @@ def test_is_cooled(camera):
def test_set_target_temperature(camera):
if camera.is_cooled_camera:
- camera._target_temperature = 10 * u.Celsius
- assert abs(camera._target_temperature - 10 * u.Celsius) < 0.5 * u.Celsius
+ camera.target_temperature = 10 * u.Celsius
+ assert abs(camera.target_temperature - 10 * u.Celsius) < 0.5 * u.Celsius
else:
pytest.skip("Camera {} doesn't implement temperature control".format(camera.name))
@@ -301,7 +302,9 @@ def test_is_temperature_stable(camera):
if camera.is_cooled_camera:
camera.target_temperature = camera.temperature
camera.cooling_enabled = True
- time.sleep(1)
+ while not camera.is_temperature_stable:
+ time.sleep(2)
+
assert camera.is_temperature_stable
camera.cooling_enabled = False
assert not camera.is_temperature_stable
@@ -317,10 +320,10 @@ def test_exposure(camera, tmpdir):
fits_path = str(tmpdir.join('test_exposure.fits'))
assert not camera.is_exposing
+ assert camera.is_ready
# A one second normal exposure.
- exp_event = camera.take_exposure(filename=fits_path)
+ camera.take_exposure(filename=fits_path)
assert camera.is_exposing
- assert not exp_event.is_set()
assert not camera.is_ready
# By default take_exposure is non-blocking, need to give it some time to complete.
if isinstance(camera, FLICamera):
@@ -329,7 +332,6 @@ def test_exposure(camera, tmpdir):
time.sleep(5)
# Output file should exist, Event should be set and camera should say it's not exposing.
assert os.path.exists(fits_path)
- assert exp_event.is_set()
assert not camera.is_exposing
assert camera.is_ready
# If can retrieve some header data there's a good chance it's a valid FITS file
@@ -353,9 +355,32 @@ def test_exposure_blocking(camera, tmpdir):
assert header['IMAGETYP'] == 'Light Frame'
+def test_long_exposure_blocking(camera, tmpdir):
+ """
+ Tests basic take_exposure functionality
+ """
+ fits_path = str(tmpdir.join('test_long_exposure_blocking.fits'))
+ original_timeout = camera._timeout
+ original_readout = camera._readout_time
+ try:
+ camera._timeout = 1
+ camera._readout_time = 0.5
+ assert not camera.is_exposing
+ assert camera.is_ready
+ seconds = 2 * (camera._timeout + camera._readout_time)
+ camera.take_exposure(filename=fits_path, seconds=seconds, blocking=True)
+ # Output file should exist, Event should be set and camera should say it's not exposing.
+ assert os.path.exists(fits_path)
+ assert not camera.is_exposing
+ assert camera.is_ready
+ finally:
+ camera._timeout = original_timeout
+ camera._readout_time = original_readout
+
+
def test_exposure_dark(camera, tmpdir):
"""
- Tests taking a dark. At least for now only SBIG cameras do this.
+ Tests taking a dark.
"""
fits_path = str(tmpdir.join('test_exposure_dark.fits'))
# A 1 second dark exposure
@@ -365,6 +390,14 @@ def test_exposure_dark(camera, tmpdir):
header = fits_utils.getheader(fits_path)
assert header['EXPTIME'] == 1.0
assert header['IMAGETYP'] == 'Dark Frame'
+ with suppress(AttributeError):
+ if not camera.can_take_internal_darks and camera.filterwheel._dark_position:
+ # Filterwheel should have moved to 'blank' position due to dark exposure.
+ assert camera.filterwheel.current_filter == 'blank'
+ fits_path2 = str(tmpdir.join('test_exposure_dark_light.fits'))
+ camera.take_exposure(filename=fits_path2, blocking=True)
+ # Filterwheel should have moved back to most recent non opaque filter now.
+ assert camera.filterwheel.current_filter == 'one'
def test_exposure_collision(camera, tmpdir):
@@ -380,12 +413,17 @@ def test_exposure_collision(camera, tmpdir):
fits_path_1 = str(tmpdir.join('test_exposure_collision1.fits'))
fits_path_2 = str(tmpdir.join('test_exposure_collision2.fits'))
camera.take_exposure(2 * u.second, filename=fits_path_1)
+ camera.logger.log('testing', 'Exposure 1 started')
with pytest.raises(error.PanError):
camera.take_exposure(1 * u.second, filename=fits_path_2)
- if isinstance(camera, FLICamera):
- time.sleep(10)
- else:
- time.sleep(5)
+ camera.logger.log('testing', 'Exposure 2 collided')
+ # Wait for exposure.
+ while camera.is_exposing:
+ time.sleep(0.5)
+ # Wait for readout on file.
+ while not os.path.exists(fits_path_1):
+ time.sleep(0.5)
+
assert os.path.exists(fits_path_1)
assert not os.path.exists(fits_path_2)
assert fits_utils.getval(fits_path_1, 'EXPTIME') == 2.0
@@ -423,17 +461,16 @@ def test_exposure_not_connected(camera):
def test_exposure_moving(camera, tmpdir):
- if not camera.filterwheel:
+ if camera.filterwheel is None:
pytest.skip("Camera does not have a filterwheel")
fits_path_1 = str(tmpdir.join('test_not_moving.fits'))
fits_path_2 = str(tmpdir.join('test_moving.fits'))
camera.filterwheel.position = 1
- exp_event = camera.take_exposure(filename=fits_path_1)
- exp_event.wait()
+ exp_event = camera.take_exposure(filename=fits_path_1, blocking=True)
assert os.path.exists(fits_path_1)
move_event = camera.filterwheel.move_to(2)
with pytest.raises(error.PanError):
- camera.take_exposure(filename=fits_path_2)
+ camera.take_exposure(filename=fits_path_2, blocking=True)
move_event.wait()
assert not os.path.exists(fits_path_2)
@@ -444,11 +481,9 @@ def test_exposure_timeout(camera, tmpdir, caplog):
"""
fits_path = str(tmpdir.join('test_exposure_timeout.fits'))
# Make timeout extremely short to force a timeout error
- original_timeout = camera._timeout
- camera._timeout = 0.01
# This should result in a timeout error in the poll thread, but the exception won't
# be seen in the main thread. Can check for logged error though.
- exposure_event = camera.take_exposure(seconds=2.0, filename=fits_path)
+ readout_thread = camera.take_exposure(seconds=2.0, filename=fits_path, timeout=0.01)
# Wait for it all to be over.
time.sleep(4)
@@ -458,8 +493,8 @@ def test_exposure_timeout(camera, tmpdir, caplog):
# Should be no data file, camera should not be exposing, and exposure event should be set
assert not os.path.exists(fits_path)
assert not camera.is_exposing
- assert exposure_event is camera._exposure_event
- assert exposure_event.is_set()
+ assert not readout_thread.is_alive()
+ assert not camera._is_exposing_event.is_set()
def test_observation(camera, images_dir):
@@ -469,13 +504,29 @@ def test_observation(camera, images_dir):
field = Field('Test Observation', '20h00m43.7135s +22d42m39.0645s')
observation = Observation(field, exptime=1.5 * u.second)
observation.seq_time = '19991231T235959'
- camera.take_observation(observation, headers={})
- time.sleep(7)
+ observation_event = camera.take_observation(observation)
+ while not observation_event.is_set():
+ camera.logger.trace(f'Waiting for observation event from inside test.')
+ time.sleep(1)
observation_pattern = os.path.join(images_dir, 'TestObservation',
camera.uid, observation.seq_time, '*.fits*')
assert len(glob.glob(observation_pattern)) == 1
- for fn in glob.glob(observation_pattern):
- os.remove(fn)
+
+
+def test_observation_headers_and_blocking(camera, images_dir):
+ """
+ Tests functionality of take_observation()
+ """
+ field = Field('Test Observation', '20h00m43.7135s +22d42m39.0645s')
+ observation = Observation(field, exptime=1.5 * u.second)
+ observation.seq_time = '19991231T235559'
+ camera.take_observation(observation, headers={'field_name': 'TESTVALUE'}, blocking=True)
+ observation_pattern = os.path.join(images_dir, 'TestObservation',
+ camera.uid, observation.seq_time, '*.fits*')
+ image_files = glob.glob(observation_pattern)
+ assert len(image_files) == 1
+ headers = fits_utils.getheader(image_files[0])
+ assert fits_utils.getval(image_files[0], 'FIELD') == 'TESTVALUE'
def test_observation_nofilter(camera, images_dir):
@@ -484,18 +535,15 @@ def test_observation_nofilter(camera, images_dir):
"""
field = Field('Test Observation', '20h00m43.7135s +22d42m39.0645s')
observation = Observation(field, exptime=1.5 * u.second, filter_name=None)
- observation.seq_time = '19991231T235959'
- camera.take_observation(observation, headers={})
- time.sleep(7)
+ observation.seq_time = '19991231T235159'
+ camera.take_observation(observation, blocking=True)
observation_pattern = os.path.join(images_dir, 'TestObservation',
camera.uid, observation.seq_time, '*.fits*')
assert len(glob.glob(observation_pattern)) == 1
- for fn in glob.glob(observation_pattern):
- os.remove(fn)
def test_autofocus_coarse(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus(coarse=True)
autofocus_event.wait()
@@ -504,7 +552,7 @@ def test_autofocus_coarse(camera, patterns, counter):
def test_autofocus_fine(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus()
autofocus_event.wait()
@@ -513,7 +561,7 @@ def test_autofocus_fine(camera, patterns, counter):
def test_autofocus_fine_blocking(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus(blocking=True)
assert autofocus_event.is_set()
@@ -522,7 +570,7 @@ def test_autofocus_fine_blocking(camera, patterns, counter):
def test_autofocus_with_plots(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus(make_plots=True)
autofocus_event.wait()
@@ -532,7 +580,7 @@ def test_autofocus_with_plots(camera, patterns, counter):
def test_autofocus_coarse_with_plots(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus(coarse=True, make_plots=True)
autofocus_event.wait()
@@ -542,7 +590,7 @@ def test_autofocus_coarse_with_plots(camera, patterns, counter):
def test_autofocus_keep_files(camera, patterns, counter):
- if not camera.focuser:
+ if camera.focuser is None:
pytest.skip("Camera does not have a focuser")
autofocus_event = camera.autofocus(keep_files=True)
autofocus_event.wait()
@@ -550,17 +598,26 @@ def test_autofocus_keep_files(camera, patterns, counter):
assert len(glob.glob(patterns['final'])) == counter['value']
+def test_autofocus_no_darks(camera, patterns, counter):
+ if camera.focuser is None:
+ pytest.skip("Camera does not have a focuser")
+ autofocus_event = camera.autofocus(keep_files=True, take_dark=False)
+ autofocus_event.wait()
+ counter['value'] += 1
+ assert len(glob.glob(patterns['final'])) == counter['value']
+
+
def test_autofocus_no_size(camera):
try:
initial_focus = camera.focuser.position
except AttributeError:
pytest.skip("Camera does not have an exposed focuser attribute")
initial_focus = camera.focuser.position
- thumbnail_size = camera.focuser.autofocus_size
+ cutout_size = camera.focuser.autofocus_size
camera.focuser.autofocus_size = None
with pytest.raises(ValueError):
camera.autofocus()
- camera.focuser.autofocus_size = thumbnail_size
+ camera.focuser.autofocus_size = cutout_size
assert camera.focuser.position == initial_focus
diff --git a/src/panoptes/pocs/tests/test_astrohaven_dome.py b/src/panoptes/pocs/tests/test_dome_astrohaven.py
similarity index 100%
rename from src/panoptes/pocs/tests/test_astrohaven_dome.py
rename to src/panoptes/pocs/tests/test_dome_astrohaven.py
diff --git a/src/panoptes/pocs/tests/test_filterwheel.py b/src/panoptes/pocs/tests/test_filterwheel.py
index 0e5197b6c..6e7953390 100644
--- a/src/panoptes/pocs/tests/test_filterwheel.py
+++ b/src/panoptes/pocs/tests/test_filterwheel.py
@@ -5,7 +5,7 @@
from astropy import units as u
from panoptes.pocs.filterwheel.simulator import FilterWheel as SimFilterWheel
-from panoptes.pocs.camera.simulator import Camera as SimCamera
+from panoptes.pocs.camera.simulator.dslr import Camera as SimCamera
from panoptes.utils import error
@@ -17,7 +17,13 @@ def filterwheel():
return sim_filterwheel
-# intialisation
+@pytest.fixture(scope='function')
+def filterwheel_with_blank():
+ sim_filterwheel = SimFilterWheel(filter_names=['blank', 'deux', 'drei', 'quattro'],
+ move_time=0.1 * u.second,
+ timeout=0.5 * u.second,
+ dark_position='blank')
+ return sim_filterwheel
def test_init(filterwheel):
@@ -26,7 +32,7 @@ def test_init(filterwheel):
def test_camera_init():
- sim_camera = SimCamera(filterwheel={'model': 'simulator',
+ sim_camera = SimCamera(filterwheel={'model': 'panoptes.pocs.filterwheel.simulator.FilterWheel',
'filter_names': ['one', 'deux', 'drei', 'quattro']})
assert isinstance(sim_camera.filterwheel, SimFilterWheel)
assert sim_camera.filterwheel.is_connected
@@ -56,7 +62,7 @@ def test_with_no_name():
def test_model(filterwheel):
model = filterwheel.model
- assert model == 'simulator'
+ assert model == 'panoptes.pocs.filterwheel.simulator.FilterWheel'
with pytest.raises(AttributeError):
filterwheel.model = "Airfix"
@@ -164,15 +170,15 @@ def test_move_times(name, unidirectional, expected):
def test_move_exposing(tmpdir):
- sim_camera = SimCamera(filterwheel={'model': 'simulator',
+ sim_camera = SimCamera(filterwheel={'model': 'panoptes.pocs.filterwheel.simulator.FilterWheel',
'filter_names': ['one', 'deux', 'drei', 'quattro']})
fits_path = str(tmpdir.join('test_exposure.fits'))
- exp_event = sim_camera.take_exposure(filename=fits_path, seconds=0.1)
+ readout_thread = sim_camera.take_exposure(filename=fits_path, seconds=0.1)
with pytest.raises(error.PanError):
# Attempt to move while camera is exposing
sim_camera.filterwheel.move_to(2, blocking=True)
assert sim_camera.filterwheel.position == 1 # Should not have moved
- exp_event.wait()
+ readout_thread.join()
def test_is_moving(filterwheel):
@@ -185,3 +191,26 @@ def test_is_moving(filterwheel):
e.wait()
assert not filterwheel.is_moving
assert filterwheel.is_ready
+
+
+def test_move_dark(filterwheel, filterwheel_with_blank):
+ with pytest.raises(error.NotFound):
+ filterwheel.move_to_dark_position()
+ filterwheel_with_blank.move_to('deux', blocking=True)
+ assert filterwheel_with_blank.current_filter == 'deux'
+ filterwheel_with_blank.move_to_dark_position(blocking=True)
+ assert filterwheel_with_blank.current_filter == 'blank'
+
+
+def test_move_light(filterwheel, filterwheel_with_blank):
+ with pytest.raises(error.NotFound):
+ filterwheel.move_to_light_position()
+ with pytest.raises(error.NotFound):
+ # Won't have been set yet.
+ filterwheel_with_blank.move_to_light_position()
+ filterwheel_with_blank.move_to('deux', blocking=True)
+ assert filterwheel_with_blank.current_filter == 'deux'
+ filterwheel_with_blank.move_to_dark_position(blocking=True)
+ assert filterwheel_with_blank.current_filter == 'blank'
+ filterwheel_with_blank.move_to_light_position(blocking=True)
+ assert filterwheel_with_blank.current_filter == 'deux'
diff --git a/src/panoptes/pocs/tests/test_focuser.py b/src/panoptes/pocs/tests/test_focuser.py
index 93c84125c..28e0f0791 100644
--- a/src/panoptes/pocs/tests/test_focuser.py
+++ b/src/panoptes/pocs/tests/test_focuser.py
@@ -7,14 +7,12 @@
from panoptes.pocs.focuser.simulator import Focuser as SimFocuser
from panoptes.pocs.focuser.birger import Focuser as BirgerFocuser
+from panoptes.pocs.focuser.astromechanics import Focuser as AstroMechanicsFocuser
from panoptes.pocs.focuser.focuslynx import Focuser as FocusLynxFocuser
-from panoptes.pocs.camera.simulator import Camera
+from panoptes.pocs.camera.simulator.dslr import Camera
-params = [SimFocuser, BirgerFocuser, FocusLynxFocuser]
-ids = ['simulator', 'birger', 'focuslynx']
-
-
-# Ugly hack to access id inside fixture
+params = [SimFocuser, BirgerFocuser, FocusLynxFocuser, AstroMechanicsFocuser]
+ids = ['simulator', 'birger', 'focuslynx', 'astromechanics']
@pytest.fixture(scope='function', params=zip(params, ids), ids=ids)
@@ -25,7 +23,7 @@ def focuser(request):
else:
# Load the local config file and look for focuser configurations of the specified type
focuser_configs = []
- local_config = load_config('pocs_local', ignore_local=True)
+ local_config = load_config('pocs_local', load_local=True)
camera_info = local_config.get('cameras')
if camera_info:
# Local config file has a cameras section
@@ -58,6 +56,8 @@ def tolerance(focuser):
return 0
elif isinstance(focuser, BirgerFocuser):
return 2
+ elif isinstance(focuser, AstroMechanicsFocuser):
+ return 2
elif isinstance(focuser, FocusLynxFocuser):
return 0
@@ -106,11 +106,15 @@ def test_position_setter(focuser, tolerance):
def test_move_below_min_position(focuser, tolerance):
+ if isinstance(focuser, AstroMechanicsFocuser):
+ pytest.skip("This does not exist for astromechanics, skipping test")
focuser.move_to(focuser.min_position - 100)
assert focuser.position == pytest.approx(focuser.min_position, tolerance)
-def test_move_above_max_positons(focuser, tolerance):
+def test_move_above_max_position(focuser, tolerance):
+ if isinstance(focuser, AstroMechanicsFocuser):
+ pytest.skip("This does not exist for astromechanics, skipping test")
focuser.move_to(focuser.max_position + 100)
assert focuser.position == pytest.approx(focuser.max_position, tolerance)
@@ -131,9 +135,9 @@ def test_camera_association(focuser):
def test_camera_init():
"""
- Test focuser init via Camera constructor/
+ Test focuser init via Camera constructor
"""
- sim_camera = Camera(focuser={'model': 'simulator',
+ sim_camera = Camera(focuser={'model': 'panoptes.pocs.focuser.simulator.Focuser',
'focus_port': '/dev/ttyFAKE'})
assert isinstance(sim_camera.focuser, SimFocuser)
assert sim_camera.focuser.is_connected
diff --git a/src/panoptes/pocs/tests/test_images.py b/src/panoptes/pocs/tests/test_images.py
index 846e67c30..f3a8dfcbe 100644
--- a/src/panoptes/pocs/tests/test_images.py
+++ b/src/panoptes/pocs/tests/test_images.py
@@ -79,7 +79,7 @@ def test_solve_field_unsolved(unsolved_fits_file,
offset_info = im0.compute_offset(im1)
# print('offset_info:', offset_info)
expected_offset = [10.1 * u.arcsec, 5.29 * u.arcsec, 8.77 * u.arcsec]
- assert u.allclose(offset_info, expected_offset, rtol=0.005)
+ assert u.allclose(offset_info, expected_offset, rtol=0.1)
def test_solve_field_solved(solved_fits_file):
diff --git a/src/panoptes/pocs/tests/test_mount.py b/src/panoptes/pocs/tests/test_mount.py
index 42923dbaf..6436b5737 100644
--- a/src/panoptes/pocs/tests/test_mount.py
+++ b/src/panoptes/pocs/tests/test_mount.py
@@ -14,12 +14,9 @@
import requests
-config_host = 'localhost'
-config_port = 6563
-url = f'http://{config_host}:{config_port}/reset-config'
-
-def reset_conf():
+def reset_conf(config_host, config_port):
+ url = f'http://{config_host}:{config_port}/reset-config'
response = requests.post(url,
data=to_json({'reset': True}),
headers={'Content-Type': 'application/json'}
@@ -27,13 +24,13 @@ def reset_conf():
assert response.ok
-def test_create_mount_simulator():
+def test_create_mount_simulator(config_host, config_port):
# Use the simulator create function directly.
mount = create_mount_simulator()
assert isinstance(mount, AbstractMount) is True
-def test_create_mount_simulator_with_config():
+def test_create_mount_simulator_with_config(config_host, config_port):
# Remove mount from list of simulators.
set_config('simulator', hardware.get_all_names(without=['mount']))
# But setting the driver to `simulator` should return simulator.
@@ -41,20 +38,20 @@ def test_create_mount_simulator_with_config():
mount = create_mount_from_config()
assert isinstance(mount, AbstractMount) is True
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_create_mount_without_mount_info():
+def test_create_mount_without_mount_info(config_host, config_port):
# Set the mount config to none and then don't pass anything for error.
set_config('mount', None)
set_config('simulator', hardware.get_all_names(without=['mount']))
with pytest.raises(error.MountNotFound):
create_mount_from_config(mount_info=None)
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_create_mount_with_mount_info():
+def test_create_mount_with_mount_info(config_host, config_port):
# Pass the mount info directly with nothing in config.
mount_info = get_config('mount', default=dict())
mount_info['driver'] = 'simulator'
@@ -64,10 +61,10 @@ def test_create_mount_with_mount_info():
set_config('simulator', hardware.get_all_names(without=['mount']))
assert isinstance(create_mount_from_config(mount_info=mount_info), AbstractMount) is True
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_create_mount_with_earth_location():
+def test_create_mount_with_earth_location(config_host, config_port):
# Get location to pass manually.
loc = create_location_from_config()
# Set config to not have a location.
@@ -75,39 +72,39 @@ def test_create_mount_with_earth_location():
set_config('simulator', hardware.get_all_names())
assert isinstance(create_mount_from_config(earth_location=loc['earth_location']), AbstractMount) is True
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_create_mount_without_earth_location():
+def test_create_mount_without_earth_location(config_host, config_port):
set_config('location', None)
with pytest.raises(error.PanError):
create_mount_from_config(earth_location=None)
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_bad_mount_port():
+def test_bad_mount_port(config_host, config_port):
# Remove the mount from the list of simulators so it thinks we have a real one.
simulators = get_config('simulator')
with suppress(KeyError, AttributeError):
- simulators.remove('mount')
+ simulators.pop('mount')
set_config('simulator', simulators)
# Set a bad port, which should cause a fail before actual mount creation.
set_config('mount.serial.port', 'foobar')
with pytest.raises(error.MountNotFound):
create_mount_from_config()
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_bad_mount_driver():
+def test_bad_mount_driver(config_host, config_port):
# Remove the mount from the list of simulators so it thinks we have a real one.
simulators = get_config('simulator')
with suppress(KeyError, AttributeError):
- simulators.remove('mount')
+ simulators.pop('mount')
set_config('simulator', simulators)
# Set a bad port, which should cause a fail before actual mount creation.
set_config('mount.serial.driver', 'foobar')
with pytest.raises(error.MountNotFound):
create_mount_from_config()
- reset_conf()
+ reset_conf(config_host, config_port)
diff --git a/src/panoptes/pocs/tests/test_mount_simulator.py b/src/panoptes/pocs/tests/test_mount_simulator.py
index 0012ab140..72059c74e 100644
--- a/src/panoptes/pocs/tests/test_mount_simulator.py
+++ b/src/panoptes/pocs/tests/test_mount_simulator.py
@@ -1,14 +1,13 @@
import os
-import pytest
+import pytest
from astropy import units as u
from astropy.coordinates import EarthLocation
from astropy.coordinates import SkyCoord
-
from panoptes.pocs.mount.simulator import Mount
-from panoptes.utils.config.client import get_config
from panoptes.utils import error
-from panoptes.utils import altaz_to_radec
+from panoptes.utils.config.client import get_config
+from panoptes.utils.utils import altaz_to_radec
@pytest.fixture
diff --git a/src/panoptes/pocs/tests/test_observatory.py b/src/panoptes/pocs/tests/test_observatory.py
index c33f58f66..6afedc73e 100644
--- a/src/panoptes/pocs/tests/test_observatory.py
+++ b/src/panoptes/pocs/tests/test_observatory.py
@@ -1,5 +1,6 @@
import os
import time
+from contextlib import suppress
import pytest
from astropy.time import Time
@@ -18,18 +19,15 @@
from panoptes.pocs.mount import create_mount_from_config
from panoptes.pocs.mount import create_mount_simulator
from panoptes.pocs.dome import create_dome_simulator
-from panoptes.pocs.camera import create_camera_simulator
+from panoptes.pocs.camera import create_cameras_from_config
from panoptes.pocs.scheduler import create_scheduler_from_config
from panoptes.pocs.utils.location import create_location_from_config
import requests
-config_host = 'localhost'
-config_port = 6563
-url = f'http://{config_host}:{config_port}/reset-config'
-
-def reset_conf():
+def reset_conf(config_host, config_port):
+ url = f'http://{config_host}:{config_port}/reset-config'
response = requests.post(url,
data=to_json({'reset': True}),
headers={'Content-Type': 'application/json'}
@@ -39,7 +37,7 @@ def reset_conf():
@pytest.fixture(scope='function')
def cameras():
- return create_camera_simulator()
+ return create_cameras_from_config(recreate_existing=True)
@pytest.fixture(scope='function')
@@ -72,19 +70,19 @@ def test_remove_cameras(observatory, cameras):
observatory.remove_camera(cam_name)
-def test_bad_site():
+def test_bad_site(config_host, config_port):
set_config('location', {})
with pytest.raises(error.PanError):
Observatory()
- reset_conf()
+ reset_conf(config_host, config_port)
def test_cannot_observe(caplog):
obs = Observatory()
site_details = create_location_from_config()
- cameras = create_camera_simulator()
+ cameras = create_cameras_from_config()
assert obs.can_observe is False
time.sleep(0.5) # log sink time
@@ -117,7 +115,7 @@ def test_camera_wrong_type():
def test_camera():
- cameras = create_camera_simulator()
+ cameras = create_cameras_from_config()
obs = Observatory(cameras=cameras)
assert obs.has_cameras
@@ -183,7 +181,7 @@ def test_set_mount():
set_config('mount', {
'brand': 'Simulacrum',
'driver': 'simulator',
- 'model': 'simulator',
+ 'model': 'panoptes.pocs.camera.simulator.dslr',
})
mount = create_mount_from_config()
obs.set_mount(mount=mount)
@@ -360,7 +358,12 @@ def test_autofocus_coarse(observatory):
def test_autofocus_named(observatory):
- cam_names = [name for name in observatory.cameras.keys()]
+ # Get the list of cameras with a focuser.
+ cam_names = [name
+ for name, camera
+ in observatory.cameras.items()
+ if hasattr(camera, 'focuser') and camera.focuser is not None
+ ]
# Call autofocus on just one camera.
events = observatory.autofocus_cameras(camera_list=[cam_names[0]])
assert len(events) == 1
@@ -377,7 +380,8 @@ def test_autofocus_bad_name(observatory):
def test_autofocus_focusers_disconnected(observatory):
for camera in observatory.cameras.values():
- camera.focuser._connected = False
+ if hasattr(camera, 'focuser') and camera.focuser is not None:
+ camera.focuser._connected = False
events = observatory.autofocus_cameras()
assert events == {}
diff --git a/src/panoptes/pocs/tests/test_pocs.py b/src/panoptes/pocs/tests/test_pocs.py
index a84b299e6..39f0853c5 100644
--- a/src/panoptes/pocs/tests/test_pocs.py
+++ b/src/panoptes/pocs/tests/test_pocs.py
@@ -15,17 +15,14 @@
from panoptes.utils.serializers import to_json, to_yaml
from panoptes.pocs.mount import create_mount_simulator
-from panoptes.pocs.camera import create_cameras_from_config
from panoptes.pocs.dome import create_dome_simulator
+from panoptes.pocs.camera import create_cameras_from_config
from panoptes.pocs.scheduler import create_scheduler_from_config
from panoptes.pocs.utils.location import create_location_from_config
-config_host = 'localhost'
-config_port = 6563
-url = f'http://{config_host}:{config_port}/reset-config'
-
-def reset_conf():
+def reset_conf(config_host, config_port):
+ url = f'http://{config_host}:{config_port}/reset-config'
response = requests.post(url,
data=to_json({'reset': True}),
headers={'Content-Type': 'application/json'}
@@ -43,6 +40,16 @@ def mount():
return create_mount_simulator()
+@pytest.fixture(scope='function')
+def pocstime_night():
+ return "2020-01-01 08:00:00"
+
+
+@pytest.fixture(scope='function')
+def pocstime_day():
+ return "2020-01-01 22:00:00"
+
+
@pytest.fixture(scope='function')
def site_details():
return create_location_from_config()
@@ -77,13 +84,13 @@ def dome():
@pytest.fixture(scope='function')
-def pocs(observatory):
+def pocs(observatory, config_host, config_port):
os.environ['POCSTIME'] = '2020-01-01 08:00:00'
pocs = POCS(observatory, run_once=True, simulators=['power'])
yield pocs
pocs.power_down()
- reset_conf()
+ reset_conf(config_host, config_port)
@pytest.fixture(scope='function')
@@ -95,11 +102,24 @@ def pocs_with_dome(pocs, dome):
pocs.power_down()
+# An observation that is valid during the day
@pytest.fixture(scope='module')
def valid_observation():
return {
- 'name': 'HIP 36850',
- 'position': '113.65 deg +31.887 deg',
+ 'name': 'TEST TARGET',
+ 'position': '100.00 deg +00.887 deg',
+ 'priority': '100',
+ 'exptime': 2,
+ 'min_nexp': 2,
+ 'exp_set_size': 2,
+ }
+
+# An observation that is valid at night
+@pytest.fixture(scope='module')
+def valid_observation_day():
+ return {
+ 'name': 'TEST TARGET',
+ 'position': '300.00 deg +70.887 deg',
'priority': '100',
'exptime': 2,
'min_nexp': 2,
@@ -145,21 +165,21 @@ def test_simple_simulator(pocs, caplog):
assert pocs.is_safe()
-def test_is_weather_and_dark_simulator(pocs):
+def test_is_weather_and_dark_simulator(pocs, pocstime_night, pocstime_day):
pocs.initialize()
# Night simulator
pocs.set_config('simulator', 'all')
- os.environ['POCSTIME'] = '2020-01-01 08:00:00' # is dark
+ os.environ['POCSTIME'] = pocstime_night # is dark
assert pocs.is_dark() is True
- os.environ['POCSTIME'] = '2020-01-01 18:00:00' # is day
+ os.environ['POCSTIME'] = pocstime_day # is day
assert pocs.is_dark() is True
# No night simulator
pocs.set_config('simulator', hardware.get_all_names(without=['night']))
- os.environ['POCSTIME'] = '2020-01-01 08:00:00' # is dark
+ os.environ['POCSTIME'] = pocstime_night # is dark
assert pocs.is_dark() is True
- os.environ['POCSTIME'] = '2020-01-01 18:00:00' # is day
+ os.environ['POCSTIME'] = pocstime_day # is day
assert pocs.is_dark() is False
pocs.set_config('simulator', ['camera', 'mount', 'weather', 'night'])
@@ -182,31 +202,6 @@ def test_is_weather_safe_no_simulator(pocs):
assert pocs.is_weather_safe() is False
-def test_unsafe_park(pocs):
- pocs.set_config('simulator', 'all')
- pocs.initialize()
- assert pocs.is_initialized is True
- os.environ['POCSTIME'] = '2020-01-01 08:00:00'
- assert pocs.state == 'sleeping'
- pocs.get_ready()
- assert pocs.state == 'ready'
- pocs.schedule()
- assert pocs.state == 'scheduling'
-
- # My time goes fast...
- os.environ['POCSTIME'] = '2020-01-01 18:00:00'
- pocs.set_config('simulator', hardware.get_all_names(without=['night']))
-
- assert pocs.is_safe() is False
-
- assert pocs.state == 'parking'
- pocs.set_park()
- pocs.clean_up()
- pocs.goto_sleep()
- assert pocs.state == 'sleeping'
- pocs.power_down()
-
-
def test_no_ac_power(pocs):
# Simulator makes AC power safe
assert pocs.has_ac_power() is True
@@ -344,31 +339,25 @@ def test_pocs_park_to_ready_without_observations(pocs):
assert pocs.is_safe() is False
-def test_run_wait_until_safe(observatory,
- valid_observation,
- ):
- os.environ['POCSTIME'] = '2020-01-01 08:00:00'
-
- # Make sure DB is clear for current weather
- observatory.db.clear_current('weather')
+def test_run_wait_until_safe(observatory, valid_observation_day, pocstime_day, pocstime_night):
+ os.environ['POCSTIME'] = pocstime_day
- observatory.logger.info('start_pocs ENTER')
# Remove weather simulator, else it would always be safe.
- observatory.set_config('simulator', hardware.get_all_names(without=['weather']))
+ observatory.set_config('simulator', hardware.get_all_names(without=['night']))
pocs = POCS(observatory)
pocs.set_config('wait_delay', 5) # Check safety every 5 seconds.
pocs.observatory.scheduler.clear_available_observations()
- pocs.observatory.scheduler.add_observation(valid_observation)
+ pocs.observatory.scheduler.add_observation(valid_observation_day)
assert pocs.connected is True
assert pocs.is_initialized is False
pocs.initialize()
pocs.logger.info('Starting observatory run')
- # Weather is bad and unit is is connected but not set.
- assert pocs.is_weather_safe() is False
+ # Not dark and unit is is connected but not set.
+ assert not pocs.is_dark()
assert pocs.is_initialized
assert pocs.connected
assert pocs.do_states
@@ -389,25 +378,81 @@ def start_pocs():
pocs_thread = threading.Thread(target=start_pocs, daemon=True)
pocs_thread.start()
- # Wait until we are in the waiting state.
- while not pocs.next_state == 'ready':
+ assert pocs.is_safe(park_if_not_safe=False) is False
+
+ # Wait to pretend we're waiting for horizon
+ time.sleep(5)
+ os.environ['POCSTIME'] = pocstime_night
+ assert pocs.is_dark()
+
+ pocs.logger.warning(f'Waiting to get to slewing state...')
+ while pocs.next_state != 'slewing':
time.sleep(1)
- assert pocs.is_safe() is False
+ pocs.logger.warning(f'Stopping states via pocs.DO_STATES')
+ observatory.set_config('pocs.DO_STATES', False)
+
+ observatory.logger.warning(f'Waiting on pocs_thread')
+ pocs_thread.join(timeout=300)
+
+ assert pocs_thread.is_alive() is False
+
+
+def test_unsafe_park(observatory, valid_observation, pocstime_night):
+ os.environ['POCSTIME'] = pocstime_night
- # Wait to pretend we're waiting for weather
- time.sleep(2)
+ # Remove weather simulator, else it would always be safe.
+ observatory.set_config('simulator', hardware.get_all_names(without=['night', 'weather']))
- # Insert a dummy weather record to break wait
+ pocs = POCS(observatory)
+ pocs.set_config('wait_delay', 5) # Check safety every 5 seconds.
+
+ pocs.observatory.scheduler.clear_available_observations()
+ pocs.observatory.scheduler.add_observation(valid_observation)
observatory.logger.warning(f'Inserting safe weather reading')
observatory.db.insert_current('weather', {'safe': True})
- assert pocs.is_safe() is True
+ assert pocs.connected is True
+ assert pocs.is_initialized is False
+ pocs.initialize()
+ pocs.logger.info('Starting observatory run')
- while pocs.next_state != 'slewing':
- pocs.logger.warning(
- f'Waiting to get to scheduling state. Currently next_state={pocs.next_state}')
- time.sleep(1)
+ # Weather is bad and unit is is connected but not set.
+ assert pocs.is_safe()
+ assert pocs.is_initialized
+ assert pocs.connected
+ assert pocs.do_states
+ assert pocs.next_state is None
+
+ pocs.set_config('wait_delay', 1)
+
+ def start_pocs():
+ # Start running, BLOCKING.
+ pocs.logger.info(f'start_pocs ENTER')
+ pocs.run(run_once=True, exit_when_done=True)
+
+ # After done running.
+ assert pocs.is_weather_safe() is True
+ pocs.power_down()
+ observatory.logger.info('start_pocs EXIT')
+
+ pocs_thread = threading.Thread(target=start_pocs, daemon=True)
+ pocs_thread.start()
+
+ # Insert bad weather report while slewing
+ pocs.logger.info(f'Waiting to get to slewing state...')
+ while pocs.state != "slewing":
+ pass
+ pocs.logger.info("Inserting bad weather record.")
+ observatory.db.insert_current('weather', {'safe': False})
+
+ # No longer safe, so should transition to parking
+ pocs.logger.info(f'Waiting to get to parked state...')
+ while True:
+ if pocs.state in ['parking', 'parked']:
+ break
+ assert pocs.state in ["slewing", "parking", "parked"] # Should be one of these states
+ time.sleep(0.5)
pocs.logger.warning(f'Stopping states via pocs.DO_STATES')
observatory.set_config('pocs.DO_STATES', False)
@@ -420,8 +465,9 @@ def start_pocs():
def test_run_power_down_interrupt(observatory,
valid_observation,
+ pocstime_night
):
- os.environ['POCSTIME'] = '2020-01-01 08:00:00'
+ os.environ['POCSTIME'] = pocstime_night
observatory.logger.info('start_pocs ENTER')
# Remove weather simulator, else it would always be safe.
@@ -451,9 +497,9 @@ def start_pocs():
pocs_thread = threading.Thread(target=start_pocs, daemon=True)
pocs_thread.start()
- while pocs.next_state != 'scheduling':
+ while pocs.next_state != 'slewing':
pocs.logger.debug(
- f'Waiting to get to scheduling state. Currently next_state={pocs.next_state}')
+ f'Waiting to get to slewing state. Currently next_state={pocs.next_state}')
time.sleep(1)
pocs.logger.warning(f'Stopping states via pocs.DO_STATES')
@@ -465,7 +511,7 @@ def start_pocs():
assert pocs_thread.is_alive() is False
-def test_custom_state_file(observatory, temp_file):
+def test_custom_state_file(observatory, temp_file, config_host, config_port):
state_table = POCS.load_state_table()
assert isinstance(state_table, dict)
@@ -477,7 +523,7 @@ def test_custom_state_file(observatory, temp_file):
pocs = POCS(observatory, state_machine_file=file_path, run_once=True, simulators=['power'])
pocs.initialize()
pocs.power_down()
- reset_conf()
+ reset_conf(config_host, config_port)
def test_free_space(pocs, caplog):
diff --git a/src/panoptes/pocs/tests/test_scheduler.py b/src/panoptes/pocs/tests/test_scheduler.py
index 7c7e9cb51..512d0732c 100644
--- a/src/panoptes/pocs/tests/test_scheduler.py
+++ b/src/panoptes/pocs/tests/test_scheduler.py
@@ -8,12 +8,9 @@
from panoptes.pocs.utils.location import create_location_from_config
from panoptes.utils.serializers import to_json
-config_host = 'localhost'
-config_port = 6563
-url = f'http://{config_host}:{config_port}/reset-config'
-
-def reset_conf():
+def reset_conf(config_host, config_port):
+ url = f'http://{config_host}:{config_port}/reset-config'
response = requests.post(url,
data=to_json({'reset': True}),
headers={'Content-Type': 'application/json'}
@@ -21,31 +18,31 @@ def reset_conf():
assert response.ok
-def test_bad_scheduler_type():
+def test_bad_scheduler_type(config_host, config_port):
set_config('scheduler.type', 'foobar')
site_details = create_location_from_config()
with pytest.raises(error.NotFound):
create_scheduler_from_config(observer=site_details['observer'])
- reset_conf()
+ reset_conf(config_host, config_port)
-def test_bad_scheduler_fields_file():
+def test_bad_scheduler_fields_file(config_host, config_port):
set_config('scheduler.fields_file', 'foobar')
site_details = create_location_from_config()
with pytest.raises(error.NotFound):
create_scheduler_from_config(observer=site_details['observer'])
- reset_conf()
+ reset_conf(config_host, config_port)
def test_no_observer():
assert isinstance(create_scheduler_from_config(observer=None), BaseScheduler) is True
-def test_no_scheduler_in_config():
+def test_no_scheduler_in_config(config_host, config_port):
set_config('scheduler', None)
site_details = create_location_from_config()
assert create_scheduler_from_config(
observer=site_details['observer']) is None
- reset_conf()
+ reset_conf(config_host, config_port)
diff --git a/src/panoptes/pocs/tests/utils/test_logger.py b/src/panoptes/pocs/tests/utils/test_logger.py
index 7a78d43ca..3d6f5db1d 100644
--- a/src/panoptes/pocs/tests/utils/test_logger.py
+++ b/src/panoptes/pocs/tests/utils/test_logger.py
@@ -14,5 +14,10 @@ def test_base_logger(caplog, profile, tmp_path):
full_log_file=None)
logger.debug('Hello')
time.sleep(1) # Wait for log to make it there.
- assert caplog.records[-1].message == 'Hello'
- assert caplog.records[-1].levelname == 'DEBUG'
+
+ see_log = False
+ for rec in caplog.records[-5:]:
+ if rec.message == 'Hello':
+ see_log = True
+
+ assert see_log
diff --git a/src/panoptes/pocs/utils/logger.py b/src/panoptes/pocs/utils/logger.py
index 4732ad6b0..432d31231 100644
--- a/src/panoptes/pocs/utils/logger.py
+++ b/src/panoptes/pocs/utils/logger.py
@@ -19,8 +19,8 @@ def __init__(self):
self.padding = 0
# Level Time_UTC Time_Local dynamic_padding Message
self.fmt = "{level:.1s} " \
- "{time:MM-DD HH:mm:ss.ss!UTC}>" \
- " ({time:HH:mm:ss.ss})> " \
+ "{time:MM-DD HH:mm:ss.SSS!UTC}>" \
+ " ({time:HH:mm:ss zz})> " \
"| {name} {function}:{line}{extra[padding]} | " \
"{message}\n"
self.handlers = dict()
@@ -86,7 +86,7 @@ def get_logger(console_log_file='panoptes.log',
loguru_logger.remove(0)
stderr_format = "{level:.1s} " \
- "{time:MM-DD HH:mm:ss.ss!UTC}> " \
+ "{time:MM-DD HH:mm:ss.SSS!UTC}> " \
"{message}"
stderr_id = loguru_logger.add(
diff --git a/src/panoptes/pocs/utils/plotting.py b/src/panoptes/pocs/utils/plotting.py
new file mode 100644
index 000000000..edd5c5655
--- /dev/null
+++ b/src/panoptes/pocs/utils/plotting.py
@@ -0,0 +1,93 @@
+import gc
+
+import matplotlib.pyplot as plt
+from matplotlib.colors import LogNorm
+
+from panoptes.utils.images.plot import get_palette, add_colorbar
+from panoptes.pocs.utils.logger import get_logger
+
+logger = get_logger()
+
+
+def make_autofocus_plot(output_path,
+ initial_thumbnail,
+ final_thumbnail,
+ initial_focus,
+ final_focus,
+ focus_positions,
+ metrics,
+ merit_function,
+ line_fit=None,
+ plot_title='Autofocus Plot',
+ plot_width=9, # inches
+ plot_height=18, # inches
+ ):
+ """Make autofocus plots.
+
+ This will make three plots, the top and bottom plots showing the initial and
+ final thumbnail, respectively. The middle plot will contain the scatter plot
+ for the `metrics` for the given `focus_positions`.
+
+ Args:
+ output_path (str): Path for saving plot.
+ initial_thumbnail (np.array): The data for the initial thumbnail.
+ final_thumbnail (np.array): The data for the final thumbnail.
+ initial_focus (int): The initial focus position.
+ final_focus (int): The final focus position.
+ focus_positions (np.array): An array of `int` corresponding the focus positions.
+ metrics (np.array): An array of `float` corresponding to the measured metrics.
+ merit_function (str): The name of the merit function used to produce the metrics.
+ line_fit (tuple(np.array, np.array)): A tuple for the fitted line. The
+ first entry should be an array of `int` used to calculate fit, the second
+ entry should be an array of the fitted values.
+ plot_title (str): Title to use for plot
+ plot_width (int): The plot width in inches.
+ plot_height (int): The plot height in inches.
+
+ Returns:
+ str: Full path the saved plot.
+ """
+ fig, axes = plt.subplots(3, 1)
+ fig.set_size_inches(plot_width, plot_height)
+
+ # Initial thumbnail.
+ ax0 = axes[0]
+ im0 = ax0.imshow(initial_thumbnail, interpolation='none', cmap=get_palette(), norm=LogNorm())
+ add_colorbar(im0)
+ ax0.set_title(f'Initial focus position: {initial_focus}')
+
+ # Focus positions scatter plot.
+ ax1 = axes[1]
+ ax1.plot(focus_positions, metrics, 'bo', label=f'{merit_function}')
+ # Line fit.
+ if line_fit:
+ ax1.plot(line_fit[0], line_fit[1], 'b-', label='Polynomial fit')
+
+ # ax1.set_xlim(focus_positions[0] - focus_step / 2, focus_positions[-1] + focus_step / 2)
+ u_limit = 1.10 * metrics.max()
+ l_limit = min(0.95 * metrics.min(), 1.05 * metrics.min())
+ ax1.set_ylim(l_limit, u_limit)
+ ax1.vlines(initial_focus, l_limit, u_limit, colors='k', linestyles=':', label='Initial focus')
+ ax1.vlines(final_focus, l_limit, u_limit, colors='k', linestyles='--', label='Best focus')
+
+ ax1.set_xlabel('Focus position')
+ ax1.set_ylabel('Focus metric')
+
+ ax1.set_title(plot_title)
+ ax1.legend()
+
+ # Final thumbnail plot.
+ ax2 = axes[2]
+ im2 = ax2.imshow(final_thumbnail, interpolation='none', cmap=get_palette(), norm=LogNorm())
+ add_colorbar(im2)
+ ax2.set_title(f'Final focus position: {final_focus}')
+
+ fig.savefig(output_path, transparent=False, bbox_inches='tight')
+
+ # Close, close, close, and close.
+ plt.cla()
+ plt.clf()
+ plt.close(fig)
+ gc.collect()
+
+ return output_path
diff --git a/src/panoptes/pocs/utils/theskyx.py b/src/panoptes/pocs/utils/theskyx.py
new file mode 100644
index 000000000..2f142e5d5
--- /dev/null
+++ b/src/panoptes/pocs/utils/theskyx.py
@@ -0,0 +1,74 @@
+import socket
+
+from panoptes.utils import error
+from panoptes.utils.logging import logger
+
+
+class TheSkyX(object):
+ """ A socket connection for communicating with TheSkyX
+
+
+ """
+
+ def __init__(self, host='localhost', port=3040, connect=True, *args, **kwargs):
+ self.logger = logger
+
+ self._host = host
+ self._port = port
+
+ self._socket = None
+
+ self._is_connected = False
+ if connect:
+ self.connect()
+
+ @property
+ def is_connected(self):
+ return self._is_connected
+
+ def connect(self):
+ """ Sets up serial connection """
+ self.logger.debug('Making TheSkyX connection at {}:{}'.format(self._host, self._port))
+ if not self.is_connected:
+
+ try:
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.connect((self._host, self._port))
+ except ConnectionRefusedError:
+ self.logger.warning('Cannot create connection to TheSkyX')
+ else:
+ self._is_connected = True
+ self.logger.info('Connected to TheSkyX via {}:{}'.format(self._host, self._port))
+
+ def write(self, value):
+ try:
+ assert isinstance(value, str)
+ self.socket.sendall(value.encode())
+ except AttributeError:
+ raise error.BadConnection("Not connected to TheSkyX")
+
+ def read(self, timeout=5):
+ try:
+ self.socket.settimeout(timeout)
+ response = None
+ err = None
+
+ try:
+ response = self.socket.recv(2048).decode()
+ if '|' in response:
+ response, err = response.split('|')
+
+ if 'Error:' in response:
+ response, err = response.split(':')
+
+ if err is not None and 'No error' not in err:
+ if 'Error = 303' in err:
+ raise error.TheSkyXKeyError("Invalid TheSkyX key")
+
+ raise error.TheSkyXError(err)
+ except socket.timeout: # pragma: no cover
+ raise error.TheSkyXTimeout()
+
+ return response
+ except AttributeError:
+ raise error.BadConnection("Not connected to TheSkyX")
diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml
new file mode 100644
index 000000000..b2a5b9001
--- /dev/null
+++ b/tests/docker-compose.yaml
@@ -0,0 +1,51 @@
+version: '3.7'
+services:
+ config-server:
+ image: panoptes-pocs:latest
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
+ init: true
+ tty: true
+ network_mode: host
+ environment:
+ # These need to be defined in the shell on the host.
+ PANDIR:
+ PANLOG:
+ PANOPTES_CONFIG_HOST:
+ PANOPTES_CONFIG_PORT:
+ PANOPTES_CONFIG_FILE:
+ command: [ "panoptes-config-server --verbose run" ]
+ pocs:
+ image: panoptes-pocs:latest
+ build:
+ context: ../
+ dockerfile: docker/Dockerfile
+ init: true
+ privileged: true
+ network_mode: host
+ environment:
+ # These need to be defined in the shell on the host.
+ PANDIR:
+ PANLOG:
+ PANOPTES_CONFIG_HOST:
+ PANOPTES_CONFIG_PORT:
+ PANOPTES_CONFIG_FILE:
+ # No-op to keep machine running, use $POCS/bin/pocs-shell to access
+ command: [ "wait-for-it ${PANOPTES_CONFIG_PORT}:${PANOPTES_CONFIG_PORT} -- pytest" ]
+ volumes:
+ - logdir:/var/panoptes/logs
+ - builddir:/var/panoptes/POCS/build
+volumes:
+ logdir:
+ driver: local
+ driver_opts:
+ type: none
+ device: logs
+ o: bind
+ builddir:
+ driver: local
+ driver_opts:
+ type: none
+ device: build
+ o: bind
diff --git a/tests/env b/tests/env
new file mode 100644
index 000000000..159f5958b
--- /dev/null
+++ b/tests/env
@@ -0,0 +1,13 @@
+# Envfile to be loaded by docker-compose for testing.
+#
+# Note this doesn't support full interpolation or quotes.
+# See https://docs.docker.com/compose/compose-file/#env_file
+#
+# THESE ARE VALUES INSIDE A RUNNING DOCKER CONTAINER.
+#
+PANDIR=/var/panoptes
+POCS=/var/panoptes/POCS
+PANLOG=/var/panoptes/logs
+PANOPTES_CONFIG_FILE=/var/panoptes/POCS/tests/testing.yaml
+PANOPTES_CONFIG_HOST=0.0.0.0
+PANOPTES_CONFIG_PORT=8765
diff --git a/tests/test_theskyx_utils.py b/tests/test_theskyx_utils.py
new file mode 100644
index 000000000..4fb84c138
--- /dev/null
+++ b/tests/test_theskyx_utils.py
@@ -0,0 +1,80 @@
+import pytest
+from mocket import Mocket
+from panoptes.pocs.utils.theskyx import TheSkyX
+from panoptes.utils import error
+
+
+@pytest.fixture(scope="function")
+def skyx(data_dir, request):
+ """Create TheSkyX class but don't connect.t
+
+ If running with a real connection TheSkyX then the Mokcet will
+ be disabled here.
+ """
+
+ # Use `--theskyx thesky` on cli to run without mock
+ Mocket.enable('theskyx', data_dir)
+ if request.config.getoption('--theskyx'):
+ Mocket.disable()
+
+ theskyx = TheSkyX(connect=False)
+
+ yield theskyx
+ Mocket.disable()
+
+
+def test_default_connect(data_dir, request):
+ """Test connection to TheSkyX
+
+ If not running with a real connection then use Mocket
+ """
+ # Use `--theskyx thesky` on cli to run without mock
+ if not request.config.getoption('--theskyx'):
+ Mocket.enable('theskyx', data_dir)
+
+ skyx = TheSkyX()
+ assert skyx.is_connected is True
+
+
+def test_no_connect_write(skyx):
+ with pytest.raises(error.BadConnection):
+ skyx.write('/* Java Script */')
+
+
+def test_no_connect_read(skyx):
+ with pytest.raises(error.BadConnection):
+ skyx.read()
+
+
+def test_write_bad_key(skyx):
+ skyx.connect()
+ skyx.write('FOOBAR')
+ with pytest.raises(error.TheSkyXKeyError):
+ skyx.read()
+
+
+def test_write_no_command(skyx):
+ skyx.connect()
+ skyx.write('/* Java Script */')
+ assert skyx.read() == 'undefined'
+
+
+def test_get_build(skyx):
+ js = '''
+/* Java Script */
+var Out;
+Out=Application.version
+'''
+ skyx.connect()
+ skyx.write(js)
+ assert skyx.read().startswith('10.5')
+
+
+def test_error(skyx):
+ skyx.connect()
+ skyx.write('''
+/* Java Script */
+sky6RASCOMTele.FindHome()
+''')
+ with pytest.raises(error.TheSkyXError):
+ skyx.read()
diff --git a/tests/pocs_testing.yaml b/tests/testing.yaml
similarity index 59%
rename from tests/pocs_testing.yaml
rename to tests/testing.yaml
index 7d4f0685f..8c2fa0c31 100644
--- a/tests/pocs_testing.yaml
+++ b/tests/testing.yaml
@@ -8,9 +8,9 @@
# files and communicate with the Google Cloud network.
#
# Leave the pan_id at `PAN000` for testing until you have been assigned
-# an official id. Update pocs_local.yaml with offical name once received.
+# an official id. Update pocs_local.yaml with official name once received.
################################################################################
-name: Generic PANOPTES Unit
+name: Testing PANOPTES Unit
pan_id: PAN000
pocs:
@@ -26,7 +26,7 @@ location:
flat_horizon: -6 deg # Flats when sun between this and focus horizon.
focus_horizon: -12 deg # Dark enough to focus on stars.
observe_horizon: -18 deg # Sun below this limit to observe.
- obstructions: []
+ obstructions: [ ]
timezone: US/Hawaii
gmt_offset: -600 # Offset in minutes from GMT during.
# standard time (not daylight saving).
@@ -35,7 +35,7 @@ directories:
images: images
data: data
resources: POCS/resources/
- targets: POCS/resources/targets
+ targets: POCS/conf_files/targets
mounts: POCS/resources/mounts
db:
name: panoptes_testing
@@ -59,11 +59,104 @@ pointing:
exptime: 30 # seconds
max_iterations: 3
cameras:
- auto_detect: False
+ defaults:
+ primary: None
+ auto_detect: False
+ file_extension: fits
+ compress_fits: True
+ make_pretty_images: True
+ keep_jpgs: False
+ readout_time: 0.5 # seconds
+ timeout: 10 # seconds
+ filter_type: RGGB
+ cooling:
+ enabled: True
+ temperature:
+ target: 0 # celsius
+ tolerance: 0.1 # celsius
+ stable_time: 60 # seconds
+ check_interval: 5 # seconds
+ timeout: 300 # seconds
+ focuser:
+ enabled: True
+ autofocus_seconds: 0.1 # seconds
+ autofocus_size: 500 # seconds
+ autofocus_keep_files: False
devices:
- - model: simulator
- - model: simulator
-
+ - model: panoptes.pocs.camera.simulator.dslr.Camera
+ name: dslr.00
+ port: /dev/fake/dslr.00
+ readout_time: 0.5
+ - model: panoptes.pocs.camera.simulator.dslr.Camera
+ name: dslr.focuser.cooling.00
+ port: /dev/fake/dslr.focuser.cooling.00
+ # Different defaults for this camera.
+ cooling:
+ enabled: True
+ target: 1
+ tolerance: 0.1
+ stable_time: 1
+ check_interval: 0.5
+ timeout: 2.5
+ focuser:
+ model: panoptes.pocs.focuser.simulator.Focuser
+ focus_port: /dev/fake/focuser.00
+ initial_position: 20000
+ autofocus_range: [ 40, 80 ]
+ autofocus_step: [ 10, 20 ]
+ autofocus_seconds: 0.1
+ autofocus_size: 500
+ autofocus_keep_files: False
+ - model: panoptes.pocs.camera.simulator.dslr.Camera
+ name: dslr.filterwheel.cooling.00
+ port: /dev/fake/dslr.filterwheel.cooling.00
+ cooling:
+ enabled: True
+ target: 0
+ tolerance: 0.1
+ stable_time: 60
+ check_interval: 5
+ timeout: 300
+ filterwheel:
+ model: panoptes.pocs.filterwheel.simulator.FilterWheel
+ filter_names:
+ - one
+ - deux
+ - drei
+ - quattro
+ - blank
+ move_time: 0.1
+ timeout: 0.5
+ - model: panoptes.pocs.camera.simulator.ccd.Camera
+ name: ccd.filterwheel.focuser.cooling.00
+ serial_number: ccd.filterwheel.focuser.cooling.00
+ cooling:
+ enabled: True
+ target: 0
+ tolerance: 0.1
+ stable_time: 60
+ check_interval: 5
+ timeout: 300
+ focuser:
+ model: panoptes.pocs.focuser.simulator.Focuser
+ focus_port: /dev/fake/focuser.00
+ initial_position: 20000
+ autofocus_range: [ 40, 80 ]
+ autofocus_step: [ 10, 20 ]
+ autofocus_seconds: 0.1
+ autofocus_size: 500
+ autofocus_keep_files: False
+ filterwheel:
+ model: panoptes.pocs.filterwheel.simulator.FilterWheel
+ filter_names:
+ - one
+ - deux
+ - drei
+ - quattro
+ - blank
+ move_time: 0.1
+ timeout: 0.5
+ dark_position: blank
########################## Observations ########################################
# An observation folder contains a contiguous sequence of images of a target/field
@@ -82,7 +175,7 @@ cameras:
################################################################################
observations:
make_timelapse: True
- keep_jpgs: True
+ record_observations: True
######################## Google Network ########################################
# By default all images are stored on googlecloud servers and we also
@@ -112,7 +205,7 @@ panoptes_network:
# webhook_url: [your_webhook_url]
# output_timestamp: False
-state_machine: simple_state_table
+state_machine: panoptes
######################### Environmental Sensors ################################
# Configure the environmental sensors that are attached.