diff --git a/.coverage b/.coverage deleted file mode 100644 index 51e66d06..00000000 Binary files a/.coverage and /dev/null differ diff --git a/.gitignore b/.gitignore index f158d046..0ba60be8 100755 --- a/.gitignore +++ b/.gitignore @@ -138,5 +138,5 @@ dmypy.json .DS_Store *ascom.alpaca.simulators* *OmniSim* -!.coverage +!coverage.xml docs/source/api/auto_api/ diff --git a/coverage.xml b/coverage.xml new file mode 100644 index 00000000..5459e86b --- /dev/null +++ b/coverage.xml @@ -0,0 +1,9140 @@ + + + + + + /Users/will/Library/CloudStorage/Dropbox/research/macro/pyscope + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/conf.py b/docs/source/conf.py index 4a4774fc..1ceb1f04 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,7 +25,6 @@ graphviz_dot = "/usr/local/bin/dot" html_theme_options = { - "logo": { "image_light": "_static/pyscope_logo_small_gray.png", "image_dark": "_static/pyscope_logo_small.png", @@ -46,7 +45,7 @@ "url": "https://macroconsortium.org", "icon": "fa-solid fa-shuttle-space", }, - ] + ], } intersphinx_mapping["click"] = ("https://click.palletsprojects.com/en/8.1.x/", None) diff --git a/pyproject.toml b/pyproject.toml index 581b4bb2..97fbd0a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,8 +1,39 @@ [build-system] -requires = ["setuptools>=42.0", "wheel"] +requires = ["setuptools>=42.0", "wheel", "numpy>=1.25"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] addopts = [ "--import-mode=importlib", + "--cov", + "--cov-report=xml" ] + +[tool.black] +line-length = 88 + +[tool.coverage] + [tool.coverage.run] + branch = true + + [tool.coverage.report] + # Regexes for lines to exclude from consideration + exclude_also = [ + # Skip any pass lines such as may be used for @abstractmethod + "pass", + + # Don't complain about missing debug-only code: + "def __repr__", + "if self\\.debug", + + # Don't complain if tests don't hit defensive assertion code: + "raise AssertionError", + "raise NotImplementedError", + + # Don't complain if non-runnable code isn't run: + "if 0:", + "if __name__ == .__main__.:", + + # Don't complain about abstract methods, they aren't run: + "@(abc\\.)?abstractmethod", + ] diff --git a/pyscope/__init__.py b/pyscope/__init__.py index b65eaf74..5842b283 100644 --- a/pyscope/__init__.py +++ b/pyscope/__init__.py @@ -72,6 +72,8 @@ import logging +__version__ = "0.1.2" + from . import utils from . import observatory from . import telrun @@ -82,4 +84,3 @@ logger.setLevel(logging.INFO) __all__ = ["analysis", "observatory", "reduction", "telrun", "utils"] -__version__ = "0.1.1" diff --git a/pyscope/config/observatory.cfg b/pyscope/config/observatory.cfg index e69a858c..c23f6e51 100644 --- a/pyscope/config/observatory.cfg +++ b/pyscope/config/observatory.cfg @@ -4,17 +4,17 @@ site_name = Winer Observatory instrument_name = Robert L. Mutel Telescope -instrument_description = +instrument_description = 20-inch PlaneWave CDK -latitude = # dd:mm:ss.s +latitude = 31:39:56.08 # dd:mm:ss.s -longitude = # dd:mm:ss.s +longitude = -110:36:06.42 # dd:mm:ss.s -elevation = # meters +elevation = 1515.7 # meters -diameter = # meters +diameter = 0.508 # meters -focal_length = # meters +focal_length = 3.454 # meters [camera] @@ -23,8 +23,6 @@ camera_driver = maxim camera_ascom = False -camera_args = - camera_kwargs = cooler_setpoint = -20 # Celsius @@ -40,9 +38,7 @@ cover_calibrator_driver = ip_cover_calibrator cover_calibrator_ascom = False -cover_calibrator_args = - -cover_calibrator_kwargs = +cover_calibrator_kwargs = tcp_ip:192.168.2.22,tcp_port:2101,buffer_size:1024 cover_calibrator_alt = 30.09397 @@ -55,8 +51,6 @@ dome_driver = dome_ascom = -dome_args = - dome_kwargs = @@ -66,8 +60,6 @@ filter_wheel_driver = maxim filter_wheel_ascom = False -filter_wheel_args = - filter_wheel_kwargs = filters = L, 6, V, B, H, W, O, 1, I, X, G, R @@ -81,21 +73,15 @@ focuser_driver = ASCOM.PWI3.Focuser focuser_ascom = True -focuser_args = - focuser_kwargs = -focuser_max_error = 10 # counts - [observing_conditions] -observing_conditions_driver = weather_winer +observing_conditions_driver = html_observing_conditions observing_conditions_ascom = False -observing_conditions_args = - observing_conditions_kwargs = url:https://winer.org/Site/Weather.php @@ -103,17 +89,15 @@ observing_conditions_kwargs = url:https://winer.org/Site/Weather.php rotator_driver = -rotator_ascom = False - -rotator_args = +rotator_ascom = rotator_kwargs = -rotator_reverse = False +rotator_reverse = -rotator_min_angle = 0 # degrees +rotator_min_angle = -rotator_max_angle = 360 # degrees +rotator_max_angle = [safety_monitor] @@ -140,8 +124,6 @@ telescope_driver = SiTech.Telescope telescope_ascom = True -telescope_args = - telescope_kwargs = min_altitude = 21 # degrees @@ -153,8 +135,6 @@ settle_time = 5 autofocus_driver = pwi_autofocus -autofocus_args = - autofocus_kwargs = @@ -162,9 +142,9 @@ autofocus_kwargs = driver_0 = maxim -driver_1 = pinpoint_wcs +driver_1 = astrometry_net_wcs -driver_2 = astrometry_net_wcs +driver_2 = [scheduling] diff --git a/pyscope/observatory/ascom_camera.py b/pyscope/observatory/ascom_camera.py index 8b0a984e..a2acf305 100644 --- a/pyscope/observatory/ascom_camera.py +++ b/pyscope/observatory/ascom_camera.py @@ -20,10 +20,6 @@ def AbortExposure(self): logger.debug(f"ASCOMCamera.AbortExposure() called") self._device.AbortExposure() - def Choose(self, cameraID): - logger.debug(f"ASCOMCamera.Choose({cameraID}) called") - self._device.Choose(cameraID) - def PulseGuide(self, Direction, Duration): logger.debug(f"ASCOMCamera.PulseGuide({Direction}, {Duration}) called") self._device.PulseGuide(Direction, Duration) @@ -37,12 +33,20 @@ def StopExposure(self): self._device.StopExposure() @property - def BayerOffsetX(self): + def BayerOffsetX(self): # pragma: no cover + """ + .. warning:: + This property is not implemented in the ASCOM Alpaca protocol. + """ logger.debug(f"ASCOMCamera.BayerOffsetX property called") return self._device.BayerOffsetX @property - def BayerOffsetY(self): + def BayerOffsetY(self): # pragma: no cover + """ + .. warning:: + This property is not implemented in the ASCOM Alpaca protocol. + """ logger.debug(f"ASCOMCamera.BayerOffsetY property called") return self._device.BayerOffsetY diff --git a/pyscope/observatory/ascom_cover_calibrator.py b/pyscope/observatory/ascom_cover_calibrator.py index 95ae3714..d3c7d1bb 100644 --- a/pyscope/observatory/ascom_cover_calibrator.py +++ b/pyscope/observatory/ascom_cover_calibrator.py @@ -24,10 +24,6 @@ def CalibratorOn(self, Brightness): logger.debug(f"ASCOMCoverCalibrator.CalibratorOn({Brightness}) called") self._device.CalibratorOn(Brightness) - def Choose(self, CalibratorID): - logger.debug(f"ASCOMCoverCalibrator.Choose({CalibratorID}) called") - self._device.Choose(CalibratorID) - def CloseCover(self): logger.debug(f"ASCOMCoverCalibrator.CloseCover() called") self._device.CloseCover() diff --git a/pyscope/observatory/ascom_device.py b/pyscope/observatory/ascom_device.py index 8ae8ceb1..addb8e8b 100644 --- a/pyscope/observatory/ascom_device.py +++ b/pyscope/observatory/ascom_device.py @@ -33,26 +33,33 @@ def Action(self, ActionName, *ActionParameters): logger.debug(f"ASCOMDevice.Action({ActionName}, {ActionParameters})") return self._device.Action(ActionName, *ActionParameters) - def CommandBlind(self, Command, Raw): + def CommandBlind(self, Command, Raw): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ + logger.debug(f"ASCOMDevice.CommandBlind({Command}, {Raw})") self._device.CommandBlind(Command, Raw) - def CommandBool(self, Command, Raw): + def CommandBool(self, Command, Raw): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ + logger.debug(f"ASCOMDevice.CommandBool({Command}, {Raw})") return self._device.CommandBool(Command, Raw) - def CommandString(self, Command, Raw): + def CommandString(self, Command, Raw): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ + logger.debug(f"ASCOMDevice.CommandString({Command}, {Raw})") return self._device.CommandString(Command, Raw) - """def Dispose(self): - logger.debug(f"ASCOMDevice.Dispose()") - self._device.Dispose() - - def SetupDialog(self): - logger.debug(f"ASCOMDevice.SetupDialog()") - self._device.SetupDialog()""" - @property def Connected(self): logger.debug(f"ASCOMDevice.Connected property") diff --git a/pyscope/observatory/ascom_dome.py b/pyscope/observatory/ascom_dome.py index 4a93b344..66f730b8 100644 --- a/pyscope/observatory/ascom_dome.py +++ b/pyscope/observatory/ascom_dome.py @@ -20,10 +20,6 @@ def AbortSlew(self): logger.debug(f"ASCOMDome.AbortSlew() called") self._device.AbortSlew() - def Choose(self, DomeID): - logger.debug(f"ASCOMDome.Choose({DomeID}) called") - self._device.Choose(DomeID) - def CloseShutter(self): logger.debug(f"ASCOMDome.CloseShutter() called") self._device.CloseShutter() diff --git a/pyscope/observatory/ascom_filter_wheel.py b/pyscope/observatory/ascom_filter_wheel.py index 934bcfd4..101a0507 100644 --- a/pyscope/observatory/ascom_filter_wheel.py +++ b/pyscope/observatory/ascom_filter_wheel.py @@ -16,10 +16,6 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): protocol=protocol, ) - def Choose(self, FilterWheelID): - logger.debug(f"ASCOMFilterWheel.Choose({FilterWheelID}) called") - self._device.Choose(FilterWheelID) - @property def FocusOffsets(self): logger.debug(f"ASCOMFilterWheel.FocusOffsets property called") diff --git a/pyscope/observatory/ascom_focuser.py b/pyscope/observatory/ascom_focuser.py index 29cea890..cb5c5006 100644 --- a/pyscope/observatory/ascom_focuser.py +++ b/pyscope/observatory/ascom_focuser.py @@ -16,10 +16,6 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): protocol=protocol, ) - def Choose(self, FocuserID): - logger.debug(f"ASCOMFocuser.Choose({FocuserID}) called") - self._device.Choose(FocuserID) - def Halt(self): logger.debug(f"ASCOMFocuser.Halt() called") self._device.Halt() @@ -38,16 +34,6 @@ def IsMoving(self): logger.debug(f"ASCOMFocuser.IsMoving property called") return self._device.IsMoving - @property - def Link(self): - logger.debug(f"ASCOMFocuser.Link property called") - return self._device.Link - - @Link.setter - def Link(self, value): - logger.debug(f"ASCOMFocuser.Link property set to {value}") - self._device.Link = value - @property def MaxIncrement(self): logger.debug(f"ASCOMFocuser.MaxIncrement property called") diff --git a/pyscope/observatory/ascom_observing_conditions.py b/pyscope/observatory/ascom_observing_conditions.py index d11ab1d1..e84e38ad 100644 --- a/pyscope/observatory/ascom_observing_conditions.py +++ b/pyscope/observatory/ascom_observing_conditions.py @@ -16,10 +16,6 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): protocol=protocol, ) - def Choose(self, ObservingConditionsID): - logger.debug(f"ASCOMObservingConditions.Choose({ObservingConditionsID}) called") - self._device.Choose(ObservingConditionsID) - def Refresh(self): logger.debug("ASCOMObservingConditions.Refresh() called") self._device.Refresh() diff --git a/pyscope/observatory/ascom_rotator.py b/pyscope/observatory/ascom_rotator.py index 4be36484..45d18eb0 100644 --- a/pyscope/observatory/ascom_rotator.py +++ b/pyscope/observatory/ascom_rotator.py @@ -16,10 +16,6 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): protocol=protocol, ) - def Choose(self, RotatorID): - logger.debug(f"ASCOMRotator.Choose({RotatorID}) called") - self._device.Choose(RotatorID) - def Halt(self): logger.debug("ASCOMRotator.Halt() called") self._device.Halt() diff --git a/pyscope/observatory/ascom_safety_monitor.py b/pyscope/observatory/ascom_safety_monitor.py index bb231653..edae555d 100644 --- a/pyscope/observatory/ascom_safety_monitor.py +++ b/pyscope/observatory/ascom_safety_monitor.py @@ -16,10 +16,6 @@ def __init__(self, identifier, alpaca=False, device_number=0, protocol="http"): protocol=protocol, ) - def Choose(self, SafetyMonitorID): - logger.debug(f"ASCOMSafetyMonitor.Choose({SafetyMonitorID}) called") - self._device.Choose(SafetyMonitorID) - @property def IsSafe(self): logger.debug(f"ASCOMSafetyMonitor.IsSafe property called") diff --git a/pyscope/observatory/ascom_switch.py b/pyscope/observatory/ascom_switch.py index 831c6eae..f4fac71b 100644 --- a/pyscope/observatory/ascom_switch.py +++ b/pyscope/observatory/ascom_switch.py @@ -20,10 +20,6 @@ def CanWrite(self, ID): logger.debug(f"ASCOMSwitch.CanWrite({ID}) called") return self._device.CanWrite(ID) - def Choose(self, SwitchID): - logger.debug(f"ASCOMSwitch.Choose({SwitchID}) called") - self._device.Choose(SwitchID) - def GetSwitch(self, ID): logger.debug(f"ASCOMSwitch.GetSwitch({ID}) called") return self._device.GetSwitch(ID) diff --git a/pyscope/observatory/ascom_telescope.py b/pyscope/observatory/ascom_telescope.py index 835092db..3eb19cd1 100644 --- a/pyscope/observatory/ascom_telescope.py +++ b/pyscope/observatory/ascom_telescope.py @@ -28,10 +28,6 @@ def CanMoveAxis(self, Axis): logger.debug(f"ASCOMTelescope.CanMoveAxis({Axis}) called") return self._device.CanMoveAxis(Axis) - def Choose(self, TelescopeID): - logger.debug(f"ASCOMTelescope.Choose({TelescopeID}) called") - self._device.Choose(TelescopeID) - def DestinationSideOfPier(self, RightAscension, Declination): logger.debug( f"ASCOMTelescope.DestinationSideOfPier({RightAscension}, {Declination}) called" @@ -58,7 +54,11 @@ def SetPark(self): logger.debug("ASCOMTelescope.SetPark() called") self._device.SetPark() - def SlewToAltAz(self, Azimuth, Altitude): + def SlewToAltAz(self, Azimuth, Altitude): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ logger.debug(f"ASCOMTelescope.SlewToAltAz({Azimuth}, {Altitude}) called") self._device.SlewToAltAz(Azimuth, Altitude) @@ -66,7 +66,11 @@ def SlewToAltAzAsync(self, Azimuth, Altitude): logger.debug(f"ASCOMTelescope.SlewToAltAzAsync({Azimuth}, {Altitude}) called") self._device.SlewToAltAzAsync(Azimuth, Altitude) - def SlewToCoordinates(self, RightAscension, Declination): + def SlewToCoordinates(self, RightAscension, Declination): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ logger.debug( f"ASCOMTelescope.SlewToCoordinates({RightAscension}, {Declination}) called" ) @@ -78,7 +82,11 @@ def SlewToCoordinatesAsync(self, RightAscension, Declination): ) self._device.SlewToCoordinatesAsync(RightAscension, Declination) - def SlewToTarget(self): + def SlewToTarget(self): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this method. + """ logger.debug("ASCOMTelescope.SlewToTarget() called") self._device.SlewToTarget() @@ -184,12 +192,20 @@ def CanSetTracking(self): return self._device.CanSetTracking @property - def CanSlew(self): + def CanSlew(self): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this property. + """ logger.debug("ASCOMTelescope.CanSlew property accessed") return self._device.CanSlew @property - def CanSlewAltAz(self): + def CanSlewAltAz(self): # pragma: no cover + """ + .. deprecated:: 0.1.1 + ASCOM is deprecating this property. + """ logger.debug("ASCOMTelescope.CanSlewAltAz property accessed") return self._device.CanSlewAltAz diff --git a/pyscope/observatory/focuser.py b/pyscope/observatory/focuser.py index b5fe1682..b524de3d 100644 --- a/pyscope/observatory/focuser.py +++ b/pyscope/observatory/focuser.py @@ -31,16 +31,6 @@ def Absolute(self, value): def IsMoving(self): pass - @property - @abstractmethod - def Link(self): - pass - - @Link.setter - @abstractmethod - def Link(self, value): - pass - @property @abstractmethod def MaxIncrement(self): diff --git a/pyscope/observatory/html_observing_conditions.py b/pyscope/observatory/html_observing_conditions.py index c704c657..733bcbd2 100755 --- a/pyscope/observatory/html_observing_conditions.py +++ b/pyscope/observatory/html_observing_conditions.py @@ -1,7 +1,11 @@ +import logging import urllib.request +from ..utils import _get_number_from_line from .observing_conditions import ObservingConditions +logger = logging.getLogger(__name__) + class HTMLObservingConditions(ObservingConditions): def __init__( @@ -26,7 +30,7 @@ def __init__( sky_brightness_units="magdeg2", sky_brightness_numeric=True, sky_quality_keyword=b"SKYQUALITY", - sky_quality_units=None, + sky_quality_units="", sky_quality_numeric=True, sky_temperature_keyword=b"SKYTEMPERATURE", sky_temperature_units=b"F", @@ -47,7 +51,7 @@ def __init__( wind_speed_units="mph", wind_speed_numeric=True, last_updated_keyword=b"LASTUPDATED", - last_updated_units=None, + last_updated_units="", last_updated_numeric=True, ): logger.debug( @@ -162,85 +166,85 @@ def Refresh(self): lines = stream.readlines() for line in lines: - cloud_cover = self._get_number_from_line( + cloud_cover = _get_number_from_line( line, self._cloud_cover_keyword, self._cloud_cover_units, self._cloud_cover_numeric, ) - dew_point = self._get_number_from_line( + dew_point = _get_number_from_line( line, self._dew_point_keyword, self._dew_point_units, self._dew_point_numeric, ) - humidity = self._get_number_from_line( + humidity = _get_number_from_line( line, self._humidity_keyword, self._humidity_units, self._humidity_numeric, ) - pressure = self._get_number_from_line( + pressure = _get_number_from_line( line, self._pressure_keyword, self._pressure_units, self._pressure_numeric, ) - rain_rate = self._get_number_from_line( + rain_rate = _get_number_from_line( line, self._rain_rate_keyword, self._rain_rate_units, self._rain_rate_numeric, ) - sky_brightness = self._get_number_from_line( + sky_brightness = _get_number_from_line( line, self._sky_brightness_keyword, self._sky_brightness_units, self._sky_brightness_numeric, ) - sky_quality = self._get_number_from_line( + sky_quality = _get_number_from_line( line, self._sky_quality_keyword, self._sky_quality_units, self._sky_quality_numeric, ) - sky_temperature = self._get_number_from_line( + sky_temperature = _get_number_from_line( line, self._sky_temperature_keyword, self._sky_temperature_units, self._sky_temperature_numeric, ) - star_fwhm = self._get_number_from_line( + star_fwhm = _get_number_from_line( line, self._star_fwhm_keyword, self._star_fwhm_units, self._star_fwhm_numeric, ) - temperature = self._get_number_from_line( + temperature = _get_number_from_line( line, self._temperature_keyword, self._temperature_units, self._temperature_numeric, ) - wind_direction = self._get_number_from_line( + wind_direction = _get_number_from_line( line, self._wind_direction_keyword, self._wind_direction_units, self._wind_direction_numeric, ) - wind_gust = self._get_number_from_line( + wind_gust = _get_number_from_line( line, self._wind_gust_keyword, self._wind_gust_units, self._wind_gust_numeric, ) - wind_speed = self._get_number_from_line( + wind_speed = _get_number_from_line( line, self._wind_speed_keyword, self._wind_speed_units, self._wind_speed_numeric, ) - last_updated = self._get_number_from_line( + last_updated = _get_number_from_line( line, self._last_updated_keyword, self._last_updated_units, @@ -278,7 +282,7 @@ def Refresh(self): def SensorDescription(self, PropertyName): logger.debug("HTMLObservingConditions.SensorDescription({PropertyName}) called") - return + return eval(f"self._{PropertyName.lower()}_keyword") def TimeSinceLastUpdate(self, PropertyName): logger.debug( @@ -288,12 +292,14 @@ def TimeSinceLastUpdate(self, PropertyName): lines = stream.readlines() for line in lines: - self._last_updated = _get_number_from_line( + last_updated = _get_number_from_line( line, self._last_updated_keyword, self._last_updated_units, self._last_updated_numeric, ) + if last_updated is not None: + self._last_updated = last_updated return self.LastUpdated @@ -372,64 +378,6 @@ def WindSpeed(self): logger.debug("HTMLObservingConditions.WindSpeed property called") return self._wind_speed - def _get_number_from_line(self, line, expected_keyword, expected_units, is_numeric): - """ - Check to see if the provided line looks like a valid telemetry line - from the Winer webpage. A typical line looks like this: - - - If the line matches this format and the contents match expectations, return - the extracted value from the line. - - line: the line text to inspect - expected_keyword: the line must contain this keyword ('TEMPERATURE' in the example above) - expected_units: the line must contain these units after the value ('F' in the example above). - If this value is None, then units are not validated - is_numeric: if True, the value is validated and converted to a float before being returned. - if False, the string value is returned - - If the line does not match or there is a problem (e.g. converting the value to a float), - the function returns None. - - Otherwise, the function returns the value, either as a float (if requested) or as a string - """ - - line = line.strip() - if not line.startswith(b""): - return None - - line = line[4:-3] # Strip off beginning and ending comment characters - - fields = line.split( - b"=", 1 - ) # Split into at most two fields (keyword and value) - if len(fields) != 2: - return None - - line_keyword = fields[0].strip() - line_value_and_units = fields[1].strip() - - fields = line_value_and_units.split(b" ", 1) - line_value = fields[0].strip() - if len(fields) > 1: - line_units = fields[1] - else: - line_units = "" - - if line_keyword != expected_keyword: - return None - if expected_units is not None and line_units != expected_units: - return None - if is_numeric: - try: - return float(line_value) - except: - return None - else: - return line_value - @property def LastUpdated(self): logger.debug("HTMLObservingConditions.LastUpdated property called") diff --git a/pyscope/observatory/html_safety_monitor.py b/pyscope/observatory/html_safety_monitor.py index ee0c5ed6..3bad969a 100755 --- a/pyscope/observatory/html_safety_monitor.py +++ b/pyscope/observatory/html_safety_monitor.py @@ -1,8 +1,11 @@ -# import pycurl -# import io +import logging +import urllib.request +from ..utils import _get_number_from_line from .safety_monitor import SafetyMonitor +logger = logging.getLogger(__name__) + class HTMLSafetyMonitor(SafetyMonitor): def __init__(self, url, check_phrase=b"ROOFPOSITION=OPEN"): @@ -17,17 +20,27 @@ def __init__(self, url, check_phrase=b"ROOFPOSITION=OPEN"): @property def IsSafe(self): logger.debug(f"""HTMLSafetyMonitor.IsSafe property called""") - safe = False - c = None - # buffer = io.BytesIO() - # c = pycurl.Curl() - c.setopt(c.URL, self._url) - c.setopt(c.WRITEDATA, buffer) - c.perform() - c.close() - - body = buffer.getvalue() - if body.find(self._check_phrase) > -1: - safe = True + safe = None + + stream = urllib.request.urlopen(self._url) + lines = stream.readlines() + + try: + units = self._check_phrase.split(b" ")[1] + except IndexError: + units = "" + + for line in lines: + s = _get_number_from_line( + line, + self._check_phrase.split(b"=")[0], + units, + False, + ) + if s == self._check_phrase.split(b"=")[1]: + safe = True + break + else: + safe = False return safe diff --git a/pyscope/observatory/ip_cover_calibrator.py b/pyscope/observatory/ip_cover_calibrator.py index 8e7bebf4..15cdc02b 100644 --- a/pyscope/observatory/ip_cover_calibrator.py +++ b/pyscope/observatory/ip_cover_calibrator.py @@ -38,7 +38,7 @@ def OpenCover(self): @property def Brightness(self): logger.debug("IPCoverCalibrator.Brightness called") - pass + return @property def CalibratorState(self): diff --git a/pyscope/observatory/observatory.py b/pyscope/observatory/observatory.py index 188e0ddd..0f9b2341 100644 --- a/pyscope/observatory/observatory.py +++ b/pyscope/observatory/observatory.py @@ -1,5 +1,6 @@ import configparser import importlib +import json import logging import shutil import sys @@ -15,15 +16,12 @@ from astropy.io import fits from astroquery.mpc import MPC -from .. import observatory -from ..utils import ( - _args_to_config, - _get_image_source_catalog, - _kwargs_to_config, - airmass, -) +from .. import __version__, observatory +from ..utils import _get_image_source_catalog, _kwargs_to_config, airmass from . import ObservatoryException +from .ascom_device import ASCOMDevice from .device import Device +from .wcs import WCS logger = logging.getLogger(__name__) @@ -49,10 +47,10 @@ def __init__(self, config_path=None, **kwargs): self._config["autofocus"] = {} self._config["wcs"] = {} - self._site_name = "pyScope Site" - self._instrument_name = "pyScope Instrument" + self._site_name = "pyscope Site" + self._instrument_name = "pyscope Instrument" self._instrument_description = ( - "pyScope is a pure-Python telescope control package." + "pyscope is a pure-Python telescope control package." ) self._latitude = None self._longitude = None @@ -61,64 +59,64 @@ def __init__(self, config_path=None, **kwargs): self._focal_length = None self._camera = None - self._camera_args = None + self._camera_driver = None self._camera_kwargs = None self._cooler_setpoint = None self._cooler_tolerance = None self._max_dimension = None self._cover_calibrator = None - self._cover_calibrator_args = None + self._cover_calibrator_driver = None self._cover_calibrator_kwargs = None self._cover_calibrator_alt = None self._cover_calibrator_az = None self._dome = None - self._dome_args = None + self._dome_driver = None self._dome_kwargs = None self._filter_wheel = None - self._filter_wheel_args = None + self._filter_wheel_driver = None self._filter_wheel_kwargs = None self._filters = None self._filter_focus_offsets = None self._focuser = None - self._focuser_args = None + self._focuser_driver = None self._focuser_kwargs = None - self._focuser_max_error = 10 self._observing_conditions = None - self._observing_conditions_args = None + self._observing_conditions_driver = None self._observing_conditions_kwargs = None self._rotator = None - self._rotator_args = None + self._rotator_driver = None self._rotator_kwargs = None self._rotator_reverse = False self._rotator_min_angle = None self._rotator_max_angle = None - self._safety_monitor = None - self._safety_monitor_args = None - self._safety_monitor_kwargs = None + self._safety_monitor = [] + self._safety_monitor_driver = [] + self._safety_monitor_kwargs = [] - self._switch = None - self._switch_args = None - self._switch_kwargs = None + self._switch = [] + self._switch_driver = [] + self._switch_kwargs = [] self._telescope = None - self._telescope_args = None + self._telescope_driver = None self._telescope_kwargs = None self._min_altitude = 10 self._settle_time = 5 self._autofocus = None - self._autofocus_args = None + self._autofocus_driver = None self._autofocus_kwargs = None - self._wcs = None - self._wcs_args = None - self._wcs_kwargs = None + + self._wcs = [] + self._wcs_driver = [] + self._wcs_kwargs = [] self._slew_rate = None self._instrument_reconfiguration_times = None @@ -136,26 +134,16 @@ def __init__(self, config_path=None, **kwargs): # Camera self._camera_driver = self._config["camera"]["camera_driver"] - self._camera_ascom = self._config["camera"]["camera_ascom"] - self._camera_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "camera", "camera_args", fallback="" - ).split() - ) - if self._config.get("camera", "camera_args", fallback=None) - not in (None, "") - else None - ) self._camera_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "camera", "camera_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get("camera", "camera_kwargs", fallback=None) @@ -164,45 +152,27 @@ def __init__(self, config_path=None, **kwargs): ) if self.camera_driver.lower() in ("maxim", "maximdl"): logger.info("Using MaxIm DL as the camera driver") - self._maxim = _import_driver("Driver", driver_name="Maxim", ascom=False) + self._maxim = _import_driver("Maxim") self._camera = self._maxim.camera else: self._camera = _import_driver( - "Camera", - driver_name=self.camera_driver, - ascom=self.camera_ascom, - args=self._camera_args, - kwargs=self._camera_kwargs, + self.camera_driver, kwargs=self.camera_kwargs ) # Cover calibrator self._cover_calibrator_driver = self._config.get( "cover_calibrator", "cover_calibrator_driver", fallback=None ) - self._cover_calibrator_ascom = self._config.get( - "cover_calibrator", "cover_calibrator_ascom", fallback=None - ) - self._cover_calibrator_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "cover_calibrator", "cover_calibrator_args", fallback="" - ).split() - ) - if self._config.get( - "cover_calibrator", "cover_calibrator_args", fallback=None - ) - not in (None, "") - else None - ) self._cover_calibrator_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "cover_calibrator", "cover_calibrator_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get( @@ -212,73 +182,42 @@ def __init__(self, config_path=None, **kwargs): else None ) self._cover_calibrator = _import_driver( - "CoverCalibrator", - driver_name=self.cover_calibrator_driver, - ascom=self.cover_calibrator_ascom, - args=self._cover_calibrator_args, - kwargs=self._cover_calibrator_kwargs, + self.cover_calibrator_driver, + kwargs=self.cover_calibrator_kwargs, ) # Dome self._dome_driver = self._config.get("dome", "dome_driver", fallback=None) - self._dome_ascom = self._config.get("dome", "dome_ascom", fallback=None) - self._dome_args = ( - iter( - literal_eval(v) - for v in self._config.get("dome", "dome_args", fallback="").split() - ) - if self._config.get("dome", "dome_args", fallback=None) - not in (None, "") - else None - ) self._dome_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") - for pair in self._config.get( - "dome", "dome_kwargs", fallback="" - ).split() + pair.split("=") + for pair in self._config.get("dome", "dome_kwargs", fallback="") + .replace(" ", "") + .split(",") ) ) if self._config.get("dome", "dome_kwargs", fallback=None) not in (None, "") else None ) - self._dome = _import_driver( - "Dome", - driver_name=self.dome_driver, - ascom=self.dome_ascom, - args=self._dome_args, - kwargs=self._dome_kwargs, - ) + self._dome = _import_driver(self.dome_driver, kwargs=self.dome_kwargs) # Filter wheel self._filter_wheel_driver = self._config.get( "filter_wheel", "filter_wheel_driver", fallback=None ) - self._filter_wheel_ascom = self._config.get( - "filter_wheel", "filter_wheel_ascom", fallback=None - ) - self._filter_wheel_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "filter_wheel", "filter_wheel_args", fallback="" - ).split() - ) - if self._config.get("filter_wheel", "filter_wheel_args", fallback=None) - not in (None, "") - else None - ) self._filter_wheel_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "filter_wheel", "filter_wheel_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get( @@ -296,39 +235,24 @@ def __init__(self, config_path=None, **kwargs): self._filter_wheel = self._maxim.filter_wheel else: self._filter_wheel = _import_driver( - "FilterWheel", - driver_name=self.filter_wheel_driver, - ascom=self.filter_wheel_ascom, - args=self._filter_wheel_args, - kwargs=self._filter_wheel_kwargs, + self.filter_wheel_driver, + kwargs=self.filter_wheel_kwargs, ) # Focuser self._focuser_driver = self._config.get( "focuser", "focuser_driver", fallback=None ) - self._focuser_ascom = self._config.get( - "focuser", "focuser_ascom", fallback=None - ) - self._focuser_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "focuser", "focuser_args", fallback="" - ).split() - ) - if self._config.get("focuser", "focuser_args", fallback=None) - not in (None, "") - else None - ) self._focuser_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "focuser", "focuser_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get("focuser", "focuser_kwargs", fallback=None) @@ -336,42 +260,26 @@ def __init__(self, config_path=None, **kwargs): else None ) self._focuser = _import_driver( - "Focuser", - driver_name=self.focuser_driver, - ascom=self.focuser_ascom, - kwargs=self._focuser_kwargs, + self.focuser_driver, + kwargs=self.focuser_kwargs, ) # Observing conditions self._observing_conditions_driver = self._config.get( "observing_conditions", "observing_conditions_driver", fallback=None ) - self._observing_conditions_ascom = self._config.get( - "observing_conditions", "observing_conditions_ascom", fallback=None - ) - self._observing_conditions_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "observing_conditions", "observing_conditions_args", fallback="" - ).split() - ) - if self._config.get( - "observing_conditions", "observing_conditions_args", fallback=None - ) - not in (None, "") - else None - ) self._observing_conditions_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "observing_conditions", "observing_conditions_kwargs", fallback="", - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get( @@ -381,39 +289,24 @@ def __init__(self, config_path=None, **kwargs): else None ) self._observing_conditions = _import_driver( - "ObservingConditions", - driver_name=self.observing_conditions_driver, - ascom=self.observing_conditions_ascom, - args=self._observing_conditions_args, - kwargs=self._observing_conditions_kwargs, + self.observing_conditions_driver, + kwargs=self.observing_conditions_kwargs, ) # Rotator self._rotator_driver = self._config.get( "rotator", "rotator_driver", fallback=None ) - self._rotator_ascom = self._config.get( - "rotator", "rotator_ascom", fallback=None - ) - self._rotator_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "rotator", "rotator_args", fallback="" - ).split() - ) - if self._config.get("rotator", "rotator_args", fallback=None) - not in (None, "") - else None - ) self._rotator_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "rotator", "rotator_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get("rotator", "rotator_kwargs", fallback=None) @@ -421,39 +314,31 @@ def __init__(self, config_path=None, **kwargs): else None ) self._rotator = _import_driver( - "Rotator", - driver_name=self.rotator_driver, - ascom=self.rotator_ascom, - args=self._rotator_args, - kwargs=self._rotator_kwargs, + self.rotator_driver, + kwargs=self.rotator_kwargs, ) # Safety monitor for val in self._config["safety_monitor"].values(): + if val == "": + continue try: - driver, ascom, ar, kw = val.split(",") - self._safety_monitor_driver.append(driver) - self._safety_monitor_ascom.append(ascom) - self._safety_monitor_args.append( - iter(literal_eval(v) for v in ar.split()) - if ar not in (None, "") - else None - ) - self._safety_monitor_kwargs.append( - dict( - (k, literal_eval(v)) - for k, v in (pair.split(":") for pair in kw.split()) + split_val = val.replace(" ", "").split(",") + self._safety_monitor_driver.append(split_val[0]) + if len(split_val) > 1: + kw = split_val[1:] + self._safety_monitor_kwargs.append( + dict( + (k, literal_eval(v)) + for k, v in (pair.split("=") for pair in kw) + ) ) - if kw not in (None, "") - else None - ) + else: + self._safety_monitor_kwargs.append(None) self._safety_monitor.append( _import_driver( - "SafetyMonitor", - driver_name=driver, - ascom=ascom, - args=self._safety_monitor_args[-1], - kwargs=self._safety_monitor_kwargs[-1], + self._safety_monitor_driver[-1], + kwargs=self.safety_monitor_kwargs[-1], ) ) except: @@ -461,30 +346,25 @@ def __init__(self, config_path=None, **kwargs): # Switch for val in self._config["switch"].values(): + if val == "": + continue try: - driver, ascom, kw = val.split(",") - self._switch_driver.append(driver) - self._switch_ascom.append(ascom) - self._switch_args.append( - iter(literal_eval(v) for v in ar.split()) - if ar not in (None, "") - else None - ) - self._switch_kwargs.append( - dict( - (k, literal_eval(v)) - for k, v in (pair.split(":") for pair in kw.split()) + split_val = val.replace(" ", "").split(",") + self._switch_driver.append(split_val[0]) + if len(split_val) > 1: + kw = split_val[1:] + self._switch_kwargs.append( + dict( + (k, literal_eval(v)) + for k, v in (pair.split("=") for pair in kw) + ) ) - if kw not in (None, "") - else None - ) + else: + self._switch_kwargs.append(None) self._switch.append( _import_driver( - "Switch", - driver_name=driver, - ascom=ascom, - args=self._switch_args[-1], - kwargs=self._switch_kwargs[-1], + self._switch_driver[-1], + kwargs=self.switch_kwargs[-1], ) ) except: @@ -492,26 +372,16 @@ def __init__(self, config_path=None, **kwargs): # Telescope self._telescope_driver = self._config["telescope"]["telescope_driver"] - self._telescope_ascom = self._config["telescope"]["telescope_ascom"] - self._telescope_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "telescope", "telescope_args", fallback="" - ).split() - ) - if self._config.get("telescope", "telescope_args", fallback=None) - not in (None, "") - else None - ) self._telescope_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "telescope", "telescope_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get("telescope", "telescope_kwargs", fallback=None) @@ -519,89 +389,74 @@ def __init__(self, config_path=None, **kwargs): else None ) self._telescope = _import_driver( - "Telescope", - driver_name=self.telescope_driver, - ascom=self.telescope_ascom, - args=self._telescope_args, - kwargs=self._telescope_kwargs, + self.telescope_driver, + kwargs=self.telescope_kwargs, ) # Autofocus self._autofocus_driver = self._config.get( "autofocus", "autofocus_driver", fallback=None ) - self._autofocus_args = ( - iter( - literal_eval(v) - for v in self._config.get( - "autofocus", "autofocus_args", fallback="" - ).split() - ) - if self._config.get("autofocus", "autofocus_args", fallback=None) - not in (None, "") - else None - ) self._autofocus_kwargs = ( dict( (k, literal_eval(v)) for k, v in ( - pair.split(":") + pair.split("=") for pair in self._config.get( "autofocus", "autofocus_kwargs", fallback="" - ).split() + ) + .replace(" ", "") + .split(",") ) ) if self._config.get("autofocus", "autofocus_kwargs", fallback=None) not in (None, "") else None ) - if self.autofocus_driver in ("maxim", "maximdl"): + if self.autofocus_driver.lower() in ("maxim", "maximdl"): if self._maxim is None: raise ObservatoryException( "MaxIm DL must be used as the camera driver when using MaxIm DL as the autofocus driver." ) self._autofocus = self._maxim.autofocus logger.info("Using MaxIm DL as the autofocus driver") - self._autofocus = _import_driver( - "Autofocus", - driver_name=self.autofocus_driver, - args=self._autofocus_args, - kwargs=self._autofocus_kwargs, - ) + else: + self._autofocus = _import_driver( + self.autofocus_driver, + kwargs=self.autofocus_kwargs, + ) # WCS - for val in self._config["WCS"].values(): + for val in self._config["wcs"].values(): + if val == "": + continue try: - driver, ar, kw = val.split(",") - self._wcs_driver.append(val) - self._wcs_args.append( - iter(literal_eval(v) for v in ar.split()) - if ar not in (None, "") - else None - ) - self._wcs_kwargs.append( - dict( - (k, literal_eval(v)) - for k, v in (pair.split(":") for pair in kw.split()) + split_val = val.replace(" ", "").split(",") + self._wcs_driver.append(split_val[0]) + if len(split_val) > 1: + kw = split_val[1:] + self._wcs_kwargs.append( + dict( + (k, literal_eval(v)) + for k, v in (pair.split("=") for pair in kw) + ) ) - if kw not in (None, "") - else None - ) - if self._wcs_driver in ("maxim", "maximdl"): + else: + self._wcs_kwargs.append(None) + if self._wcs_driver[-1].lower() in ("maxim", "maximdl"): if self._maxim is None: raise ObservatoryException( "MaxIm DL must be used as the camera driver when using MaxIm DL as the WCS driver." ) self._wcs.append(self._maxim.wcs) logger.info("Using MaxIm DL as the WCS driver") - self._wcs.append( - _import_driver( - "WCS", - driver_name=val, - args=self._wcs_args[-1], - kwargs=self._wcs_kwargs[-1], + else: + self._wcs.append( + _import_driver( + self.wcs_driver[-1], + kwargs=self.wcs_kwargs[-1], + ) ) - ) except: logger.warning("Error parsing WCS config: %s" % val) @@ -632,113 +487,60 @@ def __init__(self, config_path=None, **kwargs): # Camera self._camera = kwargs.get("camera", self._camera) _check_class_inheritance(type(self._camera), "Camera") - self._camera_driver = self._camera.Name - self._camera_ascom = ascom.Device in type(self._camera).__bases__ - self._camera_args = kwargs.get("camera_args", self._camera_args) + self._camera_driver = self._camera.__class__.__name__ self._camera_kwargs = kwargs.get("camera_kwargs", self._camera_kwargs) self._config["camera"]["camera_driver"] = self._camera_driver - self._config["camera"]["camera_ascom"] = str(self._camera_ascom) - self._config["camera"]["camera_args"] = self._args_to_config(self._camera_args) - self._config["camera"]["camera_kwargs"] = self._kwargs_to_config( - self._camera_kwargs - ) + self._config["camera"]["camera_kwargs"] = _kwargs_to_config(self._camera_kwargs) # Cover calibrator self._cover_calibrator = kwargs.get("cover_calibrator", self._cover_calibrator) - if self._cover_calibrator is None: - self._cover_calibrator = _CoverCalibrator(self) - _check_class_inheritance(type(self._cover_calibrator), "CoverCalibrator") - self._cover_calibrator_driver = ( - self._cover_calibrator.Name if self._cover_calibrator is not None else "" - ) - self._cover_calibrator_ascom = ( - (ascom.Device in type(self._cover_calibrator).__bases__) - if self._cover_calibrator is not None - else False - ) - self._cover_calibrator_args = kwargs.get( - "cover_calibrator_args", self._cover_calibrator_args - ) - self._cover_calibrator_kwargs = kwargs.get( - "cover_calibrator_kwargs", self._cover_calibrator_kwargs - ) - self._config["cover_calibrator"][ - "cover_calibrator_driver" - ] = self._cover_calibrator_driver - self._config["cover_calibrator"][ - "cover_calibrator_ascom" - ] = self._cover_calibrator_ascom - self._config["cover_calibrator"][ - "cover_calibrator_args" - ] = self._args_to_config(self._cover_calibrator_args) - self._config["cover_calibrator"][ - "cover_calibrator_kwargs" - ] = self._kwargs_to_config(self._cover_calibrator_kwargs) + if self._cover_calibrator is not None: + _check_class_inheritance(type(self._cover_calibrator), "CoverCalibrator") + self._cover_calibrator_driver = self._cover_calibrator.__class__.__name__ + self._cover_calibrator_kwargs = kwargs.get( + "cover_calibrator_kwargs", self._cover_calibrator_kwargs + ) + self._config["cover_calibrator"][ + "cover_calibrator_driver" + ] = self._cover_calibrator_driver + self._config["cover_calibrator"][ + "cover_calibrator_kwargs" + ] = _kwargs_to_config(self._cover_calibrator_kwargs) # Dome self._dome = kwargs.get("dome", self._dome) if self._dome is not None: _check_class_inheritance(type(self._dome), "Dome") - self._dome_driver = self._dome.Name if self._dome is not None else "" - self._dome_ascom = ( - (ascom.Device in type(self._dome).__bases__) - if self._dome is not None - else False - ) - self._dome_args = kwargs.get("dome_args", self._dome_args) - self._dome_kwargs = kwargs.get("dome_kwargs", self._dome_kwargs) - self._config["dome"]["dome_driver"] = str(self._dome_driver) - self._config["dome"]["dome_ascom"] = str(self._dome_ascom) - self._config["dome"]["dome_args"] = self._args_to_config(self._dome_args) - self._config["dome"]["dome_kwargs"] = self._kwargs_to_config(self._dome_kwargs) + self._dome_driver = self._dome.__class__.__name__ + self._dome_kwargs = kwargs.get("dome_kwargs", self._dome_kwargs) + self._config["dome"]["dome_driver"] = self._dome_driver + self._config["dome"]["dome_kwargs"] = _kwargs_to_config(self._dome_kwargs) # Filter wheel self._filter_wheel = kwargs.get("filter_wheel", self._filter_wheel) if self._filter_wheel is not None: _check_class_inheritance(type(self._filter_wheel), "FilterWheel") - self._filter_wheel_driver = ( - self._filter_wheel.Name if self._filter_wheel is not None else "" - ) - self._filter_wheel_ascom = ( - (ascom.Device in type(self._filter_wheel).__bases__) - if self._filter_wheel is not None - else False - ) - self._filter_wheel_args = kwargs.get( - "filter_wheel_args", self._filter_wheel_args - ) - self._filter_wheel_kwargs = kwargs.get( - "filter_wheel_kwargs", self._filter_wheel_kwargs - ) - self._config["filter_wheel"]["filter_wheel_driver"] = self._filter_wheel_driver - self._config["filter_wheel"]["filter_wheel_ascom"] = self._filter_wheel_ascom - self._config["filter_wheel"]["filter_wheel_args"] = self._args_to_config( - self._filter_wheel_args - ) - self._config["filter_wheel"]["filter_wheel_kwargs"] = self._kwargs_to_config( - self._filter_wheel_kwargs - ) + self._filter_wheel_driver = self._filter_wheel.__class__.__name__ + self._filter_wheel_kwargs = kwargs.get( + "filter_wheel_kwargs", self._filter_wheel_kwargs + ) + self._config["filter_wheel"][ + "filter_wheel_driver" + ] = self._filter_wheel_driver + self._config["filter_wheel"]["filter_wheel_kwargs"] = _kwargs_to_config( + self._filter_wheel_kwargs + ) # Focuser self._focuser = kwargs.get("focuser", self._focuser) if self._focuser is not None: _check_class_inheritance(type(self._focuser), "Focuser") - self._focuser_driver = self._focuser.Name if self._focuser is not None else "" - self._focuser_ascom = ( - (ascom.Device in type(self._focuser).__bases__) - if self._focuser is not None - else False - ) - self._focuser_args = kwargs.get("focuser_args", self._focuser_args) - self._focuser_kwargs = kwargs.get("focuser_kwargs", self._focuser_kwargs) - self._config["focuser"]["focuser_driver"] = self._focuser_driver - self._config["focuser"]["focuser_ascom"] = self._focuser_ascom - self._config["focuser"]["focuser_args"] = self._args_to_config( - self._focuser_args - ) - self._config["focuser"]["focuser_kwargs"] = self._kwargs_to_config( - self._focuser_kwargs - ) + self._focuser_driver = self._focuser.__class__.__name__ + self._focuser_kwargs = kwargs.get("focuser_kwargs", self._focuser_kwargs) + self._config["focuser"]["focuser_driver"] = self._focuser_driver + self._config["focuser"]["focuser_kwargs"] = _kwargs_to_config( + self._focuser_kwargs + ) # Observing conditions self._observing_conditions = kwargs.get( @@ -748,55 +550,29 @@ def __init__(self, config_path=None, **kwargs): _check_class_inheritance( type(self._observing_conditions), "ObservingConditions" ) - self._observing_conditions_driver = ( - self._observing_conditions.Name - if self._observing_conditions is not None - else "" - ) - self._observing_conditions_ascom = ( - (ascom.Device in type(self._observing_conditions).__bases__) - if self._observing_conditions is not None - else False - ) - self._observing_conditions_args = kwargs.get( - "observing_conditions_args", self._observing_conditions_args - ) - self._observing_conditions_kwargs = kwargs.get( - "observing_conditions_kwargs", self._observing_conditions_kwargs - ) - self._config["observing_conditions"][ - "observing_conditions_driver" - ] = self._observing_conditions_driver - self._config["observing_conditions"][ - "observing_conditions_ascom" - ] = self._observing_conditions_ascom - self._config["observing_conditions"][ - "observing_conditions_args" - ] = self._args_to_config(self._observing_conditions_args) - self._config["observing_conditions"][ - "observing_conditions_kwargs" - ] = self._kwargs_to_config(self._observing_conditions_kwargs) + self._observing_conditions_driver = ( + self._observing_conditions.__class__.__name__ + ) + self._observing_conditions_kwargs = kwargs.get( + "observing_conditions_kwargs", self._observing_conditions_kwargs + ) + self._config["observing_conditions"][ + "observing_conditions_driver" + ] = self._observing_conditions_driver + self._config["observing_conditions"][ + "observing_conditions_kwargs" + ] = _kwargs_to_config(self._observing_conditions_kwargs) # Rotator self._rotator = kwargs.get("rotator", self._rotator) if self._rotator is not None: _check_class_inheritance(type(self._rotator), "Rotator") - self._rotator_driver = self._rotator.Name if self._rotator is not None else "" - self._rotator_ascom = ( - (ascom.Device in type(self._rotator).__bases__) - if self._rotator is not None - else False - ) - self._rotator_args = kwargs.get("rotator_args", self._rotator_args) - self._rotator_kwargs = kwargs.get("rotator_kwargs", self._rotator_kwargs) - self._config["rotator"]["rotator_driver"] = self._rotator_driver - self._config["rotator"]["rotator_ascom"] = self._rotator_ascom - self._config["rotator"]["rotator_args"] = self._args_to_config( - self._rotator_args - ) - self._config["rotator"]["rotator_kwargs"] = self._kwargs_to_config( - self._rotator_kwargs - ) + self._rotator_driver = self._rotator.__class__.__name__ + self._rotator_kwargs = kwargs.get("rotator_kwargs", self._rotator_kwargs) + self._config["rotator"]["rotator_driver"] = self._rotator_driver + self._config["rotator"]["rotator_kwargs"] = _kwargs_to_config( + self._rotator_kwargs + ) # Safety monitor kwarg = kwargs.get("safety_monitor", self._safety_monitor) @@ -804,73 +580,36 @@ def __init__(self, config_path=None, **kwargs): self._safety_monitor = kwarg if self._safety_monitor is not None: _check_class_inheritance(type(self._safety_monitor), "SafetyMonitor") - self._safety_monitor_driver = ( - self._safety_monitor.Name if self._safety_monitor is not None else "" - ) - self._safety_monitor_ascom = ( - (ascom.Device in type(self._safety_monitor).__bases__) - if self._safety_monitor is not None - else False - ) - self._safety_monitor_args = kwargs.get( - "safety_monitor_args", self._safety_monitor_args - ) - self._safety_monitor_kwargs = kwargs.get( - "safety_monitor_kwargs", self._safety_monitor_kwargs - ) - self._config["safety_monitor"]["driver_0"] = ( - ( + self._safety_monitor_driver = self._safety_monitor.__class__.__name__ + self._safety_monitor_kwargs = kwargs.get( + "safety_monitor_kwargs", self._safety_monitor_kwargs + ) + self._config["safety_monitor"]["driver_0"] = ( self._safety_monitor_driver + "," - + str(self._safety_monitor_ascom) - + "," - + self._args_to_config(self._safety_monitor_args) - + "," - + self._kwargs_to_config(self._safety_monitor_kwargs) + + _kwargs_to_config(self._safety_monitor_kwargs) ) - if self._safety_monitor_driver != "" - else "" - ) else: self._safety_monitor = kwarg - self._safety_monitor_driver = [None] * len(self._safety_monitor) - self._safety_monitor_ascom = [None] * len(self._safety_monitor) - self._safety_monitor_args = [None] * len(self._safety_monitor) - self._safety_monitor_kwargs = [None] * len(self._safety_monitor) for i, safety_monitor in enumerate(self._safety_monitor): if safety_monitor is not None: _check_class_inheritance(type(safety_monitor), "SafetyMonitor") - self._safety_monitor_driver[i] = ( - safety_monitor.Name if safety_monitor is not None else "" - ) - self._safety_monitor_ascom[i] = ( - (ascom.Device in type(safety_monitor).__bases__) - if safety_monitor is not None - else False - ) - self._safety_monitor_args[i] = ( - kwargs.get("safety_monitor_args", None)[i] - if kwargs.get("safety_monitor_args", None) is not None - else None - ) - self._safety_monitor_kwargs[i] = ( - kwargs.get("safety_monitor_kwargs", None)[i] - if kwargs.get("safety_monitor_kwargs", None) is not None - else None - ) - self._config["safety_monitor"]["driver_%i" % i] = ( - ( + self._safety_monitor_driver[i] = safety_monitor.__class__.__name__ + self._safety_monitor_kwargs[i] = ( + kwargs.get( + "safety_monitor_kwargs", self._safety_monitor_kwargs + )[i] + if kwargs.get( + "safety_monitor_kwargs", self._safety_monitor_kwargs[i] + ) + is not None + else None + ) + self._config["safety_monitor"]["driver_%i" % i] = ( self._safety_monitor_driver[i] + "," - + str(self._safety_monitor_ascom[i]) - + "," - + self._args_to_config(self._safety_monitor_args[i]) - + "," - + self._kwargs_to_config(self._safety_monitor_kwargs[i]) + + _kwargs_to_config(self._safety_monitor_kwargs[i]) ) - if self._safety_monitor_driver[i] != "" - else "" - ) # Switch kwarg = kwargs.get("switch", self._switch) @@ -878,79 +617,36 @@ def __init__(self, config_path=None, **kwargs): self._switch = kwarg if self._switch is not None: _check_class_inheritance(type(self._switch), "Switch") - self._switch_driver = self._switch.Name if self._switch is not None else "" - self._switch_ascom = ( - (ascom.Device in type(self._switch).__bases__) - if self._switch is not None - else False - ) - self._switch_args = kwargs.get("switch_args", self._switch_args) - self._switch_kwargs = kwargs.get("switch_kwargs", self._switch_kwargs) - self._config["switch"]["driver_0"] = ( - ( - self._switch_driver - + "," - + str(self._switch_ascom) - + "," - + self._args_to_config(self._switch_args) - + "," - + self._kwargs_to_config(self._switch_kwargs) + self._switch_driver = self._switch.__class__.__name__ + self._switch_kwargs = kwargs.get("switch_kwargs", self._switch_kwargs) + self._config["switch"]["driver_0"] = ( + self._switch_driver + "," + _kwargs_to_config(self._switch_kwargs) ) - if self._switch_driver != "" - else "" - ) else: self._switch = kwarg - self._switch_driver = [None] * len(self._switch) - self._switch_ascom = [None] * len(self._switch) - self._switch_args = [None] * len(self._switch) - self._switch_kwargs = [None] * len(self._switch) for i, switch in enumerate(self._switch): if switch is not None: - _check_class_inheritance(switch, "Switch") - self._switch_driver[i] = switch.Name if switch is not None else "" - self._switch_ascom[i] = ( - (ascom.Device in type(switch).__bases__) - if switch is not None - else False - ) - self._switch_args[i] = ( - kwargs.get("switch_args", None)[i] - if kwargs.get("switch_args", None) is not None - else None - ) - self._switch_kwargs[i] = ( - kwargs.get("switch_kwargs", None)[i] - if kwargs.get("switch_kwargs", None) is not None - else None - ) - self._config["switch"]["driver_%i" % i] = ( - ( + _check_class_inheritance(type(switch), "Switch") + self._switch_driver[i] = switch.__class__.__name__ + self._switch_kwargs[i] = ( + kwargs.get("switch_kwargs", self._switch_kwargs[i]) + if kwargs.get("switch_kwargs", self._switch_kwargs[i]) + is not None + else None + ) + self._config["switch"]["driver_%i" % i] = ( self._switch_driver[i] + "," - + str(self._switch_ascom[i]) - + "," - + self._args_to_config(self._switch_args[i]) - + "," - + self._kwargs_to_config(self._switch_kwargs[i]) + + _kwargs_to_config(self._switch_kwargs[i]) ) - if self._switch_driver[i] != "" - else "" - ) # Telescope self._telescope = kwargs.get("telescope", self._telescope) _check_class_inheritance(type(self._telescope), "Telescope") - self._telescope_driver = self._telescope.Name - self._telescope_ascom = ascom.Device in type(self._telescope).__bases__ - self._telescope_args = kwargs.get("telescope_args", self._telescope_args) + self._telescope_driver = self._telescope.__class__.__name__ self._telescope_kwargs = kwargs.get("telescope_kwargs", self._telescope_kwargs) self._config["telescope"]["telescope_driver"] = self._telescope_driver - self._config["telescope"]["telescope_ascom"] = str(self._telescope_ascom) - self._config["telescope"]["telescope_args"] = self._args_to_config( - self._telescope_args - ) - self._config["telescope"]["telescope_kwargs"] = self._kwargs_to_config( + self._config["telescope"]["telescope_kwargs"] = _kwargs_to_config( self._telescope_kwargs ) @@ -958,72 +654,42 @@ def __init__(self, config_path=None, **kwargs): self._autofocus = kwargs.get("autofocus", self._autofocus) if self._autofocus is not None: _check_class_inheritance(type(self._autofocus), "Autofocus") - self._autofocus_driver = ( - self._autofocus.Name if self._autofocus is not None else "" - ) - self._autofocus_args = kwargs.get("autofocus_args", self._autofocus_args) - self._autofocus_kwargs = kwargs.get("autofocus_kwargs", self._autofocus_kwargs) - self._config["autofocus"]["autofocus_driver"] = self._autofocus_driver - self._config["autofocus"]["autofocus_args"] = self._args_to_config( - self._autofocus_args - ) - self._config["autofocus"]["autofocus_kwargs"] = self._kwargs_to_config( - self._autofocus_kwargs - ) + self._autofocus_driver = self._autofocus.__class__.__name__ + self._autofocus_kwargs = kwargs.get( + "autofocus_kwargs", self._autofocus_kwargs + ) + self._config["autofocus"]["autofocus_driver"] = self._autofocus_driver + self._config["autofocus"]["autofocus_kwargs"] = _kwargs_to_config( + self._autofocus_kwargs + ) # WCS kwarg = kwargs.get("wcs", self._wcs) if kwarg is None: - self._wcs = _import_driver("WCS", "wcs_astrometrynet") - self._wcs_driver = "wcs_astrometrynet" - self._config["wcs"]["driver_0"] = self._wcs_driver - elif type(kwarg) not in (iter, list, tuple): + self._wcs = _import_driver("WCSAstrometryNet") + + if type(kwarg) not in (iter, list, tuple): self._wcs = kwarg - _check_class_inheritance(type(self._wcs), "WCS") - self._wcs_driver = self._wcs.__name__ if self._wcs is not None else "" - self._wcs_args = kwargs.get("wcs_args", self._wcs_args) - self._wcs_kwargs = kwargs.get("wcs_kwargs", self._wcs_kwargs) - self._config["wcs"]["driver_0"] = ( - ( - self._wcs_driver - + "," - + self._args_to_config(self._wcs_args) - + "," - + self._kwargs_to_config(self._wcs_kwargs) + if self._wcs is not None: + _check_class_inheritance(type(self._wcs), "WCS") + self._wcs_driver = self._wcs.__class__.__name__ + self._wcs_kwargs = kwargs.get("wcs_kwargs", self._wcs_kwargs) + self._config["wcs"]["driver_0"] = ( + self._wcs_driver + "," + _kwargs_to_config(self._wcs_kwargs) ) - if self._wcs_driver != "" - else "" - ) else: self._wcs = kwarg self._wcs_driver = [None] * len(self._wcs) - self._wcs_args = [None] * len(self._wcs) self._wcs_kwargs = [None] * len(self._wcs) for i, wcs in enumerate(self._wcs): if wcs is not None: - _check_class_inheritance(wcs, "WCS") - self._wcs_driver[i] = wcs.__name__ if wcs is not None else "" - self._wcs_args[i] = ( - kwargs.get("wcs_args", None)[i] - if kwargs.get("wcs_args", None) is not None - else None - ) - self._wcs_kwargs[i] = ( - kwargs.get("wcs_kwargs", None)[i] - if kwargs.get("wcs_kwargs", None) is not None - else None - ) - self._config["wcs"]["driver_%i" % i] = ( - ( - self._wcs_driver[i] - + "," - + self._args_to_config(self._wcs_args[i]) - + "," - + self._kwargs_to_config(self._wcs_kwargs[i]) + _check_class_inheritance(type(wcs), "WCS") + self._wcs_driver[i] = wcs.__class__.__name__ + self._wcs_kwargs[i] = ( + kwargs.get("wcs_kwargs", None)[i] + if kwargs.get("wcs_kwargs", None) is not None + else None ) - if self._wcs_driver[i] != "" - else "" - ) logger.debug("Reading out keywords passed as kwargs") logger.debug("kwargs: %s" % kwargs) @@ -1034,7 +700,7 @@ def __init__(self, config_path=None, **kwargs): self._last_camera_shutter_status = None self.camera.OriginalStartExposure = self.camera.StartExposure - def NewStartExposure(self, Duration, Light): + def NewStartExposure(Duration, Light): self._last_camera_shutter_status = Light self.camera.OriginalStartExposure(Duration, Light) @@ -1049,8 +715,8 @@ def NewStartExposure(self, Duration, Light): self._safety_monitor_thread = None self._safety_monitor_event = None - self.derotation_thread = None - self.derotation_event = None + self._derotation_thread = None + self._derotation_event = None logger.debug("Config:") logger.debug(self._config) @@ -1063,6 +729,8 @@ def connect_all(self): logger.info("Camera connected") else: logger.warning("Camera failed to connect") + if self.camera.CanSetCCDTemperature: + self.cooler_setpoint = self._cooler_setpoint if self.cover_calibrator is not None: self.cover_calibrator.Connected = True @@ -1269,13 +937,12 @@ def shutdown(self): except: logger.exception("Error aborting dome motion during shutdown") - if self.dome.CanFindPark: - logger.info("Attempting to park dome...") - try: - self.dome.Park() - logger.info("Dome parked") - except: - logger.exception("Error parking dome during shutdown") + logger.info("Attempting to park dome...") + try: + self.dome.Park() + logger.info("Dome parked") + except: + logger.exception("Error parking dome during shutdown") if self.dome.CanSetShutter: logger.info("Attempting to close dome shutter...") @@ -1340,7 +1007,7 @@ def lst(self, t=None): if t is None: t = self.observatory_time else: - t = Time(t) + t = astrotime.Time(t) return ( t.sidereal_time("apparent", self.observatory_location).to("hourangle").value ) @@ -1371,7 +1038,7 @@ def moon_altaz(self, t=None): else: t = Time(t) - moon = coord.get_moon(t).transform_to( + moon = coord.get_body("moon", t).transform_to( coord.AltAz(obstime=t, location=self.observatory_location) ) @@ -1385,16 +1052,16 @@ def moon_illumination(self, t=None): if t is None: t = self.observatory_time else: - t = Time(t) + t = astrotime.Time(t) sun = coord.get_sun(t) - moon = coord.get_moon(t) + moon = coord.get_body("moon", t) elongation = sun.separation(moon) phase_angle = np.arctan2( sun.distance * np.sin(elongation), moon.distance - sun.distance * np.cos(elongation), ) - return (1.0 + np.cos(phase_angle)) / 2.0 + return (1.0 + np.cos(phase_angle.value)) / 2.0 def get_object_altaz( self, obj=None, ra=None, dec=None, unit=("hr", "deg"), frame="icrs", t=None @@ -1408,7 +1075,7 @@ def get_object_altaz( obj = self._parse_obj_ra_dec(obj, ra, dec, unit, frame) if t is None: t = self.observatory_time - t = Time(t) + t = astrotime.Time(t) return obj.transform_to( coord.AltAz(obstime=t, location=self.observatory_location) @@ -1426,7 +1093,7 @@ def get_object_slew( obj = self._parse_obj_ra_dec(obj, ra, dec, unit, frame) if t is None: t = self.observatory_time - t = Time(t) + t = astrotime.Time(t) eq_system = self.telescope.EquatorialSystem if eq_system == 0: @@ -1459,7 +1126,9 @@ def get_current_object(self): obj = self._parse_obj_ra_dec( ra=self.telescope.RightAscension, dec=self.telescope.Declination, - frame=coord.TETE(obstime=t, location=self.observatory_location), + frame=coord.TETE( + obstime=self.observatory_time, location=self.observatory_location + ), ) elif eq_system == 2: obj = self._parse_obj_ra_dec( @@ -1501,9 +1170,8 @@ def save_last_image( if ( self.camera.ImageArray is None - or self.camera.ImageArray.size == 0 - or self.camera.ImageArray.shape[0] == 0 - or self.camera.ImageArray.shape[1] == 0 + or len(self.camera.ImageArray) == 0 + or len(self.camera.ImageArray[0]) == 0 ): logger.exception("Image array is empty, cannot be saved") return False @@ -1513,14 +1181,14 @@ def save_last_image( hdr["SIMPLE"] = True hdr["BITPIX"] = (16, "8 unsigned int, 16 & 32 int, -32 & -64 real") hdr["NAXIS"] = (2, "number of axes") - hdr["NAXIS1"] = (self.camera.ImageArray.shape[0], "fastest changing axis") + hdr["NAXIS1"] = (len(self.camera.ImageArray), "fastest changing axis") hdr["NAXIS2"] = ( - self.camera.ImageArray.shape[1], + len(self.camera.ImageArray[0]), "next to fastest changing axis", ) hdr["BSCALE"] = (1, "physical=BZERO + BSCALE*array_value") hdr["BZERO"] = (32768, "physical=BZERO + BSCALE*array_value") - hdr["SWCREATE"] = ("pyScope", "Software used to create file") + hdr["SWCREATE"] = ("pyscope", "Software used to create file") hdr["SWVERSIO"] = (__version__, "Version of software used to create file") hdr["ROWORDER"] = ("TOP-DOWN", "Row order of image") @@ -1549,37 +1217,37 @@ def save_last_image( if custom_header is not None: hdr.update(custom_header) + hdu = fits.PrimaryHDU(self.camera.ImageArray, header=hdr) + hdu.writeto(filename, overwrite=overwrite) + if do_fwhm: logger.info("Attempting to measure FWHM") cat = _get_image_source_catalog(filename) hdr["FWHMH"] = ( - np.median(np.sqrt(cat.covar_sigx2)), + np.median(np.sqrt(cat.covar_sigx2)).value, "Median FWHM in horizontal direction", ) hdr["FWHMHS"] = ( - np.std(np.sqrt(cat.covar_sigx2)), + np.std(np.sqrt(cat.covar_sigx2)).value, "Std. dev. of FWHM in horizontal direction", ) hdr["FWHMV"] = ( - np.median(np.sqrt(cat.covar_sigy2)), + np.median(np.sqrt(cat.covar_sigy2)).value, "Median FWHM in vertical direction", ) hdr["FWHMVS"] = ( - np.std(np.sqrt(cat.covar_sigy2)), + np.std(np.sqrt(cat.covar_sigy2)).value, "Std. dev. of FWHM in vertical direction", ) logger.info("FWHMH: %.2f +/- %.2f" % (hdr["FWHMH"], hdr["FWHMHS"])) logger.info("FWHMV: %.2f +/- %.2f" % (hdr["FWHMV"], hdr["FWHMVS"])) - hdu = fits.PrimaryHDU(self.camera.ImageArray, header=hdr) - hdu.writeto(filename, overwrite=overwrite) - if do_wcs: logger.info("Attempting to solve image for WCS") - if type(self.wcs) is WCS: + if type(self._wcs) is WCS: logger.info("Using solver %s" % self.wcs_driver) - self.wcs.Solve( + self._wcs.Solve( filename, ra_key="TELRAIC", dec_key="TELDECIC", @@ -1593,7 +1261,7 @@ def save_last_image( crpix_center=True, ) else: - for wcs, i in enumerate(self.wcs): + for i, wcs in enumerate(self._wcs): logger.info("Using solver %s" % self.wcs_driver[i]) solution = wcs.Solve( filename, @@ -1620,9 +1288,9 @@ def set_filter_offset_focuser(self, filter_index=None, filter_name=None): f"Observatory.set_filter_offset_focuser({filter_index}, {filter_name}) called" ) - if filter_index is None: + if filter_name is None: try: - filter_index = self.filters.index(filter_name) + filter_name = self.filters[filter_index] logger.info("Filter %s found at index %i" % (filter_name, filter_index)) except: raise ObservatoryException( @@ -1642,7 +1310,7 @@ def set_filter_offset_focuser(self, filter_index=None, filter_name=None): if self.focuser is not None: if self.focuser.Connected: self._current_focus_offset = ( - self.filter_focus_offsets[filter_index] - self.current_focus_offset + self.filter_focus_offsets[filter_name] - self.current_focus_offset ) if self.current_focus_offset < self.focuser.MaxIncrement: @@ -1657,8 +1325,10 @@ def set_filter_offset_focuser(self, filter_index=None, filter_name=None): % (self.focuser.Position + self.current_focus_offset) ) self.focuser.Move( - self.focuser.Position + self.current_focus_offset + int(self.focuser.Position + self.current_focus_offset) ) + while self.focuser.IsMoving: + time.sleep(0.1) logger.info("Focuser moved") return True else: @@ -1670,7 +1340,9 @@ def set_filter_offset_focuser(self, filter_index=None, filter_name=None): "Focuser moving to relative position %i" % self.current_focus_offset ) - self.focuser.Move(self.current_focus_offset) + self.focuser.Move(int(self.current_focus_offset)) + while self.focuser.IsMoving: + time.sleep(0.1) logger.info("Focuser moved") return True else: @@ -1704,12 +1376,11 @@ def slew_to_coordinates( obj = self._parse_obj_ra_dec(obj, ra, dec, unit, frame) logger.info( - "Slewing to RA %i:%i:%.2f and Dec %i:%i:%.2f" % obj.ra.hms[0], - obj.ra.hms[1], - obj.ra.hms[2], - obj.dec.dms[0], - obj.dec.dms[1], - obj.dec.dms[2], + "Slewing to RA %s and Dec %s" + % ( + obj.ra.hms, + obj.dec.dms, + ) ) slew_obj = self.get_object_slew(obj) altaz_obj = self.get_object_altaz(obj) @@ -1737,6 +1408,14 @@ def slew_to_coordinates( self.telescope.FindHome() logger.info("Found home position.") + if track and self.telescope.CanSetTracking: + logger.info("Turning on sidereal tracking...") + # self.telescope.TrackingRate = self.telescope.TrackingRates[0] # ! FIX THIS + self.telescope.Tracking = True + logger.info("Sidereal tracking is on.") + else: + logger.warning("Tracking cannot be turned on.") + logger.info("Attempting to slew to coordinates...") if self.telescope.CanSlew: if self.telescope.CanSlewAsync: @@ -1754,14 +1433,14 @@ def slew_to_coordinates( raise ObservatoryException("The telescope cannot slew to coordinates.") if control_dome and self.dome is not None: - if self.dome.ShutterState != 0 and self.CanSetShutter: - logger.info("Opening the dome shutter...") - self.dome.OpenShutter() - logger.info("Opened.") + if self.dome.ShutterStatus != 0 and self.dome.CanSetShutter: if self.dome.CanFindHome: logger.info("Finding the dome home...") self.dome.FindHome() logger.info("Found.") + logger.info("Opening the dome shutter...") + self.dome.OpenShutter() + logger.info("Opened.") if self.dome.CanPark: if self.dome.AtPark and self.dome.CanFindHome: logger.info("Finding the dome home...") @@ -1791,30 +1470,22 @@ def slew_to_coordinates( ) control_rotator = False - logger.info("Rotating the rotator to hour angle %.2f" % hour_angle) + logger.info("Rotating the rotator to hour angle %.2f" % rotation_angle) self.rotator.MoveAbsolute(rotation_angle) logger.info("Rotated.") condition = wait_for_slew while condition: condition = self.telescope.Slewing - if self.control_dome and self.dome is not None: + if control_dome and self.dome is not None: condition = condition or self.dome.Slewing - if self.control_rotator and self.rotator is not None: + if control_rotator and self.rotator is not None: condition = condition or self.rotator.IsMoving time.sleep(0.1) else: logger.info("Settling for %.2f seconds..." % self.settle_time) time.sleep(self.settle_time) - if track and self.telescope.CanSetTracking: - logger.info("Turning on sidereal tracking...") - self.telescope.TrackingRate = 0 - self.telescope.Tracking = True - logger.info("Sidereal tracking is on.") - else: - logger.warning("Tracking cannot be turned on.") - return True def start_observing_conditions_thread(self, update_interval=60): @@ -1918,21 +1589,23 @@ def _update_safety_monitor(self, on_fail, wait_time=0): return time.sleep(wait_time) - def start_derotation_thread(self, update_interval=0.05): + def start_derotation_thread(self, update_interval=0.1): """Begin a derotation thread for the current ra and dec""" if self.rotator is None: raise ObservatoryException("There is no rotator object.") obj = self.get_current_object().transform_to( - coord.AltAz(obstime=Time.now(), location=self.location) + coord.AltAz( + obstime=astrotime.Time.now(), location=self.observatory_location + ) ) logger.info("Starting derotation thread...") self._derotation_event = threading.Event() self._derotation_thread = threading.Thread( target=self._update_rotator, - args=(obj,), + args=(obj, update_interval), daemon=True, name="Derotation Thread", ) @@ -1957,10 +1630,18 @@ def stop_derotation_thread(self): return True - def _update_rotator(self, obj, wait_time=0): + def _update_rotator(self, obj, wait_time=0.1): """Updates the rotator""" # mean sidereal rate (at J2000) in radians per second SR = 7.292115855306589e-5 + deg = np.pi / 180 + + command = ( + -(np.cos(obj.az.rad) * np.cos(self.latitude.rad) / np.cos(obj.alt.rad)) + * SR + / deg + * wait_time + ) while not self._derotation_event.is_set(): self.rotator.Move(command) @@ -1974,11 +1655,7 @@ def _update_rotator(self, obj, wait_time=0): ) command = ( - -( - np.cos(obj.az.rad) - * np.cos(self.latitude * deg) - / np.cos(obj.alt.rad) - ) + -(np.cos(obj.az.rad) * np.cos(self.latitude.rad) / np.cos(obj.alt.rad)) * SR / deg * wait_time @@ -2032,7 +1709,14 @@ def run_autofocus( if not use_current_pointing: logger.info("Slewing to zenith...") self.slew_to_coordinates( - obj=coord.SkyCoord(alt=90 * u.deg, az=0 * u.deg, frame="altaz"), + obj=coord.SkyCoord( + alt=90 * u.deg, + az=0 * u.deg, + frame=coord.AltAz( + obstime=self.observatory_time, + location=self.observatory_location, + ), + ), control_dome=(self.dome is not None), control_rotator=(self.rotator is not None), ) @@ -2041,45 +1725,56 @@ def run_autofocus( logger.info("Starting autofocus routine...") test_positions = np.linspace( - midpoint - step_size * nsteps / 2, - midpoint + step_size * nsteps / 2, + midpoint - step_size * (nsteps // 2), + midpoint + step_size * (nsteps // 2), nsteps, + endpoint=True, ) test_positions = np.round(test_positions, -2) focus_values = [] - for position, i in enumerate(test_positions): + for i, position in enumerate(test_positions): logger.info("Moving focuser to %s..." % position) if self.focuser.Absolute: - self.focuser.Move(position) + self.focuser.Move(int(position)) + while self.focuser.IsMoving: + time.sleep(0.1) elif i == 0: - self.focuser.Move(-position[0]) + self.focuser.Move(-int(position[0])) + while self.focuser.IsMoving: + time.sleep(0.1) else: - self.focuser.Move(step_size) + self.focuser.Move(int(step_size)) + while self.focuser.IsMoving: + time.sleep(0.1) logger.info("Focuser moved.") logger.info("Taking %s second exposure..." % exposure) self.camera.StartExposure(exposure, True) - while not image.ImageReady: + while not self.camera.ImageReady: time.sleep(0.1) logger.info("Exposure complete.") logger.info("Calculating mean star fwhm...") - filename = tempfile.gettempdir() + "/autofocus.fts" - self.save_last_image(filename, overwrite=True, do_wcs=True) + filename = tempfile.gettempdir() + "autofocus.fts" + self.save_last_image(filename, overwrite=True, do_fwhm=True) cat = _get_image_source_catalog(filename) - focus_values.append(np.mean(cat.fwhm)) + focus_values.append(np.mean(cat.fwhm.value)) logger.info("FWHM = %.1f pixels" % focus_values[-1]) - popt, pcov = np.polyfit(test_positions, focus_values, 2) - result = np.round(-popt[1] / (2 * popt[0]), 0) + fit = np.polyfit(test_positions, focus_values, 2) + result = np.round(-fit[1] / (2 * fit[0]), 0) logger.info("Best focus position is %i" % result) logger.info("Moving focuser to best focus position...") if self.focuser.Absolute: - self.focuer.Move(result) + self.focuser.Move(int(result)) + while self.focuser.IsMoving: + time.sleep(0.1) else: - self.focuser.Move(test_positions[-1] - result) + self.focuser.Move(int(test_positions[-1] - result)) + while self.focuser.IsMoving: + time.sleep(0.1) logger.info("Focuser moved.") logger.info("Autofocus routine complete.") @@ -2234,8 +1929,8 @@ def recenter( self.save_last_image(temp_image) logger.info("Searching for a WCS solution...") - if type(self.wcs) is WCS: - self.wcs.Solve( + if type(self._wcs) is WCS: + self._wcs.Solve( filename, ra_key="TELRAIC", dec_key="TELDECIC", @@ -2249,7 +1944,7 @@ def recenter( crpix_center=True, ) else: - for wcs, i in enumerate(self.wcs): + for i, wcs in enumerate(self._wcs): solution_found = wcs.Solve( filename, ra_key="TELRAIC", @@ -2371,7 +2066,7 @@ def take_flats( filter_exposure, filter_brightness=None, readouts=None, - binnings=(1, 1), + binnings=["1x1"], repeat=10, save_path=None, new_folder=None, @@ -2418,7 +2113,13 @@ def take_flats( logger.info("Homing complete") logger.info("Slewing to point at cover calibrator") - if self.telescope.CanSlewAltAz: + if self.telescope.CanSetTracking: + self.telescope.Tracking = False + if self.telescope.CanSlewAltAzAsync: + self.telescope.SlewToAltAzAsync( + self.cover_calibrator_az, self.cover_calibrator_alt + ) + elif self.telescope.CanSlewAltAz: self.telescope.SlewToAltAz( self.cover_calibrator_az, self.cover_calibrator_alt ) @@ -2432,7 +2133,8 @@ def take_flats( ) ) self.telescope.SlewToCoordinates(obj.ra.hour, obj.dec.deg) - self.telescope.Tracking = False + while self.telescope.Slewing: + time.sleep(0.1) logger.info("Slew complete") if self.cover_calibrator.CoverState != "NotPresent": @@ -2446,12 +2148,8 @@ def take_flats( for readout in readouts: self.camera.ReadoutMode = readout for binning in binnings: - if type(binnings[0]) is tuple: - self.camera.BinX = binning[0] - self.camera.BinY = binning[1] - else: - self.camera.BinX = binning - self.camera.BinY = binning + self.camera.BinX = binning.split("x")[0] + self.camera.BinY = binning.split("x")[1] for j in range(repeat): if self.camera.CanSetCCDTemperature: while self.camera.CCDTemperature > ( @@ -2472,26 +2170,30 @@ def take_flats( ) self.cover_calibrator.CalibratorOn(filter_brightness[i]) logger.info("Cover calibrator on") + while self.filter_wheel.Position != i: + time.sleep(0.1) logger.info("Starting %s exposure" % self.filters[i]) - camera.StartExposure(filter_exposure[i], False) + self.camera.StartExposure(filter_exposure[i], False) save_string = save_path + ( - "flat_%s_%ix%i_%4.4f_%i_%i.fts" + "flat_%s_%ix%i_%ss_%s__%i.fts" % ( self.filters[i], self.camera.BinX, self.camera.BinY, - filter_exposure[i], + ("%4.4g" % filter_exposure[i]) + .replace(".", "-") + .replace(" ", ""), self.camera.ReadoutModes[ self.camera.ReadoutMode ].replace(" ", ""), j, ) ) - while not camera.ImageReady: + while not self.camera.ImageReady: time.sleep(0.1) self.save_last_image(save_string, frametyp="Flat") - logger.info("Flat %i of %i complete" % (j, repeat)) - logger.debug("Saved flat frame to %s" % save_string) + logger.info("Flat %i of %i complete" % (j + 1, repeat)) + logger.info("Saved flat frame to %s" % save_string) if self.cover_calibrator.CalibratorState != "NotPresent": logger.info("Turning off the cover calibrator") @@ -2546,12 +2248,8 @@ def take_darks( for readout in readouts: self.camera.ReadoutMode = readout for binning in binnings: - if type(binnings[0]) is tuple: - self.camera.BinX = binning[0] - self.camera.BinY = binning[1] - else: - self.camera.BinX = binning - self.camera.BinY = binning + self.camera.BinX = binning.split("x")[0] + self.camera.BinY = binning.split("x")[1] for j in range(repeat): if self.camera.CanSetCCDTemperature: while self.camera.CCDTemperature > ( @@ -2562,24 +2260,24 @@ def take_darks( ) time.sleep(10) logger.info("Starting %4.4gs dark exposure" % exposure) - camera.StartExposure(exposure, False) + self.camera.StartExposure(exposure, False) save_string = save_path + ( - "dark_%s_%ix%i_%4.4gs__%i.fts" + "dark_%ix%i_%ss_%s__%i.fts" % ( + self.camera.BinX, + self.camera.BinY, + ("%4.4g" % exposure).replace(".", "-").replace(" ", ""), self.camera.ReadoutModes[ self.camera.ReadoutMode ].replace(" ", ""), - self.camera.BinX, - self.camera.BinY, - exposure, j, ) ) - while not camera.ImageReady: + while not self.camera.ImageReady: time.sleep(0.1) self.save_last_image(save_string, frametyp="Dark") logger.info("Dark %i of %i complete" % (j, repeat)) - logger.debug("Saved dark frame to %s" % save_string) + logger.info("Saved dark frame to %s" % save_string) logger.info("Darks complete") @@ -2605,7 +2303,7 @@ def _parse_obj_ra_dec( if t is None: t = self.observatory_time else: - t = Time(t) + t = astrotime.Time(t) eph = MPC.get_ephemeris( obj, start=t, @@ -2652,7 +2350,7 @@ def _read_out_kwargs(self, dictionary): self.diameter = dictionary.get("diameter", self.diameter) self.focal_length = dictionary.get("focal_length", self.focal_length) - self.cooler_setpoint = dictionary.get("cooler_setpoint", self.cooler_setpoint) + self._cooler_setpoint = dictionary.get("cooler_setpoint", self.cooler_setpoint) self.cooler_tolerance = dictionary.get( "cooler_tolerance", self.cooler_tolerance ) @@ -2670,10 +2368,6 @@ def _read_out_kwargs(self, dictionary): "filter_focus_offsets", self.filter_focus_offsets ) - self.focuser_max_error = dictionary.get( - "focuser_max_error", self.focuser_max_error - ) - self.rotator_reverse = dictionary.get("rotator_reverse", self.rotator_reverse) self.rotator_min_angle = dictionary.get( "rotator_min_angle", self.rotator_min_angle @@ -2686,17 +2380,19 @@ def _read_out_kwargs(self, dictionary): self.settle_time = dictionary.get("settle_time", self.settle_time) self.slew_rate = dictionary.get("slew_rate", self.slew_rate) - self.instrument_reconfiguration_times = json.loads( - dictionary.get( - "instrument_reconfiguration_times", - self.instrument_reconfiguration_times, - ) + + t = dictionary.get( + "instrument_reconfiguration_times", + self.instrument_reconfiguration_times, + ) + self.instrument_reconfiguration_times = ( + json.loads(t) if t is not None and t != "" and t != {} else "{}" ) @property def autofocus_info(self): logger.debug("Observatory.autofocus_info() called") - return {"Autofocus Driver": self.autofocus_driver} + return {"AUTODRIV": self.autofocus_driver} @property def camera_info(self): @@ -2718,6 +2414,7 @@ def camera_info(self): "DATE-OBS": (None, "YYYY-MM-DDThh:mm:ss observation start [UT]"), "JD": (None, "Julian date"), "MJD": (None, "Modified Julian date"), + "MJD-OBS": (None, "Modified Julian date"), "EXPTIME": (None, "Exposure time [seconds]"), "EXPOSURE": (None, "Exposure time [seconds]"), "SUBEXP": (None, "Subexposure time [seconds]"), @@ -2737,7 +2434,7 @@ def camera_info(self): "BAYERPAT": (None, "Bayer color pattern"), "BAYOFFX": (None, "Bayer X offset"), "BAYOFFY": (None, "Bayer Y offset"), - "HSINKT": (self.camera.HeatSinkTemperature, "Heat sink temperature [C]"), + "HSINKT": (None, "Heat sink temperature [C]"), "COOLERON": (None, "Whether the cooler is on"), "COOLPOWR": (None, "Cooler power in percent"), "SET-TEMP": (None, "Camera temperature setpoint [C]"), @@ -2746,7 +2443,7 @@ def camera_info(self): "CAMNAME": (self.camera.Name, "Name of camera"), "CAMERA": (self.camera.Name, "Name of camera"), "CAMDRVER": (self.camera.DriverVersion, "Camera driver version"), - "CAMDRV": (self.camera.DriverInfo, "Camera driver info"), + "CAMDRV": (self.camera.DriverInfo[0], "Camera driver info"), "CAMINTF": (self.camera.InterfaceVersion, "Camera interface version"), "CAMDESC": (self.camera.Description, "Camera description"), "SENSOR": (self.camera.SensorName, "Name of sensor"), @@ -2782,7 +2479,7 @@ def camera_info(self): "FULLWELL": (self.camera.FullWellCapacity, "Full well capacity [e-]"), "MAXADU": (self.camera.MaxADU, "Camera maximum ADU value possible"), "E-ADU": (self.camera.ElectronsPerADU, "Gain [e- per ADU]"), - "EGAIN": (self.camera.Gain, "Electronic gain"), + "EGAIN": (None, "Electronic gain"), "CANFASTR": (self.camera.CanFastReadout, "Can camera fast readout"), "READMDS": (None, "Possible readout modes"), "GAINS": (None, "Possible electronic gains"), @@ -2791,77 +2488,107 @@ def camera_info(self): "OFFSETS": (None, "Possible offsets"), "OFFSETMN": (None, "Minimum possible offset"), "OFFSETMX": (None, "Maximum possible offset"), - "CAMSUPAC": (self.camera.SupportedActions, "Camera supported actions"), + "CAMSUPAC": (str(self.camera.SupportedActions), "Camera supported actions"), } try: - info["Percent Completed"][0] = self.camera.PercentCompleted + info["PCNTCOMP"] = (self.camera.PercentCompleted, info["PCNTCOMP"][1]) except: pass try: - info["DATE-OBS"][0] = self.camera.LastExposureStartTime - info["JD"][0] = astrotime.Time(self.camera.LastExposureStartTime).jd - info["MJD"][0] = astrotime.Time(self.camera.LastExposureStartTime).mjd + info["DATE-OBS"] = (self.camera.LastExposureStartTime, info["DATE-OBS"][1]) + info["JD"] = ( + astrotime.Time(self.camera.LastExposureStartTime).jd, + info["JD"][1], + ) + info["MJD"] = ( + astrotime.Time(self.camera.LastExposureStartTime).mjd, + info["MJD"][1], + ) + info["MJD-OBS"] = ( + astrotime.Time(self.camera.LastExposureStartTime).mjd, + info["MJD-OBS"][1], + ) except: pass try: - info["EXPTIME"][0] = self.camera.ExposureTime - info["EXPOSURE"][0] = self.camera.ExposureTime + info["EXPTIME"] = (self.camera.ExposureTime, info["EXPTIME"][1]) + info["EXPOSURE"] = (self.camera.ExposureTime, info["EXPOSURE"][1]) except: pass try: - info["SUBEXP"][0] = self.camera.SubExposureDuration + info["SUBEXP"] = (self.camera.SubExposureDuration, info["SUBEXP"][1]) except: pass - info["CANFAST"][0] = self.camera.CanFastReadout + info["CANFASTR"] = (self.camera.CanFastReadout, info["CANFASTR"][1]) if self.camera.CanFastReadout: - info["READOUT"][0] = self.camera.ReadoutModes[self.camera.ReadoutMode] - info["READOUTM"][0] = self.camera.ReadoutModes[self.camera.ReadoutMode] - info["FASTREAD"][0] = self.camera.FastReadout - info["READMDS"][0] = self.camera.ReadoutModes - info["SENSTYP"][0] = [ - "Monochrome, Color, \ + info["READOUT"] = ( + self.camera.ReadoutModes[self.camera.ReadoutMode], + info["READOUT"][1], + ) + info["READOUTM"] = ( + self.camera.ReadoutModes[self.camera.ReadoutMode], + info["READOUTM"][1], + ) + info["FASTREAD"] = (self.camera.FastReadout, info["FASTREAD"][1]) + info["READMDS"] = (self.camera.ReadoutModes, info["READMDS"][1]) + info["SENSTYP"] = ( + [ + "Monochrome, Color, \ RGGB, CMYG, CMYG2, LRGB" - ][self.camera.SensorType] + ][self.camera.SensorType], + info["SENSTYP"][1], + ) if not self.camera.SensorType in (0, 1): - info["BAYERPAT"][0] = self.camera.SensorType - info["BAYOFFX"][0] = self.camera.BayerOffsetX - info["BAYOFFY"][0] = self.camera.BayerOffsetY + info["BAYERPAT"] = (self.camera.SensorType, info["BAYERPAT"][1]) + info["BAYOFFX"] = (self.camera.BayerOffsetX, info["BAYOFFX"][1]) + info["BAYOFFY"] = (self.camera.BayerOffsetY, info["BAYOFFY"][1]) + try: + info["HSINKT"] = (self.camera.HeatSinkTemperature, info["HSINKT"][1]) + except: + pass try: - info["GAINS"][0] = self.camera.Gains - info["GAIN"][0] = self.camera.Gains[self.camera.Gain] + info["GAINS"] = (self.camera.Gains, info["GAINS"][1]) + info["GAIN"] = (self.camera.Gains[self.camera.Gain], info["GAIN"][1]) except: try: - info["GAINMIN"][0] = self.camera.GainMin - info["GAINMAX"][0] = self.camera.GainMax - info["GAIN"][0] = self.camera.Gain + info["GAINMIN"] = (self.camera.GainMin, info["GAINMIN"][1]) + info["GAINMAX"] = (self.camera.GainMax, info["GAINMAX"][1]) + info["GAIN"] = (self.camera.Gain, info["GAIN"][1]) except: pass try: - info["OFFSETS"][0] = self.camera.Offsets - info["OFFSET"][0] = self.camera.Offsets[self.camera.Offset] + info["OFFSETS"] = (self.camera.Offsets, info["OFFSETS"][1]) + info["OFFSET"] = ( + self.camera.Offsets[self.camera.Offset], + info["OFFSET"][1], + ) except: try: - info["OFFSETMN"][0] = self.camera.OffsetMin - info["OFFSETMX"][0] = self.camera.OffsetMax - info["OFFSET"][0] = self.camera.Offset + info["OFFSETMN"] = (self.camera.OffsetMin, info["OFFSETMN"][1]) + info["OFFSETMX"] = (self.camera.OffsetMax, info["OFFSETMX"][1]) + info["OFFSET"] = (self.camera.Offset, info["OFFSET"][1]) except: pass - info["CANPULSE"][0] = self.camera.CanPulseGuide + info["CANPULSE"] = (self.camera.CanPulseGuide, info["CANPULSE"][1]) if self.camera.CanPulseGuide: - info["PULSGUID"][0] = self.camera.IsPulseGuiding + info["PULSGUID"] = (self.camera.IsPulseGuiding, info["PULSGUID"][1]) try: - info["COOLERON"][0] = self.camera.CoolerOn + info["COOLERON"] = (self.camera.CoolerOn, info["COOLERON"][1]) except: pass - info["CANCOOLP"][0] = self.camera.CanGetCoolerPower + info["CANCOOLP"] = (self.camera.CanGetCoolerPower, info["CANCOOLP"][1]) if self.camera.CanGetCoolerPower: - info["COOLPOWR"][0] = self.camera.CoolerPower - info["CANSETTE"][0] = self.camera.CanSetCCDTemperature + info["COOLPOWR"] = (self.camera.CoolerPower, info["COOLPOWR"][1]) + info["CANSETTE"] = (self.camera.CanSetCCDTemperature, info["CANSETTE"][1]) if self.camera.CanSetCCDTemperature: - info["SET-TEMP"][0] = self.camera.SetCCDTemperature + info["SET-TEMP"] = (self.camera.SetCCDTemperature, info["SET-TEMP"][1]) try: - info["CCD-TEMP"][0] = self.camera.CCDTemperature - info["CMOS-TMP"][0] = self.camera.CMOSTemperature + info["CCD-TEMP"] = (self.camera.CCDTemperature, info["CCD-TEMP"][1]) + info["CMOS-TMP"] = (self.camera.CMOSTemperature, info["CMOS-TMP"][1]) + except: + pass + try: + info["EGAIN"] = (self.camera.Gain, info["EGAIN"][1]) except: pass @@ -2887,7 +2614,7 @@ def cover_calibrator_info(self): "Cover calibrator driver version", ), "CCDRV": ( - self.cover_calibrator.DriverInfo, + str(self.cover_calibrator.DriverInfo), "Cover calibrator driver info", ), "CCINTF": ( @@ -2903,12 +2630,12 @@ def cover_calibrator_info(self): "Cover calibrator maximum possible brightness", ), "CCSUPAC": ( - self.cover_calibrator.SupportedActions, + str(self.cover_calibrator.SupportedActions), "Cover calibrator supported actions", ), } try: - info["BRIGHT"][0] = self.cover_calibrator.Brightness + info["BRIGHT"] = (self.cover_calibrator.Brightness, info["BRIGHT"][1]) except: pass return info @@ -2934,7 +2661,7 @@ def dome_info(self): "DOMEPARK": (None, "Dome park status"), "DOMENAME": (self.dome.Name, "Dome name"), "DOMDRVER": (self.dome.DriverVersion, "Dome driver version"), - "DOMEDRV": (self.dome.DriverInfo, "Dome driver info"), + "DOMEDRV": (str(self.dome.DriverInfo), "Dome driver info"), "DOMEINTF": (self.dome.InterfaceVersion, "Dome interface version"), "DOMEDESC": (self.dome.Description, "Dome description"), "DOMECALT": (self.dome.CanSetAltitude, "Can dome set altitude"), @@ -2948,30 +2675,30 @@ def dome_info(self): "DCANHOME": (self.dome.CanFindHome, "Can dome home"), "DCANPARK": (self.dome.CanPark, "Can dome park"), "DCANSPRK": (self.dome.CanSetPark, "Can dome set park"), - "DOMSUPAC": (self.dome.SupportedActions, "Dome supported actions"), + "DOMSUPAC": (str(self.dome.SupportedActions), "Dome supported actions"), } try: - info["DOMEALT"][0] = self.dome.Altitude + info["DOMEALT"] = (self.dome.Altitude, info["DOMEALT"][1]) except: pass try: - info["DOMEAZ"][0] = self.dome.Azimuth + info["DOMEAZ"] = (self.dome.Azimuth, info["DOMEAZ"][1]) except: pass try: - info["DOMESHUT"][0] = self.dome.ShutterStatus + info["DOMESHUT"] = (self.dome.ShutterStatus, info["DOMESHUT"][1]) except: pass try: - info["DOMESLAV"][0] = self.dome.Slaved + info["DOMESLAV"] = (self.dome.Slaved, info["DOMESLAV"][1]) except: pass try: - info["DOMEHOME"][0] = self.dome.AtHome + info["DOMEHOME"] = (self.dome.AtHome, info["DOMEHOME"][1]) except: pass try: - info["DOMEPARK"][0] = self.dome.AtPark + info["DOMEPARK"] = (self.dome.AtPark, info["DOMEPARK"][1]) except: pass return info @@ -3006,19 +2733,22 @@ def filter_wheel_info(self): self.filter_wheel.DriverVersion, "Filter wheel driver version", ), - "FWDRV": (self.filter_wheel.DriverInfo, "Filter wheel driver info"), + "FWDRV": ( + str(self.filter_wheel.DriverInfo), + "Filter wheel driver info", + ), "FWINTF": ( self.filter_wheel.InterfaceVersion, "Filter wheel interface version", ), "FWDESC": (self.filter_wheel.Description, "Filter wheel description"), - "FWALLNAM": (self.filter_wheel.Names, "Filter wheel names"), + "FWALLNAM": (str(self.filter_wheel.Names), "Filter wheel names"), "FWALLOFF": ( - self.filter_wheel.FocusOffsets, + str(self.filter_wheel.FocusOffsets), "Filter wheel focus offsets", ), "FWSUPAC": ( - self.filter_wheel.SupportedActions, + str(self.filter_wheel.SupportedActions), "Filter wheel supported actions", ), } @@ -3042,7 +2772,7 @@ def focuser_info(self): "FOCTEMP": (None, "Focuser temperature"), "FOCNAME": (self.focuser.Name, "Focuser name"), "FOCDRVER": (self.focuser.DriverVersion, "Focuser driver version"), - "FOCDRV": (self.focuser.DriverInfo, "Focuser driver info"), + "FOCDRV": (str(self.focuser.DriverInfo), "Focuser driver info"), "FOCINTF": (self.focuser.InterfaceVersion, "Focuser interface version"), "FOCDESC": (self.focuser.Description, "Focuser description"), "FOCSTEP": (None, "Focuser step size"), @@ -3058,19 +2788,19 @@ def focuser_info(self): ), } try: - info["FOCPOS"][0] = self.focuser.Position + info["FOCPOS"] = (self.focuser.Position, info["FOCPOS"][1]) except: pass try: - info["TEMPCOMP"][0] = self.focuser.TempComp + info["TEMPCOMP"] = (self.focuser.TempComp, info["TEMPCOMP"][1]) except: pass try: - info["FOCTEMP"][0] = self.focuser.Temperature + info["FOCTEMP"] = (self.focuser.Temperature, info["FOCTEMP"][1]) except: pass try: - info["FOCSTEP"][0] = self.focuser.StepSize + info["FOCSTEP"] = (self.focuser.StepSize, info["FOCSTEP"][1]) except: pass return info @@ -3084,8 +2814,8 @@ def observatory_info(self): "OBSNAME": (self.site_name, "Observatory name"), "OBSINSTN": (self.instrument_name, "Instrument name"), "OBSINSTD": (self.instrument_description, "Instrument description"), - "OBSLAT": (self.latitude, "Observatory latitude"), - "OBSLONG": (self.longitude, "Observatory longitude"), + "OBSLAT": (self.latitude.to_string(sep="dms"), "Observatory latitude"), + "OBSLONG": (self.longitude.to_string(sep="dms"), "Observatory longitude"), "OBSELEV": (self.elevation, "Observatory altitude"), "OBSDIA": (self.diameter, "Observatory diameter"), "OBSFL": (self.focal_length, "Observatory focal length"), @@ -3170,7 +2900,7 @@ def observing_conditions_info(self): "Observing conditions driver version", ), "WXDRIV": ( - self.observing_conditions.DriverInfo, + str(self.observing_conditions.DriverInfo), "Observing conditions driver info", ), "WXINTF": ( @@ -3183,132 +2913,189 @@ def observing_conditions_info(self): ), } try: - info["WXCLD"][0] = self.observing_conditions.CloudCover - info["WXCLDUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "CloudCover" + info["WXCLD"] = (self.observing_conditions.CloudCover, info["WXCLD"][1]) + info["WXCLDUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("CloudCover"), + info["WXCLDUPD"][1], ) - info["WXCLDD"][0] = self.observing_conditions.SensorDescription( - "CloudCover" + info["WXCLDD"] = ( + self.observing_conditions.SensorDescription("CloudCover"), + info["WXCLDD"][1], ) except: pass try: - info["WXDEW"][0] = self.observing_conditions.DewPoint - info["WXDEWUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "DewPoint" + info["WXDEW"] = (self.observing_conditions.DewPoint, info["WXDEW"][1]) + info["WXDEWUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("DewPoint"), + info["WXDEWUPD"][1], ) - info["WXDEWD"][0] = self.observing_conditions.SensorDescription( - "DewPoint" + info["WXDEWD"] = ( + self.observing_conditions.SensorDescription("DewPoint"), + info["WXDEWD"][1], ) except: pass try: - info["WXHUM"][0] = self.observing_conditions.Humidity - info["WXHUMUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "Humidity" + info["WXHUM"] = (self.observing_conditions.Humidity, info["WXHUM"][1]) + info["WXHUMUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("Humidity"), + info["WXHUMUPD"][1], ) - info["WXHUMD"][0] = self.observing_conditions.SensorDescription( - "Humidity" + info["WXHUMD"] = ( + self.observing_conditions.SensorDescription("Humidity"), + info["WXHUMD"][1], ) except: pass try: - info["WXPRES"][0] = self.observing_conditions.Pressure - info["WXPREUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "Pressure" + info["WXPRES"] = (self.observing_conditions.Pressure, info["WXPRES"][1]) + info["WXPREUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("Pressure"), + info["WXPREUPD"][1], ) - info["WXPRESD"][0] = self.observing_conditions.SensorDescription( - "Pressure" + info["WXPRESD"] = ( + self.observing_conditions.SensorDescription("Pressure"), + info["WXPRESD"][1], ) except: pass try: - info["WXRAIN"][0] = self.observing_conditions.RainRate - info["WXRAIUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "RainRate" + info["WXRAIN"] = (self.observing_conditions.RainRate, info["WXRAIN"][1]) + info["WXRAIUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("RainRate"), + info["WXRAIUPD"][1], ) - info["WXRAIND"][0] = self.observing_conditions.SensorDescription( - "RainRate" + info["WXRAIND"] = ( + self.observing_conditions.SensorDescription("RainRate"), + info["WXRAIND"][1], ) except: pass try: - info["WXSKY"][0] = self.observing_conditions.SkyBrightness - info["WXSKYUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "SkyBrightness" + info["WXSKY"] = ( + self.observing_conditions.SkyBrightness, + info["WXSKY"][1], ) - info["WXSKYD"][0] = self.observing_conditions.SensorDescription( - "SkyBrightness" + info["WXSKYUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("SkyBrightness"), + info["WXSKYUPD"][1], + ) + info["WXSKYD"] = ( + self.observing_conditions.SensorDescription("SkyBrightness"), + info["WXSKYD"][1], ) except: pass try: - info["WXSKYQ"][0] = self.observing_conditions.SkyQuality - info["WXSKYQUP"][0] = self.observing_conditions.TimeSinceLastUpdate( - "SkyQuality" + info["WXSKYQ"] = ( + self.observing_conditions.SkyQuality, + info["WXSKYQ"][1], + ) + info["WXSKYQUP"] = ( + self.observing_conditions.TimeSinceLastUpdate("SkyQuality"), + info["WXSKYQUP"][1], ) - info["WXSKYQD"][0] = self.observing_conditions.SensorDescription( - "SkyQuality" + info["WXSKYQD"] = ( + self.observing_conditions.SensorDescription("SkyQuality"), + info["WXSKYQD"][1], ) except: pass try: - info["WXSKYTMP"][0] = self.observing_conditions.SkyTemperature - info["WXSKTUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "SkyTemperature" + info["WXSKYTMP"] = ( + self.observing_conditions.SkyTemperature, + info["WXSKYTMP"][1], + ) + info["WXSKTUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("SkyTemperature"), + info["WXSKTUPD"][1], ) - info["WXSKTD"][0] = self.observing_conditions.SensorDescription( - "SkyTemperature" + info["WXSKTD"] = ( + self.observing_conditions.SensorDescription("SkyTemperature"), + info["WXSKTD"][1], ) except: pass try: - info["WXFWHM"][0] = self.observing_conditions.Seeing - info["WXFWHUP"][0] = self.observing_conditions.TimeSinceLastUpdate( - "Seeing" + info["WXFWHM"] = ( + self.observing_conditions.WindSpeed, + info["WXFWHM"][1], ) - info["WXFWHMD"][0] = self.observing_conditions.SensorDescription( - "Seeing" + info["WXFWHUP"] = ( + self.observing_conditions.TimeSinceLastUpdate("WindSpeed"), + info["WXFWHUP"][1], + ) + info["WXFWHMD"] = ( + self.observing_conditions.SensorDescription("WindSpeed"), + info["WXFWHMD"][1], ) except: pass try: - info["WXTEMP"][0] = self.observing_conditions.Temperature - info["WXTEMUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "Temperature" + info["WXTEMP"] = ( + self.observing_conditions.Temperature, + info["WXTEMP"][1], + ) + info["WXTEMUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("Temperature"), + info["WXTEMUPD"][1], ) - info["WXTEMPD"][0] = self.observing_conditions.SensorDescription( - "Temperature" + info["WXTEMPD"] = ( + self.observing_conditions.SensorDescription("Temperature"), + info["WXTEMPD"][1], ) except: pass try: - info["WXWIND"][0] = self.observing_conditions.WindSpeed - info["WXWINUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "WindSpeed" + info["WXWIND"] = ( + self.observing_conditions.WindSpeed, + info["WXWIND"][1], ) - info["WXWINDD"][0] = self.observing_conditions.SensorDescription( - "WindSpeed" + info["WXWINUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("WindSpeed"), + info["WXWINUPD"][1], + ) + info["WXWINDD"] = ( + self.observing_conditions.SensorDescription("WindSpeed"), + info["WXWINDD"][1], ) except: pass try: - info["WXWINDIR"][0] = self.observing_conditions.WindDirection - info["WXWDIRUP"][0] = self.observing_conditions.TimeSinceLastUpdate( - "WindDirection" + info["WXWINDIR"] = ( + self.observing_conditions.WindDirection, + info["WXWINDIR"][1], + ) + info["WXWDIRUP"] = ( + self.observing_conditions.TimeSinceLastUpdate("WindDirection"), + info["WXWDIRUP"][1], ) - info["WXWDIRD"][0] = self.observing_conditions.SensorDescription( - "WindDirection" + info["WXWDIRD"] = ( + self.observing_conditions.SensorDescription("WindDirection"), + info["WXWDIRD"][1], ) except: pass try: - info["WXWDGST"][0] = self.observing_conditions.WindGust - info["WXWGDUPD"][0] = self.observing_conditions.TimeSinceLastUpdate( - "WindGust" + info["WXWDGST"] = ( + self.observing_conditions.WindGust, + info["WXWDGST"][1], + ) + info["WXWGDUPD"] = ( + self.observing_conditions.TimeSinceLastUpdate("WindGust"), + info["WXWGDUPD"][1], + ) + info["WXWGDSTD"] = ( + self.observing_conditions.SensorDescription("WindGust"), + info["WXWGDSTD"][1], ) - info["WXWGDSTD"][0] = self.observing_conditions.SensorDescription( - "WindGust" + except: + pass + try: + info["WXAVGTIM"] = ( + self.observing_conditions.AveragePeriod, + info["WXAVGTIM"][1], ) except: pass @@ -3336,7 +3123,7 @@ def rotator_info(self): "ROTREVSE": (self.rotator.Reverse, "Rotator reverse"), "ROTNAME": (self.rotator.Name, "Rotator name"), "ROTDRVER": (self.rotator.DriverVersion, "Rotator driver version"), - "ROTDRV": (self.rotator.DriverInfo, "Rotator driver name"), + "ROTDRV": (str(self.rotator.DriverInfo), "Rotator driver name"), "ROTINTFC": ( self.rotator.InterfaceVersion, "Rotator interface version", @@ -3345,12 +3132,12 @@ def rotator_info(self): "ROTSTEP": (None, "Rotator step size [degrees]"), "ROTCANRV": (self.rotator.CanReverse, "Can rotator reverse"), "ROTSUPAC": ( - self.rotator.SupportedActions, + str(self.rotator.SupportedActions), "Rotator supported actions", ), } try: - info["ROTSTEP"][0] = self.rotator.StepSize + info["ROTSTEP"] = (self.rotator.StepSize, info["ROTSTEP"][1]) except: pass return info @@ -3382,7 +3169,7 @@ def safety_monitor_info(self, index=None): "Safety monitor driver version", ), ("SM%iDRV" % i): ( - self.safety_monitor[i].DriverInfo, + str(self.safety_monitor[i].DriverInfo), "Safety monitor driver name", ), ("SM%iINTF" % i): ( @@ -3394,7 +3181,7 @@ def safety_monitor_info(self, index=None): "Safety monitor description", ), ("SM%iSUPAC" % i): ( - self.safety_monitor[i].SupportedActions, + str(self.safety_monitor[i].SupportedActions), "Safety monitor supported actions", ), } @@ -3426,7 +3213,10 @@ def switch_info(self, index=None): self.switch[i].DriverVersion, "Switch driver version", ), - ("SW%iDRV" % i): (self.switch[i].DriverInfo, "Switch driver name"), + ("SW%iDRV" % i): ( + str(self.switch[i].DriverInfo), + "Switch driver name", + ), ("SW%iINTF" % i): ( self.switch[i].InterfaceVersion, "Switch interface version", @@ -3436,7 +3226,7 @@ def switch_info(self, index=None): "Switch description", ), ("SW%iSUPAC" % i): ( - self.switch[i].SupportedActions, + str(self.switch[i].SupportedActions), "Switch supported actions", ), ("SW%iMAXSW" % i): ( @@ -3457,10 +3247,6 @@ def switch_info(self, index=None): self.switch[i].GetSwitch(j), "Switch %i Device %i state" % (i, j), ) - info[("SW%iSW%iVA" % (i, j))] = ( - self.switch[i].GetSwitchValue(j), - "Switch %i Device %i value" % (i, j), - ) info[("SW%iSW%iMN" % (i, j))] = ( self.switch[i].MinSwitchValue(j), "Switch %i Device %i minimum value" % (i, j), @@ -3558,7 +3344,7 @@ def telescope_info(self): "TELUT": (None, "Telescope UTC date"), "TELNAME": (self.telescope.Name, "Telescope name"), "TELDRVER": (self.telescope.DriverVersion, "Telescope driver version"), - "TELDRV": (self.telescope.DriverInfo, "Telescope driver name"), + "TELDRV": (str(self.telescope.DriverInfo), "Telescope driver name"), "TELINTF": (self.telescope.InterfaceVersion, "Telescope interface version"), "TELDESC": (self.telescope.Description, "Telescope description"), "TELAPAR": (None, "Telescope aperture area [m^2]"), @@ -3578,7 +3364,7 @@ def telescope_info(self): "TELCANPA": (self.telescope.CanPark, "Can telescope park"), "TELCANUN": (self.telescope.CanUnpark, "Can telescope unpark"), "TELCANPP": (self.telescope.CanSetPark, "Can telescope set park position"), - "TELCANPG": (self.telescope.CanPulseGuide, "Can telescope pulse guide"), + # "TELCANPG": (self.telescope.CanPulseGuide, "Can telescope pulse guide"), "TELCANGR": ( self.telescope.CanSetGuideRates, "Can telescope set guide rates", @@ -3617,135 +3403,170 @@ def telescope_info(self): self.telescope.CanSyncAltAz, "Can telescope sync to alt-azimuth coordinates", ), - "TELTRCKS": (self.telescope.TrackingRates, "Telescope tracking rates"), + "TELTRCKS": (str(self.telescope.TrackingRates), "Telescope tracking rates"), "TELSUPAC": ( - self.telescope.SupportedActions, + str(self.telescope.SupportedActions), "Telescope supported actions", ), } try: - info["TELALT"][0] = self.telescope.Altitude + info["TELALT"] = (self.telescope.Altitude, info["TELALT"][1]) except: pass try: - info["TELAZ"][0] = self.telescope.Azimuth + info["TELAZ"] = (self.telescope.Azimuth, info["TELAZ"][1]) except: pass try: - info["TARGRA"][0] = self.telescope.TargetRightAscension + info["TARGRA"] = (self.telescope.TargetRightAscension, info["TARGRA"][1]) except: pass try: - info["TARGDEC"][0] = self.telescope.TargetDeclination + info["TARGDEC"] = (self.telescope.TargetDeclination, info["TARGDEC"][1]) except: pass obj = self.get_current_object() - info["TELRAIC"][0] = obj.ra.to_string(unit=u.hour) - info["TELDECIC"][0] = obj.dec.to_string(unit=u.degree) - info["OBJCTALT"][0] = obj.transform_to( - coord.AltAz( - obstime=self.observatory_time, location=self.observatory_location - ) - ).alt.to(u.degree) - info["OBJCTAZ"][0] = obj.transform_to( - coord.AltAz( - obstime=self.observatory_time, location=self.observatory_location + info["TELRAIC"] = (obj.ra.to_string(unit=u.hour), info["TELRAIC"][1]) + info["TELDECIC"] = (obj.dec.to_string(unit=u.degree), info["TELDECIC"][1]) + info["OBJCTALT"] = ( + obj.transform_to( + coord.AltAz( + obstime=self.observatory_time, location=self.observatory_location + ) ) - ).az.to(u.degree) - info["OBJCTHA"][0] = abs(self.lst - obj.ra).to(u.hour) - info["AIRMASS"][0] = _airmass( + .alt.to(u.degree) + .value, + info["OBJCTALT"][1], + ) + info["OBJCTAZ"] = ( obj.transform_to( coord.AltAz( obstime=self.observatory_time, location=self.observatory_location ) - ).alt.to(u.rad) + ) + .az.to(u.degree) + .value, + info["OBJCTAZ"][1], ) - info["MOONANGL"][0] = ( - coord.get_moon(self.observatory_time, location=self.observatory_location) + info["OBJCTHA"] = (np.abs(self.lst() - obj.ra.hour), info["OBJCTHA"][1]) + info["AIRMASS"] = ( + airmass( + obj.transform_to( + coord.AltAz( + obstime=self.observatory_time, + location=self.observatory_location, + ) + ) + .alt.to(u.rad) + .value + ), + info["AIRMASS"][1], + ) + info["MOONANGL"] = ( + coord.get_body( + "moon", self.observatory_time, location=self.observatory_location + ) .separation(obj) .to(u.degree) + .value, + info["MOONANGL"][1], + ) + info["MOONPHAS"] = ( + self.moon_illumination(self.observatory_time), + info["MOONPHAS"][1], ) - info["MOONPHAS"][0] = self.moon_illumination(self.observatory_time) try: - info["TELSLEW"][0] = self.telescope.Slewing + info["TELSLEW"] = (self.telescope.Slewing, info["TELSLEW"][1]) except: pass try: - info["TELSETT"][0] = self.telescope.SlewSettleTime + info["TELSETT"] = (self.telescope.SlewSettleTime, info["TELSETT"][1]) except: pass try: - info["TELPIER"][0] = ["pierEast", "pierWest", "pierUnknown"][ - self.telescope.SideOfPier - ] + info["TELPIER"] = ( + ["pierEast", "pierWest", "pierUnknown"][self.telescope.SideOfPier], + info["TELPIER"][1], + ) except: pass try: - info["TELTRACK"][0] = self.telescope.Tracking + info["TELTRACK"] = (self.telescope.Tracking, info["TELTRACK"][1]) except: pass try: - info["TELTRKRT"][0] = self.telescope.TrackingRates[ - self.telescope.TrackingRate - ] + info["TELTRKRT"] = ( + self.telescope.TrackingRates[self.telescope.TrackingRate], + info["TELTRKRT"][1], + ) except: pass try: - info["TELOFFRA"][0] = self.telescope.RightAscensionRate + info["TELOFFRA"] = (self.telescope.RightAscensionRate, info["TELOFFRA"][1]) except: pass try: - info["TELOFFDC"][0] = self.telescope.DeclinationRate + info["TELOFFDC"] = (self.telescope.DeclinationRate, info["TELOFFDC"][1]) except: pass try: - info["TELPULSE"][0] = self.telescope.IsPulseGuiding + info["TELPULSE"] = (self.telescope.IsPulseGuiding, info["TELPULSE"][1]) except: pass try: - info["TELGUIDR"][0] = self.telescope.GuideRateRightAscension + info["TELGUIDR"] = ( + self.telescope.GuideRateRightAscension, + info["TELGUIDR"][1], + ) except: pass try: - info["TELGUIDD"][0] = self.telescope.GuideRateDeclination + info["TELGUIDD"] = ( + self.telescope.GuideRateDeclination, + info["TELGUIDD"][1], + ) except: pass try: - info["TELDOREF"][0] = self.telescope.DoesRefraction + info["TELDOREF"] = (self.telescope.DoesRefraction, info["TELDOREF"][1]) except: pass try: - info["TELUT"][0] = self.telescope.UTCDate + info["TELUT"] = ( + self.telescope.UTCDate.strftime("%Y-%m-%dT%H:%M:%S"), + info["TELUT"][1], + ) except: pass try: - info["TELAPAR"][0] = self.telescope.ApertureArea + info["TELAPAR"] = (self.telescope.ApertureArea, info["TELAPAR"][1]) except: pass try: - info["TELDIAM"][0] = self.telescope.ApertureDiameter + info["TELDIAM"] = (self.telescope.ApertureDiameter, info["TELDIAM"][1]) except: pass try: - info["TELFOCL"][0] = self.telescope.FocalLength + info["TELFOCL"] = (self.telescope.FocalLength, info["TELFOCL"][1]) except: pass try: - info["TELELEV"][0] = self.telescope.SiteElevation + info["TELELEV"] = (self.telescope.SiteElevation, info["TELELEV"][1]) except: pass try: - info["TELLAT"][0] = self.telescope.SiteLatitude + info["TELLAT"] = (self.telescope.SiteLatitude, info["TELLAT"][1]) except: pass try: - info["TELLONG"][0] = self.telescope.SiteLongitude + info["TELLONG"] = (self.telescope.SiteLongitude, info["TELLONG"][1]) except: pass try: - info["TELALN"][0] = ["AltAz", "Polar", "GermanPolar"][ - self.telescope.AlignmentMode - ] + info["TELALN"] = ( + ["AltAz", "Polar", "GermanPolar"][self.telescope.AlignmentMode], + info["TELALN"][1], + ) except: pass return info @@ -3755,15 +3576,15 @@ def threads_info(self): logger.debug("Observatory.threads_info() called") return { "DEROTATE": ( - not self.derotation_thread is None, + not self._derotation_thread is None, "Is derotation thread active", ), "OCTHREAD": ( - not self.observing_conditions_thread is None, + not self._observing_conditions_thread is None, "Is observing conditions thread active", ), "SMTHREAD": ( - not self.safety_monitor_thread is None, + not self._safety_monitor_thread is None, "Is status monitor thread active", ), } @@ -3771,7 +3592,7 @@ def threads_info(self): @property def wcs_info(self): logger.debug("Observatory.wcs_info() called") - return {"WCSDRV": (self.wcs_driver, "WCS driver")} + return {"WCSDRV": (str(self.wcs_driver), "WCS driver")} @property def observatory_location(self): @@ -3856,15 +3677,9 @@ def latitude(self, value): self._latitude = ( coord.Latitude(value) if value is not None or value != "" else None ) + self.telescope.SiteLatitude = self._latitude.deg self._config["site"]["latitude"] = ( - self._latitude.to_string( - unit=u.deg, - sep=":", - precision=5, - pad=True, - alwayssign=True, - decimal=True, - ) + self._latitude.to_string(unit=u.degree, sep="dms", precision=5) if self._latitude is not None else "" ) @@ -3878,17 +3693,13 @@ def longitude(self): def longitude(self, value): logger.debug(f"Observatory.longitude = {value} called") self._longitude = ( - coord.Longitude(value) if value is not None or value != "" else None + coord.Longitude(value, wrap_angle=180 * u.deg) + if value is not None or value != "" + else None ) + self.telescope.SiteLongitude = self._longitude.deg self._config["site"]["longitude"] = ( - self._longitude.to_string( - unit=u.deg, - sep=":", - precision=5, - pad=True, - alwayssign=True, - decimal=True, - ) + self._longitude.to_string(unit=u.degree, sep="dms", precision=5) if self._longitude is not None else "" ) @@ -3949,9 +3760,9 @@ def camera_driver(self): return self._camera_driver @property - def camera_ascom(self): - logger.debug("Observatory.camera_ascom property called") - return self._camera_ascom + def camera_kwargs(self): + logger.debug("Observatory.camera_kwargs property called") + return self._camera_kwargs @property def cooler_setpoint(self): @@ -3998,7 +3809,7 @@ def max_dimension(self): def max_dimension(self, value): logger.debug(f"Observatory.max_dimension = {value} called") self._max_dimension = ( - max(int(value), 1) if value is not None or value != "" else None + max(int(value), 1) if value is not None and value != "" else None ) self._config["camera"]["max_dimension"] = ( str(self._max_dimension) if self._max_dimension is not None else "" @@ -4024,9 +3835,9 @@ def cover_calibrator_driver(self): return self._cover_calibrator_driver @property - def cover_calibrator_ascom(self): - logger.debug("Observatory.cover_calibrator_ascom property called") - return self._cover_calibrator_ascom + def cover_calibrator_kwargs(self): + logger.debug("Observatory.cover_calibrator_kwargs property called") + return self._cover_calibrator_kwargs @property def cover_calibrator_alt(self): @@ -4073,9 +3884,9 @@ def dome_driver(self): return self._dome_driver @property - def dome_ascom(self): - logger.debug("Observatory.dome_ascom property called") - return self._dome_ascom + def dome_kwargs(self): + logger.debug("Observatory.dome_kwargs property called") + return self._dome_kwargs @property def filter_wheel(self): @@ -4088,9 +3899,9 @@ def filter_wheel_driver(self): return self._filter_wheel_driver @property - def filter_wheel_ascom(self): - logger.debug("Observatory.filter_wheel_ascom property called") - return self._filter_wheel_ascom + def filter_wheel_kwargs(self): + logger.debug("Observatory.filter_wheel_kwargs property called") + return self._filter_wheel_kwargs @property def filters(self): @@ -4100,8 +3911,16 @@ def filters(self): @filters.setter def filters(self, value, position=None): logger.debug(f"Observatory.filters = {value} called") + if value is self._filters: + return + if type(value) is list: + self._filters = value if position is None: - self._filters = list(value) if value is not None or value != "" else None + self._filters = ( + [v for v in value.replace(" ", "").split(",")] + if value is not None or value != "" + else None + ) else: self._filters[position] = ( char(value) if value is not None or value != "" else None @@ -4118,18 +3937,27 @@ def filter_focus_offsets(self): @filter_focus_offsets.setter def filter_focus_offsets(self, value, filt=None): logger.debug(f"Observatory.filter_focus_offsets = {value} called") + if value is self._filter_focus_offsets: + return + if type(value) is dict: + if value.keys() == self.filters: + self._filter_focus_offsets = value + else: + raise ObservatoryException( + "Filter focus offsets dictionary must have keys matching filters" + ) if filt is None: self._filter_focus_offsets = ( - dict(zip(self.filters, value)) + dict(zip(self.filters, [int(v) for v in value.split(",")])) if value is not None or value != "" else None ) else: self._filter_focus_offsets[filt] = ( - float(value) if value is not None or value != "" else None + int(value) if value is not None or value != "" else None ) self._config["filter_wheel"]["filter_focus_offsets"] = ( - ", ".join(self._filter_focus_offsets.values()) + ",".join([str(v) for v in self._filter_focus_offsets.values()]) if self._filter_focus_offsets is not None else "" ) @@ -4145,24 +3973,9 @@ def focuser_driver(self): return self._focuser_driver @property - def focuser_ascom(self): - logger.debug("Observatory.focuser_ascom property called") - return self._focuser_ascom - - @property - def focuser_max_error(self): - logger.debug("Observatory.focuser_max_error property called") - return self._focuser_max_error - - @focuser_max_error.setter - def focuser_max_error(self, value): - logger.debug(f"Observatory.focuser_max_error = {value} called") - self._focuser_max_error = ( - max(float(value), 0) if value is not None or value != "" else None - ) - self._config["focuser"]["focuser_max_error"] = ( - str(self._focuser_max_error) if self._focuser_max_error is not None else "" - ) + def focuser_kwargs(self): + logger.debug("Observatory.focuser_kwargs property called") + return self._focuser_kwargs @property def observing_conditions(self): @@ -4175,9 +3988,9 @@ def observing_conditions_driver(self): return self._observing_conditions_driver @property - def observing_conditions_ascom(self): - logger.debug("Observatory.observing_conditions_ascom property called") - return self._observing_conditions_ascom + def observing_conditions_kwargs(self): + logger.debug("Observatory.observing_conditions_kwargs property called") + return self._observing_conditions_kwargs @property def rotator(self): @@ -4190,9 +4003,9 @@ def rotator_driver(self): return self._rotator_driver @property - def rotator_ascom(self): - logger.debug("Observatory.rotator_ascom property called") - return self._rotator_ascom + def rotator_kwargs(self): + logger.debug("Observatory.rotator_kwargs property called") + return self._rotator_kwargs @property def rotator_reverse(self): @@ -4251,9 +4064,9 @@ def safety_monitor_driver(self): return self._safety_monitor_driver @property - def safety_monitor_ascom(self): - logger.debug("Observatory.safety_monitor_ascom property called") - return self._safety_monitor_ascom + def safety_monitor_kwargs(self): + logger.debug("Observatory.safety_monitor_kwargs property called") + return self._safety_monitor_kwargs @property def switch(self): @@ -4266,9 +4079,9 @@ def switch_driver(self): return self._switch_driver @property - def switch_ascom(self): - logger.debug("Observatory.switch_ascom property called") - return self._switch_ascom + def switch_kwargs(self): + logger.debug("Observatory.switch_kwargs property called") + return self._switch_kwargs @property def telescope(self): @@ -4281,9 +4094,9 @@ def telescope_driver(self): return self._telescope_driver @property - def telescope_ascom(self): - logger.debug("Observatory.telescope_ascom property called") - return self._telescope_ascom + def telescope_kwargs(self): + logger.debug("Observatory.telescope_kwargs property called") + return self._telescope_kwargs @property def min_altitude(self): @@ -4325,11 +4138,21 @@ def autofocus_driver(self): logger.debug("Observatory.autofocus_driver property called") return self._autofocus_driver + @property + def autofocus_kwargs(self): + logger.debug("Observatory.autofocus_kwargs property called") + return self._autofocus_kwargs + @property def wcs_driver(self): logger.debug("Observatory.wcs_driver property called") return self._wcs_driver + @property + def wcs_kwargs(self): + logger.debug("Observatory.wcs_kwargs property called") + return self._wcs_kwargs + @property def slew_rate(self): logger.debug("Observatory.slew_rate property called") @@ -4339,10 +4162,27 @@ def slew_rate(self): def slew_rate(self, value): logger.debug(f"Observatory.slew_rate = {value} called") self._slew_rate = float(value) if value is not None or value != "" else None - self._config["telescope"]["slew_rate"] = ( + self._config["scheduling"]["slew_rate"] = ( str(self._slew_rate) if self._slew_rate is not None else "" ) + @property + def instrument_reconfiguration_times(self): + logger.debug("Observatory.instrument_reconfiguration_times property called") + return self._instrument_reconfiguration_times + + @instrument_reconfiguration_times.setter + def instrument_reconfiguration_times(self, value): + logger.debug(f"Observatory.instrument_reconfiguration_times = {value} called") + self._instrument_reconfiguration_times = ( + value if value is not None or value != "" else {} + ) + self._config["scheduling"]["instrument_reconfiguration_times"] = ( + json.dumps(self._instrument_reconfiguration_times) + if self._instrument_reconfiguration_times is not None + else "" + ) + @property def last_camera_shutter_status(self): logger.debug("Observatory.last_camera_shutter_status property called") @@ -4359,31 +4199,34 @@ def maxim(self): return self._maxim -def _import_driver(device, driver_name=None, ascom=False, filepath=None, **kwargs): - """Imports a driver""" +def _import_driver(driver, kwargs=None): + """Imports a driver. If the driver is None, returns None. First tries to + import the driver from the observatory package. + + """ logger.debug("observatory._import_driver() called") - if driver_name is None and not ascom: + if driver is None: return None - if ascom: - return getattr(observatory, "ASCOM" + device)(driver_name) - else: + try: + device_class = getattr(observatory, driver) + except: try: - device_class = getattr(observatory, driver_name) + module_name = kwargs.values()[0].split("/")[-1].split(".")[0] + spec = importlib.util.spec_from_file_location(module_name, kwargs[0]) + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + spec.loader.exec_module(module) + device_class = getattr(module, driver) + + if len(kwargs) > 1: + kwargs = kwargs[1:] + else: + kwargs = None except: - try: - module_name = filepath.split("/")[-1].split(".")[0] - spec = importlib.util.spec_from_file_location(module_name, filepath) - module = importlib.util.module_from_spec(spec) - sys.modules[module_name] = module - spec.loader.exec_module(module) - device_class = getattr(module, driver_name) - except: - return None - - _check_class_inheritance(device_class, device) + return None if kwargs is None: return device_class() diff --git a/pyscope/utils/__init__.py b/pyscope/utils/__init__.py index 86277561..112b7c3b 100644 --- a/pyscope/utils/__init__.py +++ b/pyscope/utils/__init__.py @@ -1,6 +1,7 @@ -from ._args_kwargs_config import _args_to_config, _kwargs_to_config +from ._args_kwargs_config import _kwargs_to_config from ._function_synchronicity import _force_async, _force_sync from ._get_image_source_catalog import _get_image_source_catalog +from ._html_line_parser import _get_number_from_line from .airmass import airmass from .pyscope_exception import PyscopeException diff --git a/pyscope/utils/_args_kwargs_config.py b/pyscope/utils/_args_kwargs_config.py index bab7c5c6..d3572c8c 100644 --- a/pyscope/utils/_args_kwargs_config.py +++ b/pyscope/utils/_args_kwargs_config.py @@ -1,16 +1,10 @@ -def _args_to_config(self, args): - if args is None or len(args) == 0: - return "" - string = "" - for arg in args: - string += str(arg) + " " - return string[:-1] - - -def _kwargs_to_config(self, kwargs): +def _kwargs_to_config(kwargs): if kwargs is None or len(kwargs) == 0: return "" string = "" for key, value in kwargs.items(): - string += str(key) + ":" + str(value) + " " + if ":" in str(value): + string += str(key) + "='" + str(value) + "'," + else: + string += str(key) + "=" + str(value) + "," return string[:-1] diff --git a/pyscope/utils/_function_synchronicity.py b/pyscope/utils/_function_synchronicity.py index df750ed4..cf0b9771 100644 --- a/pyscope/utils/_function_synchronicity.py +++ b/pyscope/utils/_function_synchronicity.py @@ -6,6 +6,7 @@ def _force_async(fn): """ turns a sync function to async function using threads """ + import asyncio from concurrent.futures import ThreadPoolExecutor pool = ThreadPoolExecutor() diff --git a/pyscope/utils/_html_line_parser.py b/pyscope/utils/_html_line_parser.py new file mode 100644 index 00000000..b69f103c --- /dev/null +++ b/pyscope/utils/_html_line_parser.py @@ -0,0 +1,55 @@ +def _get_number_from_line(line, expected_keyword, expected_units, is_numeric): + """ + Check to see if the provided line looks like a valid telemetry line + from the Winer webpage. A typical line looks like this: + + + If the line matches this format and the contents match expectations, return + the extracted value from the line. + + line: the line text to inspect + expected_keyword: the line must contain this keyword ('TEMPERATURE' in the example above) + expected_units: the line must contain these units after the value ('F' in the example above). + If this value is None, then units are not validated + is_numeric: if True, the value is validated and converted to a float before being returned. + if False, the string value is returned + + If the line does not match or there is a problem (e.g. converting the value to a float), + the function returns None. + + Otherwise, the function returns the value, either as a float (if requested) or as a string + """ + + line = line.strip() + if not line.startswith(b""): + return None + + line = line[4:-3] # Strip off beginning and ending comment characters + + fields = line.split(b"=", 1) # Split into at most two fields (keyword and value) + if len(fields) != 2: + return None + + line_keyword = fields[0].strip() + line_value_and_units = fields[1].strip() + + fields = line_value_and_units.split(b" ", 1) + line_value = fields[0].strip() + if len(fields) > 1: + line_units = fields[1] + else: + line_units = "" + + if line_keyword != expected_keyword: + return None + if expected_units is not None and line_units != expected_units: + return None + if is_numeric: + try: + return float(line_value) + except: + return None + else: + return line_value diff --git a/requirements.txt b/requirements.txt index 93384111..43b8fd8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,8 @@ paramiko == 3.3.1 photutils == 1.9.0 prettytable == 3.9.0 pywin32 == 306;platform_system=='Windows' +scikit-image == 0.21.0 +scipy == 1.10.1 smplotlib == 0.0.9 timezonefinder == 6.2.0 tksheet == 6.2.4 diff --git a/setup.cfg b/setup.cfg index 9a2370a5..6c62acfd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -57,7 +57,7 @@ console_scripts = [options.extras_require] docs = - sphinx==7.2.4 + sphinx==7.2.5 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 sphinxcontrib-programoutput==0.17 @@ -77,7 +77,7 @@ dev = pytest-cov==4.1.0 pytest-order==1.0.1 rstcheck==6.1.2 - sphinx==7.2.4 + sphinx==7.2.5 sphinx-astropy[confv2]==1.9.1 sphinx-favicon==1.0.1 sphinxcontrib-programoutput==0.17 diff --git a/tests/observatory/conftest.py b/tests/observatory/conftest.py index 62dad9a1..a89057a9 100644 --- a/tests/observatory/conftest.py +++ b/tests/observatory/conftest.py @@ -15,7 +15,7 @@ def pytest_configure(): pytest.simulator_server = observatory.SimulatorServer() - time.sleep(10) + time.sleep(12) def pytest_sessionfinish(session, exitstatus): @@ -45,6 +45,32 @@ def disconnect(request): ) +@pytest.fixture() +def winer_oc(request): + global d + d = observatory.HTMLObservingConditions( + "https://winer.org/Site/Weather.php", + rain_rate_keyword="RAIN", + rain_rate_units="in", + sky_brightness_keyword="BRIGHTNESS", + sky_brightness_units="Vmag/sq.asec", + star_fwhm_keyword="LAST_SEEING", + star_fwhm_units='"', + wind_direction_keyword="WINDDIR", + ) + return d + + +@pytest.fixture() +def winer_sm(request): + global d + d = observatory.HTMLSafetyMonitor( + "https://winer.org/Site/Roof.php", + check_phrase=b"ROOFPOSITION=OPEN", + ) + return d + + """@pytest.fixture() def settings(request): n = request.module.__name__.split('_')[-1].capitalize() diff --git a/tests/observatory/test_all_abstract.py b/tests/observatory/test_all_abstract.py index 8dd444f4..cf380c6f 100644 --- a/tests/observatory/test_all_abstract.py +++ b/tests/observatory/test_all_abstract.py @@ -179,7 +179,6 @@ def test_abstract(self, cls_name): "Move", "Absolute", "IsMoving", - "Link", "MaxIncrement", "MaxStep", "Position", diff --git a/tests/observatory/test_ascom_camera.py b/tests/observatory/test_ascom_camera.py index bb90db99..d452b9ca 100644 --- a/tests/observatory/test_ascom_camera.py +++ b/tests/observatory/test_ascom_camera.py @@ -67,12 +67,14 @@ def test_binx(device, disconnect): assert device.BinX is not None device.BinX = 2 assert device.BinX == 2 + device.BinX = 1 def test_biny(device, disconnect): assert device.BinY is not None device.BinY = 2 assert device.BinY == 2 + device.BinY = 1 def test_camera_state(device, disconnect): @@ -104,7 +106,8 @@ def test_cooleron(device, disconnect): def test_coolerpower(device, disconnect): - assert device.CoolerPower is not None + if device.CanGetCoolerPower: + assert device.CoolerPower is not None def test_electronsperadu(device, disconnect): @@ -163,6 +166,7 @@ def test_lastexposureduration(device, disconnect): device.StartExposure(0.1, True) while not device.ImageReady: time.sleep(0.1) + assert device.ImageArray is not None assert device.LastExposureDuration is not None @@ -187,15 +191,17 @@ def test_maxbiny(device, disconnect): def test_numx(device, disconnect): - assert device.NumX is not None + old_numx = device.NumX device.NumX = 2 assert device.NumX == 2 + device.NumX = old_numx def test_numy(device, disconnect): - assert device.NumY is not None + old_numy = device.NumY device.NumY = 2 assert device.NumY == 2 + device.NumY = old_numy @pytest.mark.skip(reason="Not implemented") @@ -224,10 +230,8 @@ def test_pixelsizey(device, disconnect): def test_readoutmode(device, disconnect): assert device.ReadoutMode is not None - - -def test_readoutmodes(device, disconnect): - assert device.ReadoutModes is not None + device.ReadoutMode = 0 + assert device.ReadoutMode == 0 def test_sensorname(device, disconnect): diff --git a/tests/observatory/test_ascom_cover_calibrator.py b/tests/observatory/test_ascom_cover_calibrator.py index 62ff849e..2de69c59 100644 --- a/tests/observatory/test_ascom_cover_calibrator.py +++ b/tests/observatory/test_ascom_cover_calibrator.py @@ -4,6 +4,7 @@ def test_calibratoron(device, disconnect): device.CalibratorOn(1) assert device.Brightness == 1 + device.CalibratorOff() def test_calibratoroff(device, disconnect): diff --git a/tests/observatory/test_ascom_filter_wheel.py b/tests/observatory/test_ascom_filter_wheel.py index 577cadde..6498e29c 100644 --- a/tests/observatory/test_ascom_filter_wheel.py +++ b/tests/observatory/test_ascom_filter_wheel.py @@ -11,3 +11,5 @@ def test_names(device, disconnect): def test_position(device, disconnect): assert device.Position is not None + device.Position = 0 + assert device.Position == 0 diff --git a/tests/observatory/test_ascom_focuser.py b/tests/observatory/test_ascom_focuser.py index df0d1e9c..d4890872 100644 --- a/tests/observatory/test_ascom_focuser.py +++ b/tests/observatory/test_ascom_focuser.py @@ -4,15 +4,15 @@ def test_move(device, disconnect): - device.Move(50) + device.Move(20000) while device.IsMoving: time.sleep(0.1) if device.Absolute: - assert device.Position == 50 + assert device.Position == 20000 def test_halt(device, disconnect): - device.Move(50) + device.Move(20000) time.sleep(0.1) device.Halt() diff --git a/tests/observatory/test_ascom_observing_conditions.py b/tests/observatory/test_ascom_observing_conditions.py index 8da9ede6..1a69aa48 100644 --- a/tests/observatory/test_ascom_observing_conditions.py +++ b/tests/observatory/test_ascom_observing_conditions.py @@ -36,6 +36,9 @@ def test_timesincelastupdate(device, disconnect): def test_averageperiod(device, disconnect): assert device.AveragePeriod is not None + # device.AveragePeriod = 0.1 + # assert device.AveragePeriod == 0.1 + # alpyca implementation issue def test_cloudcover(device, disconnect): diff --git a/tests/observatory/test_ascom_rotator.py b/tests/observatory/test_ascom_rotator.py index 858436aa..8c8e8562 100644 --- a/tests/observatory/test_ascom_rotator.py +++ b/tests/observatory/test_ascom_rotator.py @@ -3,6 +3,11 @@ import pytest +def test_halt(device, disconnect): + device.Move(5) + device.Halt() + + def test_moveabsolute(device, disconnect): device.MoveAbsolute(5) while device.IsMoving: @@ -15,6 +20,14 @@ def test_movemechanical(device, disconnect): time.sleep(0.1) +def test_sync(device, disconnect): + device.Sync(5) + + +def test_position(device, disconnect): + assert device.Position is not None + + def test_reverse(device, disconnect): if device.CanReverse: device.Reverse = True diff --git a/tests/observatory/test_ascom_telescope.py b/tests/observatory/test_ascom_telescope.py index b62d1e5c..64899387 100644 --- a/tests/observatory/test_ascom_telescope.py +++ b/tests/observatory/test_ascom_telescope.py @@ -105,6 +105,13 @@ def test_synctoaltaz(device, disconnect): device.SyncToAltAz(45, 90) +def test_synctocoordinates(device, disconnect): + if device.CanSync: + if device.CanSetTracking: + device.Tracking = True + device.SyncToCoordinates(device.SiderealTime, device.SiteLatitude) + + def test_synctotarget(device, disconnect): if device.CanSync: device.TargetDeclination = device.SiteLatitude @@ -203,10 +210,14 @@ def test_sideofpier(device, disconnect): def test_siteeleveation(device, disconnect): assert device.SiteElevation is not None + device.SiteElevation = 100 + assert device.SiteElevation == 100 def test_sitelongitude(device, disconnect): assert device.SiteLongitude is not None + device.SiteLongitude = 0 + assert device.SiteLongitude == 0 def test_slewsettletime(device, disconnect): @@ -221,14 +232,15 @@ def test_tracking(device, disconnect): assert device.Tracking +@pytest.mark.skip(reason="Alpaca implementation issue") def test_trackingrate(device, disconnect): if device.CanSetTracking: assert device.TrackingRate is not None - - -def test_trackingrates(device, disconnect): - assert device.TrackingRates is not None + device.TrackingRate = device.TrackingRates[0] + assert device.TrackingRate == device.TrackingRates[0] def test_utcdate(device, disconnect): assert device.UTCDate is not None + device.UTCDate = "2020-01-01T00:00:00" + assert device.UTCDate.strftime("%Y-%m-%dT%H:%M:%S") == "2020-01-01T00:00:00" diff --git a/tests/observatory/test_html_observing_conditions.py b/tests/observatory/test_html_observing_conditions.py index e69de29b..59f59a1f 100644 --- a/tests/observatory/test_html_observing_conditions.py +++ b/tests/observatory/test_html_observing_conditions.py @@ -0,0 +1,67 @@ +import pytest + + +def test_sensordescription(winer_oc): + assert winer_oc.SensorDescription("rain_rate") == "RAIN" + + +def test_timesincelastupdate(winer_oc): + assert winer_oc.TimeSinceLastUpdate("rain_rate") is not None + + +def test_averageperiod(winer_oc): + assert winer_oc.AveragePeriod is None + winer_oc.AveragePeriod = 10 # Note for this driver this does nothing + assert winer_oc.AveragePeriod is None + + +def test_cloudcover(winer_oc): + winer_oc.CloudCover is None + + +def test_dewpoint(winer_oc): + winer_oc.DewPoint is None + + +def test_humidity(winer_oc): + winer_oc.Humidity is not None + + +def test_pressure(winer_oc): + winer_oc.Pressure is not None + + +def test_rainrate(winer_oc): + winer_oc.RainRate is not None + + +def test_skybrightness(winer_oc): + winer_oc.SkyBrightness is not None + + +def test_skyquality(winer_oc): + winer_oc.SkyQuality is None + + +def test_skytemperature(winer_oc): + winer_oc.SkyTemperature is None + + +def test_starfwhm(winer_oc): + winer_oc.StarFWHM is not None + + +def test_temperature(winer_oc): + winer_oc.Temperature is not None + + +def test_winddirection(winer_oc): + winer_oc.WindDirection is not None + + +def test_windgust(winer_oc): + winer_oc.WindGust is None + + +def test_windspeed(winer_oc): + winer_oc.WindSpeed is not None diff --git a/tests/observatory/test_html_safety_monitor.py b/tests/observatory/test_html_safety_monitor.py index e69de29b..ceb056ed 100644 --- a/tests/observatory/test_html_safety_monitor.py +++ b/tests/observatory/test_html_safety_monitor.py @@ -0,0 +1,5 @@ +import pytest + + +def test_issafe(winer_sm): + assert winer_sm.IsSafe is not None diff --git a/tests/observatory/test_ip_cover_calibrator.py b/tests/observatory/test_ip_cover_calibrator.py index e69de29b..7ad7b81c 100644 --- a/tests/observatory/test_ip_cover_calibrator.py +++ b/tests/observatory/test_ip_cover_calibrator.py @@ -0,0 +1,29 @@ +import platform + +import pytest + +from pyscope.observatory import IPCoverCalibrator + + +@pytest.mark.skipif(platform.node() != "TCC1-MACRO", reason="Only run on TCC1-MACRO") +def test_IPCoverCalibrator(): + c = IPCoverCalibrator("192.168.2.22", 2101, 1024) + + c.CalibratorOn(1) + c.CalibratorOff() + + with pytest.raises(NotImplementedError): + c.CloseCover() + with pytest.raises(NotImplementedError): + c.HaltCover() + with pytest.raises(NotImplementedError): + c.OpenCover() + + assert c.Brightness is None + assert c.CalibratorState is None + assert c.CoverState is None + assert c.MaxBrightness == 254 + + assert c.tcp_ip is not None + assert c.tcp_port is not None + assert c.buffer_size is not None diff --git a/tests/observatory/test_maxim.py b/tests/observatory/test_maxim.py index e69de29b..248f61e0 100644 --- a/tests/observatory/test_maxim.py +++ b/tests/observatory/test_maxim.py @@ -0,0 +1,208 @@ +import platform +import time + +import pytest + +from pyscope.observatory import Maxim + + +@pytest.mark.skipif(platform.system() != "Windows", reason="Maxim only runs on Windows") +def test_maxim(): + m = Maxim() + assert m.Connected + assert m.name == "MaxIm DL" + assert m.app is not None + assert m.autofocus is not None + m.Connected = False + + +@pytest.mark.skipif(platform.system() != "Windows", reason="Maxim only runs on Windows") +def test_maxim_autofocus(): + m = Maxim() + success = m.autofocus.Run(exposure=1) + assert success is not None + + with pytest.raises(NotImplementedError): + m.autofocus.Abort() + + m.Connected = False + + +@pytest.mark.skipif(platform.system() != "Windows", reason="Maxim only runs on Windows") +def test_maxim_camera(): + m = Maxim() + c = m.camera + c.Connected = True + assert c.Connected + assert c.Name is not None + + c.StartExposure(1, True) + time.sleep(0.5) + if c.CanStopExposure: + c.StopExposure() + + c.StartExposure(1, False) + time.sleep(0.5) + if c.CanAbortExposure: + c.AbortExposure() + + with pytest.raises(NotImplementedError): + c.PulseGuide(0, 0) + + with pytest.raises(NotImplementedError): + c.BayerOffsetX = 0 + + with pytest.raises(NotImplementedError): + c.BayerOffsetY = 0 + + c.BinX = 2 + assert c.BinX == 2 + if not c.CanAsymmetricBin: + assert c.BinY == 2 + else: + assert c.BinY == 1 + + with pytest.raises(NotImplementedError): + c.CanFastReadout + + assert c.CameraState is not None + + assert c.CameraXSize is not None + assert c.CameraYSize is not None + + assert c.CanGetCoolerPower + + assert not c.CanPulseGuide + + if c.CanSetCCDTemperature: + c.SetCCDTemperature = -20 + assert c.SetCCDTemperature == -20 + + assert c.CCDTemperature is not None + + c.CoolerOn = True + assert c.CoolerOn + assert c.CoolerPower is not None + c.CoolerOn = False + assert not c.CoolerOn + + with pytest.raises(NotImplementedError): + c.ElectronsPerADU + with pytest.raises(NotImplementedError): + c.ExposureMax + with pytest.raises(NotImplementedError): + c.ExposureMin + with pytest.raises(NotImplementedError): + c.ExposureResolution + + with pytest.raises(DeprecationWarning): + c.FastReadout + with pytest.raises(DeprecationWarning): + c.FastReadout = True + + with pytest.raises(NotImplementedError): + c.FullWellCapacity + + c.Gain = c.Gains[0] + assert c.Gain == c.Gains[0] + + with pytest.raises(NotImplementedError): + c.GainMax + with pytest.raises(NotImplementedError): + c.GainMin + + assert c.HasShutter is not None + assert c.HeatSinkTemperature is not None + + c.StartExposure(0.1, True) + while not c.ImageReady: + time.sleep(0.1) + assert c.ImageArray is not None + + with pytest.raises(NotImplementedError): + c.IsPulseGuiding + + assert c.LastExposureDuration is not None + assert c.LastExposureStartTime is not None + + with pytest.raises(NotImplementedError): + c.MaxADU + + assert c.MaxBinX is not None + assert c.MaxBinY is not None + + c.NumX = 100 + assert c.NumX == 100 + c.NumY = 100 + assert c.NumY == 100 + + with pytest.raises(NotImplementedError): + c.Offset + with pytest.raises(NotImplementedError): + c.Offset = 1 + with pytest.raises(NotImplementedError): + c.OffsetMax + with pytest.raises(NotImplementedError): + c.OffsetMin + with pytest.raises(NotImplementedError): + c.Offsets + with pytest.raises(NotImplementedError): + c.PercentCompleted + + assert c.PixelSizeX is not None + assert c.PixelSizeY is not None + + c.ReadoutMode = 0 + assert c.ReadoutMode == 0 + assert c.ReadoutModes is not None + + with pytest.raises(NotImplementedError): + c.SensorName + with pytest.raises(NotImplementedError): + c.SensorType + + c.StartX = 1 + assert c.StartX == 1 + c.StartY = 1 + assert c.StartY == 1 + + with pytest.raises(NotImplementedError): + c.SubExposureDuration + with pytest.raises(NotImplementedError): + c.SubExposureDuration = 1 + + m.Connected = False + + +@pytest.mark.skipif(platform.system() != "Windows", reason="Maxim only runs on Windows") +def test_maxim_filter_wheel(): + m = Maxim() + f = m.filter_wheel + f.Connected = True + assert f.Connected + + assert f.Name is not None + + with pytest.raises(NotImplementedError): + f.FocusOffsets + + assert f.Names is not None + assert f.Position is not None + f.Position = 1 + assert f.Position == 1 + + m.Connected = False + + +@pytest.mark.skipif(platform.system() != "Windows", reason="Maxim only runs on Windows") +def test_maxim_pinpoint(): + m = Maxim() + p = m.pinpoint + + m.Camera.StartExposure(1, True) + while not m.Camera.ImageReady: + time.sleep(0.1) + + assert p.Solve() is not None + + m.Connected = False diff --git a/tests/observatory/test_observatory.py b/tests/observatory/test_observatory.py index e69de29b..ace1a011 100644 --- a/tests/observatory/test_observatory.py +++ b/tests/observatory/test_observatory.py @@ -0,0 +1,79 @@ +import logging +import time + +import pytest + +from pyscope import logger +from pyscope.observatory import ASCOMCamera, Observatory + + +def test_observatory(tmp_path): + logger.setLevel("INFO") + logger.addHandler(logging.StreamHandler()) + + obs = Observatory(config_path="tests/reference/simulator_observatory.cfg") + obs.save_config("tests/reference/saved_observatory.cfg") + new_obs = Observatory(config_path="tests/reference/saved_observatory.cfg") + obs.connect_all() + obs.dome.CloseShutter() + obs.dome.OpenShutter() + + assert obs.lst() is not None + assert obs.sun_altaz() is not None + assert obs.moon_altaz() is not None + assert obs.moon_illumination() is not None + assert obs.get_object_altaz(obj="M1") is not None + assert obs.get_object_slew(obj="M1") is not None + + obs.start_observing_conditions_thread(update_interval=1) + # obs.start_safety_monitor_thread() + + obs.telescope.Unpark() + if obs.telescope.CanFindHome: + obs.telescope.FindHome() + assert obs.get_current_object() is not None + + obs.slew_to_coordinates(ra=obs.lst(), dec=45) + obs.run_autofocus(midpoint=20000, exposure=1, use_current_pointing=True) + + old_position = obs.focuser.Position + obs.set_filter_offset_focuser(filter_index=5) + assert obs.filter_wheel.Position == 5 + assert obs.focuser.Position != old_position + obs.start_derotation_thread() + obs.camera.StartExposure(0.1, True) + while not obs.camera.ImageReady: + time.sleep(0.1) + obs.save_last_image( + str(tmp_path) + "last_image.fts", frametyp="light", do_fwhm=True, overwrite=True + ) + obs.stop_derotation_thread() + + assert obs.safety_status() is not None + assert obs.switch_status() is not None + + obs.take_flats( + filter_exposure=6 * [0.1], + filter_brightness=6 * [1], + readouts=[0], + repeat=1, + save_path=str(tmp_path), + ) + + obs.take_darks( + exposures=[0.1, 0.2], + readouts=[0], + binnings=["1x1"], + repeat=1, + save_path=str(tmp_path), + ) + + # obs.stop_safety_monitor_thread() + obs.stop_observing_conditions_thread() + obs.telescope.Park() + obs.shutdown() + obs.disconnect_all() + + +"""if __name__ == "__main__": + test_observatory(tmp_path='tests/reference/')""" diff --git a/tests/observatory/test_pwi_autofocus.py b/tests/observatory/test_pwi_autofocus.py index e69de29b..98b03503 100644 --- a/tests/observatory/test_pwi_autofocus.py +++ b/tests/observatory/test_pwi_autofocus.py @@ -0,0 +1,12 @@ +import platform + +import pytest + +from pyscope.observatory import PWIAutofocus + + +@pytest.mark.skipif(platform.node() != "TCC1-MACRO", reason="Only works on TCC1-MACRO") +def test_pwi_autofocus(): + a = PWIAutofocus() + + best_position = a.Run(5, 120) diff --git a/tests/reference/saved_observatory.cfg b/tests/reference/saved_observatory.cfg new file mode 100644 index 00000000..ed1516b2 --- /dev/null +++ b/tests/reference/saved_observatory.cfg @@ -0,0 +1,76 @@ +[site] +site_name = Simulator Site +instrument_name = Simulator Telecope +instrument_description = ASCOM Alpaca Simulator Server +latitude = 42d22m53.41000s +longitude = -71d07m42.60000s +elevation = 20.0 +diameter = 0.508 +focal_length = 3.454 + +[camera] +camera_driver = ASCOMCamera +camera_kwargs = identifier='localhost:32323',alpaca=True +cooler_setpoint = -20 +cooler_tolerance = 1.0 +max_dimension = + +[cover_calibrator] +cover_calibrator_driver = ASCOMCoverCalibrator +cover_calibrator_kwargs = identifier='localhost:32323',alpaca=True +cover_calibrator_alt = 45.0 +cover_calibrator_az = 180.0 + +[dome] +dome_driver = ASCOMDome +dome_kwargs = identifier='localhost:32323',alpaca=True + +[filter_wheel] +filter_wheel_driver = ASCOMFilterWheel +filter_wheel_kwargs = identifier='localhost:32323',alpaca=True +filters = R, G, B, C, H, O +filter_focus_offsets = 0,0,0,0,1000,-1000 + +[focuser] +focuser_driver = ASCOMFocuser +focuser_kwargs = identifier='localhost:32323',alpaca=True + +[observing_conditions] +observing_conditions_driver = ASCOMObservingConditions +observing_conditions_kwargs = identifier='localhost:32323',alpaca=True + +[rotator] +rotator_driver = ASCOMRotator +rotator_kwargs = identifier='localhost:32323',alpaca=True +rotator_reverse = True +rotator_min_angle = 0.0 +rotator_max_angle = 360.0 + +[safety_monitor] +driver_0 = ASCOMSafetyMonitor,identifier='localhost:32323',alpaca=True +driver_1 = +driver_2 = + +[switch] +driver_0 = ASCOMSwitch,identifier='localhost:32323',alpaca=True +driver_1 = +driver_2 = + +[telescope] +telescope_driver = ASCOMTelescope +telescope_kwargs = identifier='localhost:32323',alpaca=True +min_altitude = 10.0 +settle_time = 5.0 + +[autofocus] +autofocus_driver = +autofocus_kwargs = + +[wcs] +driver_0 = AstrometryNetWCS +driver_1 = +driver_2 = + +[scheduling] +slew_rate = 2.0 +instrument_reconfiguration_times = {} diff --git a/tests/reference/simulator_observatory.cfg b/tests/reference/simulator_observatory.cfg new file mode 100644 index 00000000..4beeed95 --- /dev/null +++ b/tests/reference/simulator_observatory.cfg @@ -0,0 +1,147 @@ +[site] + +site_name = Simulator Site + +instrument_name = Simulator Telecope + +instrument_description = ASCOM Alpaca Simulator Server + +latitude = 42d22m53.41s + +longitude = -71d07m42.60s + +# meters +elevation = 20 + +# meters +diameter = 0.508 + +# meters +focal_length = 3.454 + + +[camera] + +camera_driver = ASCOMCamera + +camera_kwargs = identifier='localhost:32323', alpaca=True + +# celsius +cooler_setpoint = -20 + +# celsius +cooler_tolerance = 1 + +# pixels +max_dimension = + + +[cover_calibrator] + +cover_calibrator_driver = ASCOMCoverCalibrator + +cover_calibrator_kwargs = identifier='localhost:32323', alpaca=True + +cover_calibrator_alt = 45 + +cover_calibrator_az = 180 + + +[dome] + +dome_driver = ASCOMDome + +dome_kwargs = identifier='localhost:32323', alpaca=True + + +[filter_wheel] + +filter_wheel_driver = ASCOMFilterWheel + +filter_wheel_kwargs = identifier='localhost:32323', alpaca=True + +filters = R, G, B, C, H, O + +# comma-separated list of focus offsets (in counts) for each filter +filter_focus_offsets = 0, 0, 0, 0, 1000, -1000 + + +[focuser] + +focuser_driver = ASCOMFocuser + +focuser_kwargs = identifier='localhost:32323', alpaca=True + + +[observing_conditions] + +observing_conditions_driver = ASCOMObservingConditions + +observing_conditions_kwargs = identifier='localhost:32323', alpaca=True + + +[rotator] + +rotator_driver = ASCOMRotator + +rotator_kwargs = identifier='localhost:32323', alpaca=True + +rotator_reverse = False + +rotator_min_angle = 0 + +rotator_max_angle = 360 + + +[safety_monitor] + +driver_0 = ASCOMSafetyMonitor, identifier='localhost:32323', alpaca=True + +driver_1 = + +driver_2 = + + +[switch] + +driver_0 = ASCOMSwitch, identifier='localhost:32323', alpaca=True + +driver_1 = + +driver_2 = + + +[telescope] + +telescope_driver = ASCOMTelescope + +telescope_kwargs = identifier='localhost:32323', alpaca=True + +# degrees +min_altitude = 10 + +# seconds +settle_time = 5 + + +[autofocus] + +autofocus_driver = + +autofocus_kwargs = + + +[wcs] + +driver_0 = AstrometryNetWCS + +driver_1 = + +driver_2 = + +[scheduling] + +# degrees/second +slew_rate = 2 + +instrument_reconfiguration_times = diff --git a/tests/utils/test__args_kwargs_config.py b/tests/utils/test__args_kwargs_config.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/utils/test__function_synchronicity.py b/tests/utils/test__function_synchronicity.py index e69de29b..16eee04a 100644 --- a/tests/utils/test__function_synchronicity.py +++ b/tests/utils/test__function_synchronicity.py @@ -0,0 +1,32 @@ +import asyncio + +import pytest + +from pyscope.utils import _force_async, _force_sync + + +def test__forceaync(): + @_force_async + def t(): + import time + + time.sleep(1) + return True + + @_force_sync + async def main(): + import asyncio + + # if it were to execute sequentially, it would take 10 seconds, in this case we expect to see only 1 second + futures = list(map(lambda x: t(), range(10))) + return await asyncio.gather(*futures) + + assert main() == [True] * 10 + + +def test__forcesync(): + @_force_sync + async def fn(): + return 1 + + assert fn() == 1 diff --git a/tests/utils/test__get_image_source_catalog.py b/tests/utils/test__get_image_source_catalog.py deleted file mode 100644 index e69de29b..00000000