├── migrations
├── versions
│ └── .empty
├── README
├── script.py.mako
└── alembic.ini
├── .htaccess
├── indi_allsky
├── devices
│ ├── __init__.py
│ ├── controllers
│ │ ├── __init__.py
│ │ └── dockerpi.py
│ ├── fans
│ │ ├── fanBase.py
│ │ ├── fanSimulator.py
│ │ ├── __init__.py
│ │ ├── fanStandard.py
│ │ ├── fanPwm.py
│ │ ├── fanSerialPwm.py
│ │ ├── fanDockerPi4ChannelRelay.py
│ │ └── fanMotorKit.py
│ ├── generic
│ │ ├── genericBase.py
│ │ ├── __init__.py
│ │ ├── gpioSimulator.py
│ │ ├── gpioRpiGpio.py
│ │ ├── gpioStandard.py
│ │ └── gpioDockerPi4ChannelRelay.py
│ ├── dew_heaters
│ │ ├── dewHeaterBase.py
│ │ ├── dewHeaterSimulator.py
│ │ ├── __init__.py
│ │ ├── dewHeaterStandard.py
│ │ ├── dewHeaterPwm.py
│ │ ├── dewHeaterSerialPwm.py
│ │ ├── dewHeaterDockerPi4ChannelRelay.py
│ │ └── dewHeaterMotorKit.py
│ ├── exceptions.py
│ ├── focusers
│ │ ├── focuserBase.py
│ │ ├── focuserSimulator.py
│ │ ├── __init__.py
│ │ └── focuserSerial28byj.py
│ └── sensors
│ │ ├── adafruit_mlx90615.py
│ │ ├── lightSensorBh1750.py
│ │ ├── tempSensorDs18x20.py
│ │ └── tempSensorMlx90614.py
├── version.py
├── __init__.py
├── fonts
│ ├── hack
│ │ ├── Hack-Bold.ttf
│ │ ├── Hack-Italic.ttf
│ │ ├── Hack-Regular.ttf
│ │ └── Hack-BoldItalic.ttf
│ ├── fonts-freefont-ttf
│ │ ├── FreeMono.ttf
│ │ ├── FreeSans.ttf
│ │ ├── FreeSerif.ttf
│ │ ├── FreeMonoBold.ttf
│ │ ├── FreeSansBold.ttf
│ │ ├── FreeSerifBold.ttf
│ │ ├── FreeMonoOblique.ttf
│ │ ├── FreeSansOblique.ttf
│ │ ├── FreeSerifItalic.ttf
│ │ ├── FreeMonoBoldOblique.ttf
│ │ ├── FreeSansBoldOblique.ttf
│ │ └── FreeSerifBoldItalic.ttf
│ ├── liberation2
│ │ ├── LiberationMono-Bold.ttf
│ │ ├── LiberationSans-Bold.ttf
│ │ ├── LiberationSerif-Bold.ttf
│ │ ├── LiberationMono-Italic.ttf
│ │ ├── LiberationMono-Regular.ttf
│ │ ├── LiberationSans-Italic.ttf
│ │ ├── LiberationSans-Regular.ttf
│ │ ├── LiberationSerif-Italic.ttf
│ │ ├── LiberationMono-BoldItalic.ttf
│ │ ├── LiberationSans-BoldItalic.ttf
│ │ ├── LiberationSerif-Regular.ttf
│ │ └── LiberationSerif-BoldItalic.ttf
│ └── intel-one-mono
│ │ ├── intelone-mono-font-family-bold.ttf
│ │ ├── intelone-mono-font-family-italic.ttf
│ │ ├── intelone-mono-font-family-light.ttf
│ │ ├── intelone-mono-font-family-medium.ttf
│ │ ├── intelone-mono-font-family-regular.ttf
│ │ ├── intelone-mono-font-family-bolditalic.ttf
│ │ ├── intelone-mono-font-family-lightitalic.ttf
│ │ └── intelone-mono-font-family-mediumitalic.ttf
├── flask
│ ├── static
│ │ ├── images
│ │ │ ├── favicon_32.png
│ │ │ ├── favicon_128.png
│ │ │ └── logo_outline_full.png
│ │ ├── astropanel
│ │ │ └── img
│ │ │ │ ├── moon.png
│ │ │ │ ├── sun.png
│ │ │ │ ├── polaris.png
│ │ │ │ ├── reticle.png
│ │ │ │ ├── moon_rot.png
│ │ │ │ ├── solar_system.png
│ │ │ │ ├── glyphicons-halflings.png
│ │ │ │ └── glyphicons-halflings-white.png
│ │ ├── css
│ │ │ └── style.css
│ │ ├── svg
│ │ │ ├── circle.svg
│ │ │ ├── play-fill.svg
│ │ │ ├── play-btn-fill.svg
│ │ │ ├── lightning-charge-fill.svg
│ │ │ ├── subtract.svg
│ │ │ ├── film.svg
│ │ │ ├── graph-up.svg
│ │ │ ├── terminal-fill.svg
│ │ │ ├── layout-three-columns.svg
│ │ │ ├── image-fill.svg
│ │ │ ├── distribute-horizontal.svg
│ │ │ ├── hdd-fill.svg
│ │ │ ├── input-cursor.svg
│ │ │ ├── toggles.svg
│ │ │ ├── house-fill.svg
│ │ │ ├── layers.svg
│ │ │ ├── info-circle.svg
│ │ │ ├── info-square-fill.svg
│ │ │ ├── camera-fill.svg
│ │ │ ├── aspect-ratio.svg
│ │ │ ├── repeat.svg
│ │ │ ├── stopwatch.svg
│ │ │ ├── cloud-moon-fill.svg
│ │ │ ├── wrench.svg
│ │ │ ├── grid-3x2-gap-fill.svg
│ │ │ ├── newspaper.svg
│ │ │ ├── magic.svg
│ │ │ ├── gear-fill.svg
│ │ │ ├── journal-medical.svg
│ │ │ ├── wifi.svg
│ │ │ ├── tools.svg
│ │ │ ├── stars.svg
│ │ │ └── plus-square-dotted.svg
│ │ └── virtualsky
│ │ │ └── extra
│ │ │ └── highlight.css
│ ├── templates
│ │ ├── cameras.html
│ │ ├── mask.html
│ │ ├── taskqueue.html
│ │ ├── users.html
│ │ ├── notifications.html
│ │ ├── watch_video.html
│ │ ├── lag.html
│ │ ├── adu.html
│ │ └── config_list.html
│ └── misc.py
├── timelapse_preprocessor
│ ├── __init__.py
│ ├── preProcessorStandard.py
│ └── preProcessorBase.py
├── stretch
│ ├── stretchBase.py
│ └── __init__.py
├── filetransfer
│ ├── exceptions.py
│ ├── __init__.py
│ └── generic.py
├── wsgi.py
├── exceptions.py
└── focuser.py
├── html
├── .htaccess
└── images
│ ├── .htaccess
│ ├── darks
│ └── .htaccess
│ └── export
│ └── .htaccess
├── log
├── .htaccess
├── rsyslog_indi-allsky.conf
└── logrotate_indi-allsky
├── examples
├── .htaccess
├── telegraf
│ └── indi_file.conf
└── example.php
├── requirements
├── requirements_empty.txt
├── requirements_latest_ai.txt
├── requirements_latest_post_32.txt
├── requirements_optional.txt
├── requirements_latest_web.txt
├── requirements_debian11_web.txt
├── requirements_gpio.txt
├── requirements_latest_armv6l.txt
├── requirements_latest_32.txt
├── requirements_debian11.txt
├── requirements_latest.txt
└── requirements_debian10.txt
├── licenses
├── fish2pano
│ └── LICENSE
├── chart.js
│ └── LICENSE.md
├── clipboard.js
│ └── LICENSE
├── bootstrap
│ └── LICENSE
├── jquery
│ └── LICENSE.txt
├── DataTables
│ └── license.txt
└── photoswipe
│ └── LICENSE
├── content
├── webui_home.png
├── webui_images.png
├── webui_chart01.png
├── webui_chart02.png
├── 20210421_043940.jpg
├── 20210930_224951.jpg
├── 20210930_224955.jpg
├── keogram_example.jpg
├── webui_systeminfo.png
├── startrails_example.jpg
├── screenshot_mqtt_dash.jpg
└── webui_timelapse_mono.png
├── passenger_wsgi.py
├── testing
├── README.md
├── blob_detection
│ ├── test_moon.jpg
│ ├── test_no_clouds.jpg
│ ├── test_full_clouds.jpg
│ ├── test_transparent_clouds.jpg
│ └── test_transparent_clouds_plane.jpg
├── net
│ ├── sftp.py
│ ├── ftp.py
│ └── openweathermap.py
├── indi
│ └── indi_dbus_kstars_setup.sh
├── gpio
│ ├── dht11_22_blinka.py
│ └── 28byj_step_blinka.py
├── image
│ └── fits_headers.py
├── benchmark
│ ├── shift_bench.py
│ └── sat_tle_bench.py
├── json_test.py
├── nm
│ └── nm-settings.py
└── astrometrics
│ ├── sun_moon_sep.py
│ └── transit_longitude_test.py
├── service
├── sudoers_indi-allsky
├── indi-allsky.env
├── gunicorn-indi-allsky.socket
├── udiskie-automount.service
├── indi-allsky.timer
├── indiserver.timer
├── upgrade-indi-allsky.service
├── mysql_indi-allsky.conf
├── indi-allsky.service
├── gunicorn.conf.py
├── gunicorn-indi-allsky.service
├── indiserver.service
├── 90-org.aaronwmorris.indi-allsky.pkla
├── cgi_indi-allsky.py
└── 90-indi-allsky.rules
├── misc
├── README.md
├── camera_change.sh
├── perf_indexes_sqlite.sql
├── mosquitto_indi-allsky.conf
├── mysql_optimize.sh
├── example_ccd_temp.sh
├── example_ccd_temp.py
├── oci_config_test.py
├── example_pre_cature_hook.py
├── add_indi_allsky_pth.py
├── example_image_postsave_hook.py
├── backup_database.py
└── image_folder_perms.py
├── app.py
├── docker
├── README.md
├── Dockerfile.mariadb
├── homeassistant_docker-compose.yaml
├── Dockerfile.mosquitto
├── mosquitto.conf
├── start_indiserver.sh
├── Dockerfile.indiserver_ubuntu2404
├── Dockerfile.indiserver_debian12
├── upgrade.sh
├── Dockerfile.webserver_nginx
├── Dockerfile.webserver_apache
├── Dockerfile.indi_base_ubuntu2404
├── nginx.local.conf
├── Dockerfile.indi_base_debian12
└── env_template
├── .dockerignore
├── .gitignore
├── flask.json_template
└── config.py
/migrations/versions/.empty:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.htaccess:
--------------------------------------------------------------------------------
1 | Require all denied
2 |
--------------------------------------------------------------------------------
/indi_allsky/devices/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/html/.htaccess:
--------------------------------------------------------------------------------
1 | Require all granted
2 |
--------------------------------------------------------------------------------
/log/.htaccess:
--------------------------------------------------------------------------------
1 | Require all granted
2 |
--------------------------------------------------------------------------------
/examples/.htaccess:
--------------------------------------------------------------------------------
1 | Require all denied
2 |
--------------------------------------------------------------------------------
/indi_allsky/devices/controllers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/html/images/.htaccess:
--------------------------------------------------------------------------------
1 | Require all granted
2 |
--------------------------------------------------------------------------------
/html/images/darks/.htaccess:
--------------------------------------------------------------------------------
1 | Require all granted
2 |
--------------------------------------------------------------------------------
/html/images/export/.htaccess:
--------------------------------------------------------------------------------
1 | Require all granted
2 |
--------------------------------------------------------------------------------
/migrations/README:
--------------------------------------------------------------------------------
1 | Single-database configuration for Flask.
2 |
--------------------------------------------------------------------------------
/requirements/requirements_empty.txt:
--------------------------------------------------------------------------------
1 | # file is empty on purpose
2 |
--------------------------------------------------------------------------------
/indi_allsky/version.py:
--------------------------------------------------------------------------------
1 | __version__ = "indi_v2025.10.1"
2 | __config_level__ = "20251028.0"
3 |
--------------------------------------------------------------------------------
/licenses/fish2pano/LICENSE:
--------------------------------------------------------------------------------
1 | CC0-1.0
2 | https://creativecommons.org/publicdomain/zero/1.0/deed.en
--------------------------------------------------------------------------------
/requirements/requirements_latest_ai.txt:
--------------------------------------------------------------------------------
1 | keras
2 | tensorflow < 2.16
3 | typing-extensions >= 4.6.0
4 |
--------------------------------------------------------------------------------
/content/webui_home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_home.png
--------------------------------------------------------------------------------
/content/webui_images.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_images.png
--------------------------------------------------------------------------------
/content/webui_chart01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_chart01.png
--------------------------------------------------------------------------------
/content/webui_chart02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_chart02.png
--------------------------------------------------------------------------------
/content/20210421_043940.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/20210421_043940.jpg
--------------------------------------------------------------------------------
/content/20210930_224951.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/20210930_224951.jpg
--------------------------------------------------------------------------------
/content/20210930_224955.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/20210930_224955.jpg
--------------------------------------------------------------------------------
/content/keogram_example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/keogram_example.jpg
--------------------------------------------------------------------------------
/content/webui_systeminfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_systeminfo.png
--------------------------------------------------------------------------------
/content/startrails_example.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/startrails_example.jpg
--------------------------------------------------------------------------------
/content/screenshot_mqtt_dash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/screenshot_mqtt_dash.jpg
--------------------------------------------------------------------------------
/content/webui_timelapse_mono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/content/webui_timelapse_mono.png
--------------------------------------------------------------------------------
/indi_allsky/__init__.py:
--------------------------------------------------------------------------------
1 | from .version import __version__ # noqa: F401
2 |
3 | __all__ = [
4 | '__version__',
5 | ]
6 |
--------------------------------------------------------------------------------
/passenger_wsgi.py:
--------------------------------------------------------------------------------
1 | # Phusion Passenger interface
2 | from indi_allsky.flask import create_app
3 | application = create_app()
4 |
--------------------------------------------------------------------------------
/testing/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | The scripts here are testing functionality and technologies found in indi-allsky.
4 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/fonts/hack/Hack-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/hack/Hack-Bold.ttf
--------------------------------------------------------------------------------
/testing/blob_detection/test_moon.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/testing/blob_detection/test_moon.jpg
--------------------------------------------------------------------------------
/indi_allsky/fonts/hack/Hack-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/hack/Hack-Italic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/hack/Hack-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/hack/Hack-Regular.ttf
--------------------------------------------------------------------------------
/testing/blob_detection/test_no_clouds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/testing/blob_detection/test_no_clouds.jpg
--------------------------------------------------------------------------------
/indi_allsky/fonts/hack/Hack-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/hack/Hack-BoldItalic.ttf
--------------------------------------------------------------------------------
/requirements/requirements_latest_post_32.txt:
--------------------------------------------------------------------------------
1 | #rawpy # not available on armv7l
2 | git+https://github.com/letmaik/rawpy.git@v0.24.0#egg=rawpy
3 |
--------------------------------------------------------------------------------
/testing/blob_detection/test_full_clouds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/testing/blob_detection/test_full_clouds.jpg
--------------------------------------------------------------------------------
/indi_allsky/flask/static/images/favicon_32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/images/favicon_32.png
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/moon.png
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/sun.png
--------------------------------------------------------------------------------
/indi_allsky/flask/static/images/favicon_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/images/favicon_128.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeMono.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSans.ttf
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/polaris.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/polaris.png
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/reticle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/reticle.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSerif.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSerif.ttf
--------------------------------------------------------------------------------
/testing/blob_detection/test_transparent_clouds.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/testing/blob_detection/test_transparent_clouds.jpg
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/moon_rot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/moon_rot.png
--------------------------------------------------------------------------------
/indi_allsky/flask/static/images/logo_outline_full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/images/logo_outline_full.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoBold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSansBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSansBold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifBold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationMono-Bold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSans-Bold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSerif-Bold.ttf
--------------------------------------------------------------------------------
/log/rsyslog_indi-allsky.conf:
--------------------------------------------------------------------------------
1 | local6.* -/var/log/indi-allsky/indi-allsky.log
2 | & stop
3 |
4 | local7.* -/var/log/indi-allsky/webapp-indi-allsky.log
5 | & stop
6 |
--------------------------------------------------------------------------------
/service/sudoers_indi-allsky:
--------------------------------------------------------------------------------
1 | # commands necessary for unattended upgrade
2 | %ALLSKY_USER% ALL=(root) NOPASSWD: /usr/bin/apt-get update,/usr/bin/apt-get -y install *
3 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/solar_system.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/solar_system.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoOblique.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSansOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSansOblique.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifItalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationMono-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationMono-Italic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationMono-Regular.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSans-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSans-Italic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSans-Regular.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSerif-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSerif-Italic.ttf
--------------------------------------------------------------------------------
/misc/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | Information about these scripts may be found in the wiki.
4 |
5 | https://github.com/aaronwmorris/indi-allsky/wiki/Misc-Folder
6 |
7 |
--------------------------------------------------------------------------------
/testing/blob_detection/test_transparent_clouds_plane.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/testing/blob_detection/test_transparent_clouds_plane.jpg
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationMono-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationMono-BoldItalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSans-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSans-BoldItalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSerif-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSerif-Regular.ttf
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | ### This file sets up the flask environment for the flask cli tool
2 |
3 | from indi_allsky.flask import create_app
4 | app = create_app()
5 | app.app_context().push()
6 |
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoBoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeMonoBoldOblique.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSansBoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSansBoldOblique.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/fonts-freefont-ttf/FreeSerifBoldItalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/liberation2/LiberationSerif-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/liberation2/LiberationSerif-BoldItalic.ttf
--------------------------------------------------------------------------------
/docker/README.md:
--------------------------------------------------------------------------------
1 | # indi-allsky Docker support
2 |
3 | More detailed information can be found in the wiki.
4 |
5 | https://github.com/aaronwmorris/indi-allsky/wiki/Docker
6 |
7 |
8 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/glyphicons-halflings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/glyphicons-halflings.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-bold.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-italic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-light.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-medium.ttf
--------------------------------------------------------------------------------
/indi_allsky/flask/static/astropanel/img/glyphicons-halflings-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/flask/static/astropanel/img/glyphicons-halflings-white.png
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-regular.ttf
--------------------------------------------------------------------------------
/log/logrotate_indi-allsky:
--------------------------------------------------------------------------------
1 | /var/log/indi-allsky/*.log
2 | {
3 | rotate 30
4 | daily
5 | missingok
6 | dateext
7 | copytruncate
8 | notifempty
9 | compress
10 | }
11 |
--------------------------------------------------------------------------------
/service/indi-allsky.env:
--------------------------------------------------------------------------------
1 | ### Override temporary directory
2 | #TMPDIR=/path/to/tmp
3 |
4 | ### Override locales
5 | #LC_ALL=de_DE.UTF-8
6 | ### or
7 | #LC_TIME=fr_FR.UTF-8
8 | #LC_NUMERIC=fr_FR.UTF-8
9 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/css/style.css:
--------------------------------------------------------------------------------
1 | /* this is needed to make the content scrollable on larger screens */
2 | @media (min-width: 576px) {
3 | .h-sm-100 {
4 | height: 100%;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-bolditalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-bolditalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-lightitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-lightitalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-mediumitalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aaronwmorris/indi-allsky/HEAD/indi_allsky/fonts/intel-one-mono/intelone-mono-font-family-mediumitalic.ttf
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanBase.py:
--------------------------------------------------------------------------------
1 |
2 | class FanBase(object):
3 | def __init__(self, *args, **kwargs):
4 | self.config = args[0]
5 |
6 |
7 | def deinit(self):
8 | pass
9 |
10 |
--------------------------------------------------------------------------------
/docker/Dockerfile.mariadb:
--------------------------------------------------------------------------------
1 |
2 | FROM mariadb:latest
3 |
4 | ARG DEBIAN_FRONTEND=noninteractive
5 |
6 | USER root
7 | RUN apt-get update
8 | RUN apt-get -y upgrade
9 |
10 |
11 | # cleanup
12 | RUN apt-get clean
13 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/genericBase.py:
--------------------------------------------------------------------------------
1 |
2 | class GenericBase(object):
3 | def __init__(self, *args, **kwargs):
4 | self.config = args[0]
5 |
6 |
7 | def deinit(self):
8 | pass
9 |
10 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterBase.py:
--------------------------------------------------------------------------------
1 |
2 | class DewHeaterBase(object):
3 | def __init__(self, *args, **kwargs):
4 | self.config = args[0]
5 |
6 |
7 | def deinit(self):
8 | pass
9 |
10 |
--------------------------------------------------------------------------------
/indi_allsky/devices/exceptions.py:
--------------------------------------------------------------------------------
1 |
2 | class SensorException(Exception):
3 | pass
4 |
5 |
6 | class SensorReadException(Exception):
7 | pass
8 |
9 |
10 | class DeviceControlException(Exception):
11 | pass
12 |
13 |
--------------------------------------------------------------------------------
/service/gunicorn-indi-allsky.socket:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=indi-allsky gunicorn socket
3 |
4 | [Socket]
5 | ListenStream=%DB_FOLDER%/%GUNICORN_SERVICE_NAME%.sock
6 | SocketMode=666
7 |
8 | [Install]
9 | WantedBy=sockets.target
10 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/requirements/requirements_optional.txt:
--------------------------------------------------------------------------------
1 | # Optional modules
2 | PyMySQL
3 | boto3
4 | apache-libcloud
5 | google-cloud-storage
6 | google-api-python-client
7 | google_auth_oauthlib
8 | ### OCI has old cryptography and pyopenssl requirements
9 | #oci
10 |
--------------------------------------------------------------------------------
/examples/telegraf/indi_file.conf:
--------------------------------------------------------------------------------
1 | [[inputs.file]]
2 | files = ["/var/lib/indi-allsky/indi_allsky_status.json"]
3 | data_format = "json"
4 | json_name_key = "name"
5 | tag_keys = ["class", "device"]
6 | json_time_key = "time"
7 | json_time_format = "unix"
8 |
--------------------------------------------------------------------------------
/indi_allsky/timelapse_preprocessor/__init__.py:
--------------------------------------------------------------------------------
1 | from .preProcessorStandard import PreProcessorStandard as standard
2 | from .preProcessorWrapKeogram import PreProcessorWrapKeogram as wrap_keogram
3 |
4 | __all__ = (
5 | 'standard',
6 | 'wrap_keogram',
7 | )
8 |
--------------------------------------------------------------------------------
/service/udiskie-automount.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Udiskie automount daemon
3 |
4 | [Service]
5 | ExecStart=/usr/bin/udiskie --verbose --automount --no-config --no-notify --no-tray
6 | RestartSec=5
7 | UMask=0022
8 |
9 | [Install]
10 | WantedBy=default.target
11 |
--------------------------------------------------------------------------------
/indi_allsky/stretch/stretchBase.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logger = logging.getLogger('indi_allsky')
4 |
5 |
6 | class IndiAllSky_Stretch_Base(object):
7 | def __init__(self, *args, **kwargs):
8 | self.config = args[0]
9 | self.bin_v = args[1]
10 |
11 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/play-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/*.py[oc]
2 | **/__pycache__
3 | **/.git
4 | **/.env
5 | virtualenv
6 | #virtualenv/**/*
7 | log
8 | #log/**/*
9 | Docker/Dockerfile*
10 | .dockerignore
11 | .git/
12 | .gitignore
13 | .github/
14 | .htaccess
15 | README.md
16 | content/
17 | examples/
18 | log/
19 | service/
20 | testing/
21 | setup.sh
22 |
--------------------------------------------------------------------------------
/service/indi-allsky.timer:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Timer for the indi-allsky service
3 |
4 | [Timer]
5 | ### Start 2 minutes after boot
6 | OnBootSec=2min
7 |
8 | ### Start once an hour
9 | #OnUnitActiveSec=1h
10 |
11 | ### Start daily at noon
12 | #OnCalendar=*-*-* 12:00:00
13 |
14 | [Install]
15 | WantedBy=timers.target
16 |
--------------------------------------------------------------------------------
/service/indiserver.timer:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Timer for the indiserver service
3 |
4 | [Timer]
5 | ### Start 30 seconds after boot
6 | OnBootSec=30sec
7 |
8 | ### Start once an hour
9 | #OnUnitActiveSec=1h
10 |
11 | ### Start daily at noon
12 | #OnCalendar=*-*-* 12:00:00
13 |
14 | [Install]
15 | WantedBy=timers.target
16 |
--------------------------------------------------------------------------------
/testing/net/sftp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import paramiko
4 |
5 | ssh = paramiko.SSHClient()
6 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
7 |
8 | ssh.connect("localhost", port=22, username="username", password="password", timeout=5.0)
9 |
10 | sftp = ssh.open_sftp()
11 |
12 | sftp.mkdir('foobar')
13 |
--------------------------------------------------------------------------------
/indi_allsky/devices/focusers/focuserBase.py:
--------------------------------------------------------------------------------
1 |
2 | class FocuserBase(object):
3 | def __init__(self, *args, **kwargs):
4 | self.config = args[0]
5 |
6 |
7 | def deinit(self):
8 | pass
9 |
10 |
11 | def move(self, *args):
12 | # override in child class
13 | raise Exception('Not Implemented')
14 |
15 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/play-btn-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/lightning-charge-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/subtract.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/filetransfer/exceptions.py:
--------------------------------------------------------------------------------
1 | class AuthenticationFailure(Exception):
2 | pass
3 |
4 |
5 | class ConnectionFailure(Exception):
6 | pass
7 |
8 |
9 | class TransferFailure(Exception):
10 | pass
11 |
12 |
13 | class PermissionFailure(Exception):
14 | pass
15 |
16 |
17 | class CertificateValidationFailure(Exception):
18 | pass
19 |
--------------------------------------------------------------------------------
/indi_allsky/stretch/__init__.py:
--------------------------------------------------------------------------------
1 | from .mode1_stddev_cutoff import IndiAllSky_Mode1_Stretch as mode1_stddev_cutoff
2 | from .mode2_mtf import IndiAllSky_Mode2_MTF_Stretch as mode2_mtf
3 | from .mode2_mtf import IndiAllSky_Mode2_MTF_Stretch_x2 as mode2_mtf_x2
4 |
5 |
6 | __all__ = (
7 | 'mode1_stddev_cutoff',
8 | 'mode2_mtf',
9 | 'mode2_mtf_x2',
10 | )
11 |
--------------------------------------------------------------------------------
/service/upgrade-indi-allsky.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Indi AllSky Unattended Upgrade
3 |
4 | [Service]
5 | EnvironmentFile=%ALLSKY_ETC%/indi-allsky.env
6 | WorkingDirectory=%ALLSKY_DIRECTORY%
7 | ExecStart=%ALLSKY_DIRECTORY%/misc/unattended_upgrade.sh
8 | RemainAfterExit=true
9 | Type=oneshot
10 | UMask=0022
11 |
12 | [Install]
13 | WantedBy=default.target
14 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/film.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/misc/camera_change.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo
4 | echo
5 | echo "NOTICE: Script has been renamed"
6 | echo
7 | echo "Running indiserver_only_setup.sh"
8 | echo
9 | sleep 5
10 |
11 |
12 | SCRIPT_DIR=$(dirname "$0")
13 | cd "$SCRIPT_DIR/.." || catch_error
14 | ALLSKY_DIRECTORY=$PWD
15 | cd "$OLDPWD" || catch_error
16 |
17 |
18 | "$ALLSKY_DIRECTORY/misc/indiserver_only_setup.sh"
19 |
20 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/graph-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/terminal-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/layout-three-columns.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/service/mysql_indi-allsky.conf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | bind-address = 127.0.0.1
3 | #bind-address = 0.0.0.0
4 |
5 | ssl-ca = /etc/mysql/ssl/indi-allsky_mysql.pem
6 | ssl-cert = /etc/mysql/ssl/indi-allsky_mysql.pem
7 | ssl-key = /etc/mysql/ssl/indi-allsky_mysql.key
8 |
9 | require-secure-transport = on
10 | #ssl = on
11 |
12 | innodb_file_per_table = 1
13 | lower_case_table_name = 0
14 | innodb_flush_log_at_trx_commit = 0
15 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/image-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/testing/net/ftp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import ftplib
4 |
5 | #ftp = ftplib.FTP()
6 | ftp = ftplib.FTP_TLS()
7 |
8 | ftp.connect(host="localhost", port=21, timeout=5.0)
9 |
10 | print(ftp.getwelcome())
11 |
12 | print(ftp.auth())
13 | print(ftp.prot_p())
14 |
15 | ftpResponse = ftp.login(user="username", passwd="password")
16 |
17 | print(ftpResponse)
18 |
19 | print(ftp.pwd())
20 |
21 | ftp.mkd('foobar')
22 |
23 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/distribute-horizontal.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/hdd-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/input-cursor.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/toggles.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docker/homeassistant_docker-compose.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | services:
3 | homeassistant.indi.allsky:
4 | image: "ghcr.io/home-assistant/home-assistant:stable"
5 | env_file: .env
6 | volumes:
7 | - homeassistant_config_indi_allsky:/config
8 | #- /etc/localtime:/etc/localtime:ro
9 | #- /run/dbus:/run/dbus:ro
10 | #privileged: true
11 | ports:
12 | - "18123:8123"
13 |
14 |
15 | volumes:
16 | homeassistant_config_indi_allsky:
17 |
--------------------------------------------------------------------------------
/testing/indi/indi_dbus_kstars_setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -o errexit
4 | set -o nounset
5 |
6 |
7 | PATH=/usr/bin:/bin
8 | export PATH
9 |
10 |
11 | sudo apt-get -y install \
12 | build-essential
13 | cmake \
14 | python3-dev \
15 | pkg-config \
16 | libcairo2-dev \
17 | libgirepository1.0-dev \
18 | libdbus-1-dev \
19 | ftools-fv
20 |
21 |
22 | echo
23 | echo "Now install PyGObject and dbus-python in your virtualenv"
24 | echo
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.swp
2 | *.fit
3 | *.bin
4 | *.lock
5 | *.pid
6 | core.*
7 | images/**/*.jpg
8 | images/**/*.jpeg
9 | images/**/*.png
10 | images/**/*.tif
11 | images/**/*.mp4
12 | images/**/*.gif
13 | /*.json
14 | *.py[oc]
15 | __pycache__/
16 | *.log
17 | log/*.log
18 | log/*.log.?
19 | log/*.log.??
20 | virtualenv/*
21 | alembic/versions/*.py
22 | migrations/versions/*.py
23 | gunicorn-indi-allsky.sock
24 | *.retry
25 | .env
26 | env.*
27 | ssl.crt
28 | ssl.key
29 | de421.bsp
30 | *.h5
31 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/house-fill.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/wsgi.py:
--------------------------------------------------------------------------------
1 | # WSGI file for mod_wsgi in apache/gunicorn
2 | #
3 | # This file is monitored for changes via inotify
4 | # Updates should restart gunicorn automatically
5 | #
6 | # Version 00023
7 | #
8 | import logging
9 |
10 | from indi_allsky.flask import create_app
11 | application = create_app()
12 |
13 | gunicorn_logger = logging.getLogger('gunicorn.error')
14 | application.logger.handlers = gunicorn_logger.handlers
15 | application.logger.setLevel(gunicorn_logger.level)
16 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/layers.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/info-circle.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/service/indi-allsky.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Indi AllSky Service
3 | After=network.target indiserver.service
4 |
5 | [Service]
6 | EnvironmentFile=%ALLSKY_ETC%/indi-allsky.env
7 | WorkingDirectory=%ALLSKY_DIRECTORY%
8 | ExecStart=%ALLSKY_DIRECTORY%/virtualenv/indi-allsky/bin/python3 allsky.py --log syslog run
9 | ExecReload=/bin/kill -HUP $MAINPID
10 | ExecStop=/bin/kill -TERM $MAINPID
11 | RestartSec=5
12 | PrivateTmp=true
13 | UMask=0022
14 |
15 | [Install]
16 | WantedBy=default.target
17 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/__init__.py:
--------------------------------------------------------------------------------
1 | from .gpioSimulator import GpioSimulator as gpio_simulator
2 | from .gpioStandard import GpioStandard as blinka_gpio_standard
3 | from .gpioRpiGpio import GpioRpiGpio as rpigpio_gpio_rpigpio
4 |
5 | from .gpioDockerPi4ChannelRelay import GpioDockerPi4ChannelRelay_I2C as gpio_dockerpi_4channel_relay
6 |
7 |
8 | __all__ = (
9 | 'gpio_simulator',
10 | 'blinka_gpio_standard',
11 | 'gpio_dockerpi_4channel_relay',
12 | 'rpigpio_gpio_rpigpio',
13 | )
14 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/info-square-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/camera-fill.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/aspect-ratio.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/service/gunicorn.conf.py:
--------------------------------------------------------------------------------
1 | worker_class = 'gthread'
2 | threads = 8
3 | timeout = 180
4 | syslog = True
5 | syslog_addr = 'unix:///dev/log'
6 | syslog_facility = 'local7'
7 | loglevel = 'info'
8 | reload = True
9 | reload_engine = 'inotify'
10 | umask = 0o0022
11 |
12 | # Not necessary for sockets. May need to be locked down for a reverse proxy.
13 | forwarded_allow_ips = '*'
14 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/repeat.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/stopwatch.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/cloud-moon-fill.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/wrench.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/grid-3x2-gap-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/migrations/script.py.mako:
--------------------------------------------------------------------------------
1 | """${message}
2 |
3 | Revision ID: ${up_revision}
4 | Revises: ${down_revision | comma,n}
5 | Create Date: ${create_date}
6 |
7 | """
8 | from alembic import op
9 | import sqlalchemy as sa
10 | ${imports if imports else ""}
11 |
12 | # revision identifiers, used by Alembic.
13 | revision = ${repr(up_revision)}
14 | down_revision = ${repr(down_revision)}
15 | branch_labels = ${repr(branch_labels)}
16 | depends_on = ${repr(depends_on)}
17 |
18 |
19 | def upgrade():
20 | ${upgrades if upgrades else "pass"}
21 |
22 |
23 | def downgrade():
24 | ${downgrades if downgrades else "pass"}
25 |
--------------------------------------------------------------------------------
/service/gunicorn-indi-allsky.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=indi-allsky gunicorn daemon
3 | Requires=%GUNICORN_SERVICE_NAME%.socket
4 | After=network.target
5 |
6 | [Service]
7 | Type=notify
8 | EnvironmentFile=%ALLSKY_ETC%/indi-allsky.env
9 | #RuntimeDirectory=%GUNICORN_SERVICE_NAME%
10 | WorkingDirectory=%ALLSKY_DIRECTORY%
11 | ExecStart=%ALLSKY_DIRECTORY%/virtualenv/indi-allsky/bin/gunicorn --config %ALLSKY_ETC%/gunicorn.conf.py indi_allsky.wsgi
12 | ExecReload=/bin/kill -s HUP $MAINPID
13 | KillMode=mixed
14 | TimeoutStopSec=5
15 | PrivateTmp=true
16 | Nice=10
17 | UMask=0022
18 |
19 | [Install]
20 | WantedBy=default.target
21 |
--------------------------------------------------------------------------------
/misc/perf_indexes_sqlite.sql:
--------------------------------------------------------------------------------
1 | CREATE INDEX idx_image_createDate_YmdH on image (
2 | CAST(STRFTIME("%Y", "createDate") AS INTEGER),
3 | CAST(STRFTIME("%m", "createDate") AS INTEGER),
4 | CAST(STRFTIME("%d", "createDate") AS INTEGER),
5 | CAST(STRFTIME("%H", "createDate") AS INTEGER)
6 | );
7 |
8 |
9 | CREATE INDEX idx_video_dayDate_Ym on video (
10 | CAST(STRFTIME("%Y", "dayDate") AS INTEGER),
11 | CAST(STRFTIME("%m", "dayDate") AS INTEGER)
12 | );
13 |
14 |
15 | CREATE INDEX idx_mini_video_dayDate_Ym on mini_video (
16 | CAST(STRFTIME("%Y", "dayDate") AS INTEGER),
17 | CAST(STRFTIME("%m", "dayDate") AS INTEGER)
18 | );
19 |
20 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/virtualsky/extra/highlight.css:
--------------------------------------------------------------------------------
1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#fff}.hljs,.hljs-subst{color:#000}.hljs-string,.hljs-meta,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-addition{color:#0DBC37}.hljs-comment,.hljs-quote{color:#aaa}.hljs-number,.hljs-regexp,.hljs-literal,.hljs-bullet,.hljs-link{color:#31a354}.hljs-deletion,.hljs-variable{color:#88f}.hljs-keyword,.hljs-selector-tag,.hljs-title,.hljs-section,.hljs-built_in,.hljs-doctag,.hljs-type,.hljs-tag,.hljs-name,.hljs-selector-id,.hljs-selector-class,.hljs-strong{color:#2254F4}.hljs-emphasis{font-style:italic}.hljs-attribute{color:#D73058}
--------------------------------------------------------------------------------
/service/indiserver.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Indi Server
3 | After=network.target
4 |
5 | [Service]
6 | #User=%INDISERVER_USER%
7 | #Environment="PATH=%ALLSKY_DIRECTORY%/virtualenv/indi-allsky/bin:/usr/local/bin:/usr/bin:/bin"
8 | #Environment="PYTHONPATH=%ALLSKY_DIRECTORY%/virtualenv/indi-allsky/lib/python3.11/site-packages:/usr/local/lib/aarch64-linux-gnu/python3.11/site-packages"
9 | ExecStart=%INDI_DRIVER_PATH%/indiserver -p %INDI_PORT% indi_simulator_telescope %INDI_CCD_DRIVER% %INDI_GPS_DRIVER%
10 | ExecStop=/bin/kill -TERM $MAINPID
11 | RestartSec=5
12 | PrivateTmp=true
13 | UMask=0022
14 |
15 | [Install]
16 | WantedBy=default.target
17 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/newspaper.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/indi_allsky/devices/focusers/focuserSimulator.py:
--------------------------------------------------------------------------------
1 | # This is a fake device that acts like a focuser but does not actually do anything
2 | import time
3 | import logging
4 |
5 | from .focuserBase import FocuserBase
6 |
7 |
8 | logger = logging.getLogger('indi_allsky')
9 |
10 |
11 | class FocuserSimulator(FocuserBase):
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(FocuserSimulator, self).__init__(*args, **kwargs)
15 |
16 |
17 | def move(self, direction, degrees):
18 | steps = degrees
19 |
20 | if direction == 'ccw':
21 | steps *= -1 # negative for CCW
22 |
23 | # simulate waiting for movement to complete
24 | time.sleep(1.0)
25 |
26 | return steps
27 |
--------------------------------------------------------------------------------
/docker/Dockerfile.mosquitto:
--------------------------------------------------------------------------------
1 |
2 | FROM eclipse-mosquitto:2.0
3 |
4 | ARG MOSQUITTO_USER
5 | ARG MOSQUITTO_PASS
6 |
7 |
8 | USER root
9 |
10 | RUN apk update
11 | RUN apk upgrade --no-cache
12 | RUN apk add --no-cache \
13 | ca-certificates
14 |
15 | RUN update-ca-certificates
16 |
17 |
18 | RUN apk update
19 | RUN apk upgrade --no-cache
20 |
21 |
22 | COPY docker/mosquitto.conf /mosquitto/config/mosquitto.conf
23 | COPY docker/ssl.crt /etc/ssl/certs/mosquitto.crt
24 | COPY docker/ssl.key /etc/ssl/private/mosquitto.key
25 |
26 | RUN chmod 640 /etc/ssl/private/mosquitto.key
27 | RUN chown root:mosquitto /etc/ssl/private/mosquitto.key
28 |
29 |
30 | RUN mosquitto_passwd -b -c /mosquitto/passwd_file $MOSQUITTO_USER $MOSQUITTO_PASS
31 |
32 |
--------------------------------------------------------------------------------
/docker/mosquitto.conf:
--------------------------------------------------------------------------------
1 | allow_anonymous false
2 | password_file /mosquitto/passwd_file
3 |
4 | persistence true
5 | persistence_location /mosquitto/data/
6 |
7 |
8 | # MQTT
9 | listener 1883
10 | protocol mqtt
11 |
12 | listener 8883
13 | protocol mqtt
14 |
15 | cafile /etc/ssl/certs/ca-certificates.crt
16 | certfile /etc/ssl/certs/mosquitto.crt
17 | keyfile /etc/ssl/private/mosquitto.key
18 | #tls_version tlsv1.2
19 |
20 | require_certificate false
21 |
22 |
23 | # Websockets
24 | listener 8080
25 | protocol websockets
26 |
27 | listener 8081
28 | protocol websockets
29 |
30 | cafile /etc/ssl/certs/ca-certificates.crt
31 | certfile /etc/ssl/certs/mosquitto.crt
32 | keyfile /etc/ssl/private/mosquitto.key
33 | #tls_version tlsv1.2
34 |
35 | require_certificate false
36 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanSimulator.py:
--------------------------------------------------------------------------------
1 | # This is a fake device that acts like a fan but does not actually do anything
2 | import logging
3 |
4 | from .fanBase import FanBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class FanSimulator(FanBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(FanSimulator, self).__init__(*args, **kwargs)
14 |
15 | self._state = -1
16 |
17 |
18 | @property
19 | def state(self):
20 | return self._state
21 |
22 |
23 | @state.setter
24 | def state(self, new_state):
25 | #logger.warning('Set fan state: %d%% (fake)', int(new_state))
26 | self._state = 0 # 0 is intentional
27 |
28 |
29 | def disable(self):
30 | self.state = 0
31 |
32 |
--------------------------------------------------------------------------------
/testing/gpio/dht11_22_blinka.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import board
4 | import adafruit_dht
5 | import logging
6 |
7 |
8 | PIN = board.D5
9 | DHT = adafruit_dht.DHT22
10 | #DHT = adafruit_dht.DHT21
11 | #DHT = adafruit_dht.DHT11
12 |
13 |
14 | logging.basicConfig(level=logging.INFO)
15 | logger = logging
16 |
17 |
18 | class DhtTempSensor(object):
19 |
20 | def __init__(self):
21 |
22 | self.dht = DHT(PIN, use_pulseio=False)
23 |
24 |
25 | def main(self):
26 |
27 | temp_c = self.dht.temperature
28 | humidity = self.dht.humidity
29 |
30 |
31 | logger.info('Temperature device: temp %0.1f, humidity %0.1f%%', temp_c, humidity)
32 |
33 |
34 | if __name__ == "__main__":
35 | d = DhtTempSensor()
36 | d.main()
37 |
38 |
--------------------------------------------------------------------------------
/misc/mosquitto_indi-allsky.conf:
--------------------------------------------------------------------------------
1 | allow_anonymous false
2 | password_file /etc/mosquitto/passwd
3 |
4 |
5 | # MQTT
6 | listener 1883
7 | protocol mqtt
8 |
9 | listener 8883
10 | protocol mqtt
11 |
12 | cafile /etc/ssl/certs/ca-certificates.crt
13 | certfile /etc/mosquitto/certs/indi-allsky_mosquitto.crt
14 | keyfile /etc/mosquitto/certs/indi-allsky_mosquitto.key
15 | #tls_version tlsv1.2
16 |
17 | require_certificate false
18 |
19 |
20 | # Websockets
21 | listener 8080
22 | protocol websockets
23 |
24 | listener 8081
25 | protocol websockets
26 |
27 | cafile /etc/ssl/certs/ca-certificates.crt
28 | certfile /etc/mosquitto/certs/indi-allsky_mosquitto.crt
29 | keyfile /etc/mosquitto/certs/indi-allsky_mosquitto.key
30 | #tls_version tlsv1.2
31 |
32 | require_certificate false
33 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/gpioSimulator.py:
--------------------------------------------------------------------------------
1 | # This is a fake device that acts like a GPIO but does not actually do anything
2 | import logging
3 |
4 | from .genericBase import GenericBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class GpioSimulator(GenericBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(GpioSimulator, self).__init__(*args, **kwargs)
14 |
15 | self._state = -1
16 |
17 |
18 | @property
19 | def state(self):
20 | return self._state
21 |
22 |
23 | @state.setter
24 | def state(self, new_state):
25 | #logger.warning('Set gpio state: %d%% (fake)', int(new_state))
26 | self._state = 0 # 0 is intentional
27 |
28 |
29 | def disable(self):
30 | self.state = 0
31 |
32 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/magic.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterSimulator.py:
--------------------------------------------------------------------------------
1 | # This is a fake device that acts like a dew heater but does not actually do anything
2 | import logging
3 |
4 | from .dewHeaterBase import DewHeaterBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class DewHeaterSimulator(DewHeaterBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(DewHeaterSimulator, self).__init__(*args, **kwargs)
14 |
15 | self._state = -1
16 |
17 |
18 | @property
19 | def state(self):
20 | return self._state
21 |
22 |
23 | @state.setter
24 | def state(self, new_state):
25 | #logger.warning('Set dew heater state: %d%% (fake)', int(new_state))
26 | self._state = 0 # 0 is intentional
27 |
28 |
29 | def disable(self):
30 | self.state = 0
31 |
32 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/gear-fill.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/exceptions.py:
--------------------------------------------------------------------------------
1 |
2 | class TimeOutException(Exception):
3 | pass
4 |
5 |
6 | class CalibrationNotFound(Exception):
7 | pass
8 |
9 |
10 | class TemperatureException(Exception):
11 | pass
12 |
13 |
14 | class IndiServerException(Exception):
15 | pass
16 |
17 |
18 | class CameraException(Exception):
19 | pass
20 |
21 |
22 | class TimelapseException(Exception):
23 | pass
24 |
25 |
26 | class ConfigSaveException(Exception):
27 | pass
28 |
29 |
30 | class BadImage(Exception):
31 | pass
32 |
33 |
34 | class KeogramMismatchException(Exception):
35 | pass
36 |
37 |
38 | class ConnectionFailure(Exception):
39 | pass
40 |
41 |
42 | class NotFound(Exception):
43 | pass
44 |
45 |
46 | class BackupFailure(Exception):
47 | pass
48 |
49 |
50 | class BinModeException(Exception):
51 | pass
52 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/journal-medical.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/indi_allsky/devices/focusers/__init__.py:
--------------------------------------------------------------------------------
1 | from .focuserSimulator import FocuserSimulator as focuser_simulator
2 |
3 | from .focuser_28byj import focuser_28byj_64 as blinka_focuser_28byj_64
4 | from .focuser_28byj import focuser_28byj_16 as blinka_focuser_28byj_16
5 |
6 | from .focuser_a4988 import focuser_a4988_nema17_full as blinka_focuser_a4988_nema17_full
7 | from .focuser_a4988 import focuser_a4988_nema17_half as blinka_focuser_a4988_nema17_half
8 |
9 | from .focuserMotorKit import FocuserMotorKitSingleStep as motorkit_focuser_single_step
10 |
11 | from .focuserSerial28byj import FocuserSerial28byj_64 as serial_focuser_28byj_64
12 |
13 | __all__ = (
14 | 'focuser_simulator',
15 | 'blinka_focuser_28byj_64',
16 | 'blinka_focuser_28byj_16',
17 | 'blinka_focuser_a4988_nema17_full',
18 | 'blinka_focuser_a4988_nema17_half',
19 | 'serial_focuser_28byj_64',
20 | 'motorkit_focuser_single_step',
21 | )
22 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/wifi.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/misc/mysql_optimize.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -x # command tracing
4 | set -o errexit
5 | set -o nounset
6 |
7 |
8 | PATH=/bin:/usr/bin
9 | export PATH
10 |
11 |
12 | sudo mysql -u root -e \
13 | "SELECT table_name, data_length, data_free \
14 | FROM information_schema.tables \
15 | WHERE table_schema='indi_allsky' \
16 | ORDER BY data_length DESC;"
17 |
18 |
19 | if systemctl --user --quiet is-active indi-allsky >/dev/null 2>&1; then
20 | echo
21 | echo
22 | echo "ERROR: indi-allsky is running. Please stop the service before running this script."
23 | echo
24 | exit 1
25 | fi
26 |
27 |
28 | echo
29 | echo
30 | echo "Optimizing tables in 10 seconds (control-c to cancel)"
31 | echo
32 | sleep 10
33 |
34 |
35 | echo
36 | echo
37 | echo "Note: Messages about tables not supporting optimize are normal"
38 | echo
39 |
40 | sudo mysqlcheck -u root --optimize --databases indi_allsky
41 |
42 |
--------------------------------------------------------------------------------
/docker/start_indiserver.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -x # command tracing
4 | set -o errexit
5 | set -o nounset
6 |
7 | PATH=/usr/local/bin:/usr/bin:/bin
8 | export PATH
9 |
10 |
11 | echo -n "System timezone: "
12 | cat /etc/timezone || true
13 |
14 | # log the date (shows timezone)
15 | date
16 |
17 |
18 | if [ -f "/usr/local/bin/indiserver" ]; then
19 | INDISERVER="/usr/local/bin/indiserver"
20 | else
21 | INDISERVER="/usr/bin/indiserver"
22 | fi
23 |
24 |
25 | if [ -n "${INDIALLSKY_INDI_GPS_DRIVER:-}" ]; then
26 | exec "$INDISERVER" \
27 | -v \
28 | -p 7624 \
29 | indi_simulator_telescope \
30 | "$INDIALLSKY_INDI_CCD_DRIVER" \
31 | "$INDIALLSKY_INDI_GPS_DRIVER"
32 |
33 | else
34 | echo "No GPS driver configured"
35 | exec "$INDISERVER" \
36 | -v \
37 | -p 7624 \
38 | indi_simulator_telescope \
39 | "$INDIALLSKY_INDI_CCD_DRIVER"
40 | fi
41 |
42 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/tools.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/stars.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/__init__.py:
--------------------------------------------------------------------------------
1 | from .fanSimulator import FanSimulator as fan_simulator
2 | from .fanPwm import FanPwm as blinka_fan_pwm
3 | from .fanStandard import FanStandard as blinka_fan_standard
4 |
5 | from .fanSoftwarePwm import FanSoftwarePwmRpiGpio as rpigpio_fan_software_pwm
6 | from .fanSoftwarePwm import FanSoftwarePwmGpiozero as gpiozero_fan_software_pwm
7 |
8 | from .fanMotorKit import FanMotorKitPwm as motorkit_fan_pwm
9 | from .fanDockerPi4ChannelRelay import FanDockerPi4ChannelRelay_I2C as fan_dockerpi_4channel_relay
10 |
11 | from .fanMqtt import FanMqttStandard as mqtt_fan_standard
12 | from .fanMqtt import FanMqttPwm as mqtt_fan_pwm
13 |
14 | from .fanSerialPwm import FanSerialPwm as serial_fan_pwm
15 |
16 |
17 | __all__ = (
18 | 'fan_simulator',
19 | 'blinka_fan_pwm',
20 | 'blinka_fan_standard',
21 | 'fan_dockerpi_4channel_relay',
22 | 'serial_fan_pwm',
23 | 'rpigpio_fan_software_pwm',
24 | 'gpiozero_fan_software_pwm',
25 | 'motorkit_fan_pwm',
26 | 'mqtt_fan_standard',
27 | 'mqtt_fan_pwm',
28 | )
29 |
--------------------------------------------------------------------------------
/docker/Dockerfile.indiserver_ubuntu2404:
--------------------------------------------------------------------------------
1 | FROM indi.base
2 |
3 | ARG DEBIAN_FRONTEND=noninteractive
4 |
5 | USER root
6 | RUN apt-get update
7 | RUN apt-get -y upgrade
8 |
9 | RUN apt-get -y install \
10 | indi-full \
11 | libindi-dev \
12 | indi-webcam \
13 | indi-asi \
14 | libasi \
15 | indi-qhy \
16 | libqhy \
17 | indi-playerone \
18 | libplayerone \
19 | indi-svbony \
20 | libsvbony \
21 | libaltaircam \
22 | libmallincam \
23 | libmicam \
24 | libnncam \
25 | indi-toupbase \
26 | libtoupcam \
27 | indi-gphoto \
28 | indi-sx \
29 | indi-dsi \
30 | indi-gpsd \
31 | indi-gpsnmea
32 |
33 |
34 | # Unnecessary
35 | #gsc \
36 |
37 |
38 | # cleanup
39 | RUN apt-get clean
40 |
41 |
42 | #RUN rm -f /etc/sudoers.d/allsky
43 |
44 |
45 | COPY docker/start_indiserver.sh /home/allsky
46 | RUN chown allsky:allsky /home/allsky/start_indiserver.sh
47 | RUN chmod 755 /home/allsky/start_indiserver.sh
48 |
49 |
50 | USER allsky
51 | WORKDIR /home/allsky
52 |
53 |
54 | ENTRYPOINT ["./start_indiserver.sh"]
55 |
--------------------------------------------------------------------------------
/migrations/alembic.ini:
--------------------------------------------------------------------------------
1 | # A generic, single database configuration.
2 |
3 | [alembic]
4 | # template used to generate migration files
5 | # file_template = %%(rev)s_%%(slug)s
6 |
7 | # set to 'true' to run the environment during
8 | # the 'revision' command, regardless of autogenerate
9 | # revision_environment = false
10 |
11 |
12 | # Logging configuration
13 | [loggers]
14 | keys = root,sqlalchemy,alembic,flask_migrate
15 |
16 | [handlers]
17 | keys = console
18 |
19 | [formatters]
20 | keys = generic
21 |
22 | [logger_root]
23 | level = WARN
24 | handlers = console
25 | qualname =
26 |
27 | [logger_sqlalchemy]
28 | level = WARN
29 | handlers =
30 | qualname = sqlalchemy.engine
31 |
32 | [logger_alembic]
33 | level = INFO
34 | handlers =
35 | qualname = alembic
36 |
37 | [logger_flask_migrate]
38 | level = INFO
39 | handlers =
40 | qualname = flask_migrate
41 |
42 | [handler_console]
43 | class = StreamHandler
44 | args = (sys.stderr,)
45 | level = NOTSET
46 | formatter = generic
47 |
48 | [formatter_generic]
49 | format = %(levelname)-5.5s [%(name)s] %(message)s
50 | datefmt = %H:%M:%S
51 |
--------------------------------------------------------------------------------
/docker/Dockerfile.indiserver_debian12:
--------------------------------------------------------------------------------
1 | FROM indi.base
2 |
3 | ARG INDI_3RDPARTY_VERSION
4 | ARG INDI_CAMERA_VENDOR
5 | ARG DEBIAN_FRONTEND=noninteractive
6 |
7 | USER root
8 | RUN apt-get update
9 | RUN apt-get -y upgrade
10 |
11 |
12 | # cleanup
13 | RUN apt-get clean
14 |
15 |
16 | #RUN rm -f /etc/sudoers.d/allsky
17 |
18 |
19 | COPY docker/start_indiserver.sh /home/allsky
20 | RUN chown allsky:allsky /home/allsky/start_indiserver.sh
21 | RUN chmod 755 /home/allsky/start_indiserver.sh
22 |
23 |
24 | COPY --chown=allsky:allsky misc/build_indi.sh /home/allsky
25 | RUN chmod 755 /home/allsky/build_indi.sh
26 |
27 | USER allsky
28 | WORKDIR /home/allsky
29 |
30 |
31 | ENV BUILD_INDI_SETTINGS=manual
32 | ENV BUILD_INDI_CORE=false
33 | ENV BUILD_INDI_3RDPARTY=true
34 | ENV BUILD_INDI_CAMERA_VENDOR=$INDI_CAMERA_VENDOR
35 | ENV BUILD_INDI_OS_PACKAGE_UPGRADE=false
36 | RUN bash build_indi.sh null $INDI_3RDPARTY_VERSION \
37 | && rm -fR /home/allsky/Projects \
38 | && sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
39 |
40 |
41 | ENTRYPOINT ["./start_indiserver.sh"]
42 |
--------------------------------------------------------------------------------
/indi_allsky/timelapse_preprocessor/preProcessorStandard.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import tempfile
3 | import logging
4 |
5 | from .preProcessorBase import PreProcessorBase
6 |
7 |
8 | logger = logging.getLogger('indi_allsky')
9 |
10 |
11 | class PreProcessorStandard(PreProcessorBase):
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(PreProcessorStandard, self).__init__(*args, **kwargs)
15 |
16 |
17 | # this needs to be a class variable
18 | # tmp folder needs to be in /tmp so symlinks are supported (image_dir might be fat32)
19 | self.temp_seqfolder = tempfile.TemporaryDirectory(suffix='_timelapse') # context manager automatically deletes files when finished
20 | self._seqfolder = Path(self.temp_seqfolder.name)
21 |
22 |
23 | def main(self, file_list):
24 | for i, f in enumerate(file_list):
25 | # the symlink files must start at index 0 or ffmpeg will fail
26 | p_symlink = self.seqfolder.joinpath('{0:05d}.{1:s}'.format(i, self.config['IMAGE_FILE_TYPE']))
27 | p_symlink.symlink_to(f)
28 |
29 |
--------------------------------------------------------------------------------
/misc/example_ccd_temp.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Example of an external temperature script for indi-allsky
4 | # STDOUT and STDERR are ignored
5 | #
6 | # The json output file is set in the environment variable TEMP_JSON
7 |
8 | set -o errexit # exit on any error
9 | set -o nounset # exit on any unset variable
10 |
11 |
12 | # shellcheck disable=SC2317
13 | function sigint_handler() {
14 | # this prevents a keyboard interrupt from stopping the script
15 | true
16 | }
17 | trap sigint_handler SIGINT
18 |
19 |
20 | #############
21 | ### START ###
22 | #############
23 |
24 |
25 | TEMP_C=-5.111
26 |
27 |
28 | # data file is communicated via environment variable
29 | if [ -z "${TEMP_JSON:-}" ]; then
30 | echo "TEMP_JSON environment variable is not defined"
31 | exit 1
32 | fi
33 |
34 |
35 | # write json data
36 | jq --null-input --argjson temp_c "$TEMP_C" '.temp = $temp_c' '{}' > "$TEMP_JSON"
37 |
38 |
39 | # you could also just use a string as long as it is valid json
40 | #echo "{ \"temp\" : $TEMP_C }" | jq > $TEMP_JSON
41 |
42 |
43 | # script must return exit code 0 for success
44 | exit 0
45 |
--------------------------------------------------------------------------------
/licenses/chart.js/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2022 Chart.js Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/licenses/clipboard.js/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Zeno Rocha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/licenses/bootstrap/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2023 The Bootstrap Authors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/licenses/jquery/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright OpenJS Foundation and other contributors, https://openjsf.org/
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/licenses/DataTables/license.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2008-present, SpryMedia Limited
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/licenses/photoswipe/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2022 Dmitry Semenov, https://dimsemenov.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/docker/upgrade.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | #set -x # command tracing
4 | set -o errexit
5 | set -o nounset
6 |
7 | PATH=/usr/bin:/bin
8 | export PATH
9 |
10 |
11 | echo "#############################################################"
12 | echo "### Welcome to the indi-allsky docker code upgrade script ###"
13 | echo "#############################################################"
14 | echo
15 | echo
16 | echo
17 | echo "This script rebuilds the containers that contain code when you"
18 | echo "want to upgrade indi-allsky."
19 | echo
20 | echo "Please ensure you have pulled down the latest code from GitHub"
21 | echo
22 | echo "The expected run time is 60s or less if you still have your"
23 | echo "build cache from previous builds"
24 | echo
25 | echo
26 | echo "Setup proceeding in 10 seconds... (control-c to cancel)"
27 | echo
28 | sleep 10
29 |
30 |
31 | START_TIME=$(date +%s)
32 |
33 |
34 | docker compose \
35 | build \
36 | capture.indi.allsky \
37 | gunicorn.indi.allsky \
38 | webserver.indi.allsky
39 |
40 |
41 | END_TIME=$(date +%s)
42 |
43 | echo
44 | echo
45 | echo "Completed in $((END_TIME - START_TIME))s"
46 | echo
47 |
48 | echo
49 | echo "You may now restart your containers"
50 | echo
51 |
52 |
53 | echo
54 | echo "Enjoy!"
55 |
56 |
--------------------------------------------------------------------------------
/misc/example_ccd_temp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # Example of an external temperature script for indi-allsky
4 | # STDOUT and STDERR are ignored
5 | #
6 | # The json output file is set in the environment variable TEMP_JSON
7 |
8 |
9 | import os
10 | import sys
11 | import io
12 | import json
13 | import signal
14 | import logging
15 |
16 |
17 | logging.basicConfig(level=logging.INFO)
18 | logger = logging
19 |
20 |
21 | def sigint_handler(signum, frame):
22 | # this prevents a keyboard interrupt from stopping the script
23 | pass
24 |
25 |
26 | signal.signal(signal.SIGINT, sigint_handler)
27 |
28 |
29 | #############
30 | ### START ###
31 | #############
32 |
33 |
34 | temp_c = -5.111
35 |
36 | try:
37 | # data file is communicated via environment variable
38 | temp_json = os.environ['TEMP_JSON']
39 | except KeyError:
40 | logger.error('TEMP_JSON environment variable is not defined')
41 | sys.exit(1)
42 |
43 |
44 | # dict to be used for json data
45 | temp_data = {
46 | 'temp' : temp_c,
47 | }
48 |
49 |
50 | # write json data
51 | with io.open(temp_json, 'w') as f_temp_json:
52 | json.dump(temp_data, f_temp_json, indent=4)
53 |
54 |
55 | # script must return exit code 0 for success
56 | sys.exit(0)
57 |
58 |
--------------------------------------------------------------------------------
/service/90-org.aaronwmorris.indi-allsky.pkla:
--------------------------------------------------------------------------------
1 | [Allow indi-allsky user to shutdown]
2 | Identity=unix-user:%ALLSKY_USER%
3 | Action=org.freedesktop.login1.power-off;org.freedesktop.login1.power-off-multiple-sessions;org.freedesktop.login1.reboot;org.freedesktop.login1.reboot-multiple-sessions
4 | ResultActive=yes
5 | ResultAny=yes
6 |
7 | [Allow indi-allsky user to manage network]
8 | Identity=unix-user:%ALLSKY_USER%
9 | Action=org.freedesktop.NetworkManager.enable-disable-wifi;org.freedesktop.NetworkManager.network-control;org.freedesktop.NetworkManager.wifi.share.protected;org.freedesktop.NetworkManager.settings.modify.system;org.freedesktop.NetworkManager.wifi.scan;org.freedesktop.NetworkManager.settings.modify.own
10 | ResultActive=yes
11 | ResultAny=yes
12 |
13 | [Allow indi-allsky user to mount USB disks]
14 | Identity=unix-user:%ALLSKY_USER%
15 | Action=org.freedesktop.udisks2.filesystem-mount-system;org.freedesktop.udisks2.filesystem-mount;org.freedesktop.udisks2.filesystem-mount-other-seat
16 | ResultActive=yes
17 | ResultAny=yes
18 |
19 | [Allow indi-allsky user to set system time]
20 | Identity=unix-user:%ALLSKY_USER%
21 | Action=org.freedesktop.timedate1.set-time;org.freedesktop.timedate1.set-timezone;org.freedesktop.timedate1.set-ntp
22 | ResultActive=yes
23 | ResultAny=yes
24 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/gpioRpiGpio.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .genericBase import GenericBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class GpioRpiGpio(GenericBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(GpioRpiGpio, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 |
17 | self.gpio_pin = int(pin_1_name)
18 |
19 | import RPi.GPIO as GPIO
20 |
21 |
22 | #GPIO.setmode(GPIO.BOARD)
23 | GPIO.setmode(GPIO.BCM)
24 | GPIO.setup(self.gpio_pin, GPIO.OUT)
25 |
26 |
27 | self._state = bool(GPIO.input(self.gpio_pin))
28 |
29 | time.sleep(0.25)
30 |
31 |
32 | @property
33 | def state(self):
34 | return self._state
35 |
36 |
37 | @state.setter
38 | def state(self, new_state):
39 | new_state_b = bool(new_state)
40 |
41 |
42 | import RPi.GPIO as GPIO
43 |
44 | if new_state_b:
45 | logger.warning('Set gpio state: HIGH')
46 | GPIO.output(self.gpio_pin, GPIO.HIGH)
47 | self._state = True
48 | else:
49 | logger.warning('Set gpio state: LOW')
50 | GPIO.output(self.gpio_pin, GPIO.LOW)
51 | self._state = False
52 |
53 |
--------------------------------------------------------------------------------
/docker/Dockerfile.webserver_nginx:
--------------------------------------------------------------------------------
1 |
2 | FROM nginx:bookworm
3 |
4 | ARG TZ
5 | ARG INDIALLSKY_IMAGE_FOLDER
6 | ARG DEBIAN_FRONTEND=noninteractive
7 |
8 |
9 | USER root
10 | RUN apt-get update
11 | RUN apt-get -y upgrade
12 | RUN apt-get -y install \
13 | ca-certificates \
14 | tzdata
15 |
16 |
17 | # cleanup
18 | RUN apt-get clean
19 |
20 |
21 | # update timezone
22 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
23 | RUN dpkg-reconfigure -f noninteractive tzdata
24 |
25 |
26 | # need the uid and gid to match in all containers
27 | RUN groupadd --gid 10001 allsky
28 | RUN useradd --create-home --no-user-group --uid 10001 --gid allsky --home-dir /home/allsky --shell /bin/bash allsky
29 |
30 |
31 | # installs latest code
32 | RUN mkdir /home/allsky/indi-allsky
33 | COPY . /home/allsky/indi-allsky
34 | RUN chown -R allsky:allsky /home/allsky/indi-allsky
35 |
36 |
37 | RUN rm -f /etc/nginx/conf.d/default.conf
38 | COPY docker/nginx.local.conf /etc/nginx/conf.d/default.conf
39 | RUN sed \
40 | -i \
41 | -e "s|%INDIALLSKY_IMAGE_FOLDER%|$INDIALLSKY_IMAGE_FOLDER|g" \
42 | /etc/nginx/conf.d/default.conf
43 | COPY docker/ssl.crt /etc/ssl/certs/nginx.crt
44 | COPY docker/ssl.key /etc/ssl/private/nginx.key
45 |
46 | RUN chmod 600 /etc/ssl/private/nginx.key
47 |
48 |
--------------------------------------------------------------------------------
/requirements/requirements_latest_web.txt:
--------------------------------------------------------------------------------
1 | ### Requirements for a web interface only interface
2 | # https://www.wheelodex.org/projects/astropy/
3 | astropy >= 6.1.0
4 | pyerfa >= 2.0.1.4
5 | # https://www.wheelodex.org/projects/numpy/
6 | numpy >= 2.1.0
7 | # https://www.wheelodex.org/projects/opencv-python-headless/
8 | opencv-python-headless >= 4.10.0.84
9 | # https://www.wheelodex.org/projects/ccdproc/
10 | ccdproc >= 2.4.2
11 | # https://www.wheelodex.org/projects/scikit-image/
12 | scikit-image >= 0.25.0
13 | # https://www.wheelodex.org/projects/Pillow/
14 | Pillow
15 | simplejpeg
16 | bottleneck >= 1.4.0
17 | ephem
18 | skyfield
19 | # https://www.wheelodex.org/projects/pycurl/ or https://www.piwheels.org/project/pycurl/
20 | pycurl
21 | gunicorn[gthread] >= 23.0.0
22 | inotify
23 | psutil
24 | Flask >= 3.1.1
25 | Flask-SQLAlchemy >= 3.1.1
26 | Flask-Migrate >= 4.0.5
27 | Flask-WTF >= 1.2.1
28 | Flask-Login >= 0.6.2
29 | werkzeug
30 | is-safe-url
31 | certifi >= 2023.7.22
32 | # https://www.wheelodex.org/projects/cryptography/
33 | cryptography >= 44.0.1
34 | dbus-python
35 | setuptools-rust
36 | # https://www.wheelodex.org/projects/bcrypt/
37 | bcrypt
38 | passlib[argon2] >= 1.7.4
39 | prettytable
40 | astroalign >= 2.6.0
41 | requests[security]
42 | lxml
43 | shapely >= 2.0.6
44 | pytz
45 | mysql-connector-python
46 |
--------------------------------------------------------------------------------
/requirements/requirements_debian11_web.txt:
--------------------------------------------------------------------------------
1 | ### Requirements for a web interface only interface
2 | # https://www.wheelodex.org/projects/numpy/
3 | numpy >= 1.22.2, < 2.0
4 | # https://www.wheelodex.org/projects/opencv-python-headless/
5 | opencv-python-headless >= 4.8.1.78, <= 4.11.0.86
6 | # https://www.wheelodex.org/projects/ccdproc/
7 | ccdproc
8 | # https://www.wheelodex.org/projects/scikit-image/
9 | scikit-image <= 0.19.3
10 | # https://www.wheelodex.org/projects/Pillow/
11 | Pillow
12 | simplejpeg ; platform_machine == 'x86_64' or 'aarch64' in platform_machine
13 | simplejpeg <= 1.7.2 ; 'arm' in platform_machine
14 | bottleneck
15 | ephem
16 | skyfield
17 | # https://www.wheelodex.org/projects/pycurl/ or https://www.piwheels.org/project/pycurl/
18 | pycurl
19 | gunicorn[gthread] >= 23.0.0
20 | inotify
21 | psutil
22 | Flask >= 3.1.1
23 | Flask-SQLAlchemy >= 3.1.1
24 | Flask-Migrate >= 4.0.5
25 | Flask-WTF >= 1.2.1
26 | Flask-Login >= 0.6.2
27 | werkzeug
28 | is-safe-url
29 | certifi >= 2023.7.22
30 | # https://www.wheelodex.org/projects/cryptography/
31 | cryptography >= 42.0.8
32 | dbus-python
33 | setuptools-rust
34 | # https://www.wheelodex.org/projects/bcrypt/
35 | bcrypt
36 | passlib[argon2] >= 1.7.4
37 | prettytable
38 | astroalign
39 | requests[security]
40 | lxml
41 | shapely
42 | pytz
43 | mysql-connector-python
44 |
--------------------------------------------------------------------------------
/docker/Dockerfile.webserver_apache:
--------------------------------------------------------------------------------
1 |
2 | FROM httpd:bookworm
3 |
4 | ARG TZ
5 | ARG INDIALLSKY_IMAGE_FOLDER
6 | ARG DEBIAN_FRONTEND=noninteractive
7 |
8 |
9 | USER root
10 | RUN apt-get update
11 | RUN apt-get -y upgrade
12 | RUN apt-get -y install \
13 | ca-certificates \
14 | tzdata
15 |
16 |
17 | # cleanup
18 | RUN apt-get clean
19 |
20 |
21 | # update timezone
22 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
23 | RUN dpkg-reconfigure -f noninteractive tzdata
24 |
25 |
26 | # need the uid and gid to match in all containers
27 | RUN groupadd --gid 10001 allsky
28 | RUN useradd --create-home --no-user-group --uid 10001 --gid allsky --home-dir /home/allsky --shell /bin/bash allsky
29 |
30 |
31 | # installs latest code
32 | RUN mkdir /home/allsky/indi-allsky
33 | COPY . /home/allsky/indi-allsky
34 | RUN chown -R allsky:allsky /home/allsky/indi-allsky
35 |
36 |
37 | RUN rm -f /usr/local/apache2/conf/httpd.conf
38 | COPY docker/apache.local.conf /usr/local/apache2/conf/httpd.conf
39 | RUN sed \
40 | -i \
41 | -e "s|%INDIALLSKY_IMAGE_FOLDER%|$INDIALLSKY_IMAGE_FOLDER|g" \
42 | /usr/local/apache2/conf/httpd.conf
43 | COPY docker/ssl.crt /etc/ssl/certs/apache.crt
44 | COPY docker/ssl.key /etc/ssl/private/apache.key
45 |
46 | RUN chmod 600 /etc/ssl/private/apache.key
47 |
48 |
--------------------------------------------------------------------------------
/requirements/requirements_gpio.txt:
--------------------------------------------------------------------------------
1 | Adafruit-Blinka
2 | gpiod
3 | gpiozero
4 | pyserial
5 | adafruit-circuitpython-dht
6 | adafruit-circuitpython-bmp280
7 | adafruit-circuitpython-bme280
8 | adafruit-circuitpython-bme680
9 | circuitpython-bmp180
10 | adafruit-circuitpython-si7021
11 | adafruit-circuitpython-sht31d
12 | adafruit-circuitpython-sht4x
13 | adafruit-circuitpython-ahtx0
14 | adafruit-circuitpython-mlx90614
15 | adafruit-circuitpython-mlx90640
16 | adafruit-circuitpython-tsl2591
17 | adafruit-circuitpython-tsl2561
18 | adafruit-circuitpython-veml7700
19 | adafruit-circuitpython-bh1750
20 | adafruit-circuitpython-ads1x15
21 | adafruit-circuitpython-si1145
22 | adafruit-circuitpython-ltr390
23 | adafruit-circuitpython-htu21d
24 | adafruit-circuitpython-htu31d
25 | adafruit-circuitpython-scd30
26 | adafruit-circuitpython-scd4x
27 | adafruit-circuitpython-hdc302x
28 | adafruit-circuitpython-bmp3xx
29 | adafruit-circuitpython-ina3221
30 | adafruit-circuitpython-ina219
31 | adafruit-circuitpython-ina23x
32 | adafruit-circuitpython-pca9685
33 | adafruit-circuitpython-motorkit
34 | sparkfun-circuitpython-qwiicas3935
35 | sparkfun-qwiic-mmc5983ma
36 | git+https://github.com/aaronwmorris/CircuitPython_MLX90615.git@ab15707
37 |
38 | ### does not work on raspi
39 | #adafruit-circuitpython-ds18x20
40 |
41 | ### Rpi5
42 | #rpi-lgpio
43 |
--------------------------------------------------------------------------------
/testing/image/fits_headers.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | ###
4 | ### Processes FITS files using the indi-allsky processing pipeline
5 | ###
6 |
7 | import sys
8 | import argparse
9 | from pathlib import Path
10 | from astropy.io import fits
11 | import logging
12 | from pprint import pformat # noqa: F401
13 |
14 |
15 | logging.basicConfig(level=logging.INFO)
16 | logger = logging
17 |
18 |
19 | class FitsHeaders(object):
20 |
21 |
22 | def main(self, input_file):
23 | input_file_p = Path(input_file)
24 | if not input_file_p.exists():
25 | logger.error('%s does not exist', input_file_p)
26 | sys.exit(1)
27 |
28 |
29 | if input_file_p.suffix != '.fit' and input_file_p.suffix != '.fits':
30 | logger.error('Please specify a FITS file')
31 | sys.exit(1)
32 |
33 |
34 | hdulist = fits.open(input_file_p)
35 |
36 | for k, v in hdulist[0].header.items():
37 | print('{0}: {1}'.format(k, v))
38 |
39 |
40 | if __name__ == "__main__":
41 | argparser = argparse.ArgumentParser()
42 | argparser.add_argument(
43 | 'fits_file',
44 | help='FITS file',
45 | type=str,
46 | )
47 |
48 | args = argparser.parse_args()
49 |
50 |
51 | fh = FitsHeaders()
52 | fh.main(args.fits_file)
53 |
54 |
--------------------------------------------------------------------------------
/misc/oci_config_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import oci
4 | import sys
5 | from pathlib import Path
6 | import logging
7 | #from pprint import pprint
8 |
9 | from sqlalchemy.orm.exc import NoResultFound
10 |
11 | sys.path.append(str(Path(__file__).parent.absolute().parent))
12 |
13 | from indi_allsky.flask import create_app
14 | from indi_allsky.config import IndiAllSkyConfig
15 |
16 | # setup flask context for db access
17 | app = create_app()
18 | app.app_context().push()
19 |
20 |
21 | logging.basicConfig(level=logging.INFO)
22 | logger = logging
23 |
24 |
25 |
26 | class OciConfigTest(object):
27 | def __init__(self):
28 | try:
29 | self._config_obj = IndiAllSkyConfig()
30 | #logger.info('Loaded config id: %d', self._config_obj.config_id)
31 | except NoResultFound:
32 | logger.error('No config file found, please import a config')
33 | sys.exit(1)
34 |
35 | self.config = self._config_obj.config
36 |
37 |
38 | def main(self):
39 |
40 | oci_config = oci.config.from_file(file_location=str(self.config['S3UPLOAD']['CREDS_FILE']))
41 |
42 | oci.config.validate_config(oci_config)
43 |
44 | logger.warning('Test succeeded')
45 |
46 |
47 | if __name__ == "__main__":
48 | t = OciConfigTest()
49 | t.main()
50 |
--------------------------------------------------------------------------------
/misc/example_pre_cature_hook.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Example of a pre-capture hook
3 | # The pre-capture script is executed before an image is captured
4 | #
5 | # STDOUT and STDERR are ignored
6 |
7 |
8 | import sys
9 | import signal
10 | import logging
11 |
12 |
13 | logging.basicConfig(level=logging.INFO)
14 | logger = logging
15 |
16 |
17 | # Available environment variables with data. Environment variables are strings, therefore
18 | # it requires using int() or float() to convert to numbers
19 | #GAIN : int(os.environ['GAIN'])
20 | #BIN : int(os.environ['BIN'])
21 | #NIGHT : int(os.environ['NIGHT'])
22 | #MOONMODE : int(os.environ['MOONMODE'])
23 | #LATITUDE : float(os.environ['LATITUDE'])
24 | #LONGITUDE : float(os.environ['LONGITUDE'])
25 | #ELEVATION : int(os.environ['ELEVATION'])
26 | #SENSOR_TEMP_0 - SENSOR_TEMP_59 : float(os.environ['SENSOR_TEMP_##'])
27 | #SENSOR_USER_0 - SENSOR_USER_59 : float(os.environ['SENSOR_USER_##'])
28 |
29 |
30 | def sigint_handler(signum, frame):
31 | # this prevents a keyboard interrupt from stopping the script
32 | pass
33 |
34 |
35 | signal.signal(signal.SIGINT, sigint_handler)
36 |
37 |
38 | #############
39 | ### START ###
40 | #############
41 |
42 |
43 | # Do something interesting here
44 |
45 |
46 | # script must exist with exit code 0 for success
47 | sys.exit(0)
48 |
--------------------------------------------------------------------------------
/testing/benchmark/shift_bench.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | import timeit
5 | import logging
6 |
7 | #import cv2
8 | #from PIL import Image
9 |
10 |
11 | logging.basicConfig(level=logging.INFO)
12 | logger = logging
13 |
14 |
15 | class ShiftBench(object):
16 | rounds = 50
17 |
18 |
19 | def __init__(self):
20 | pass
21 |
22 |
23 | def main(self):
24 |
25 | setup_1 = '''
26 | import numpy
27 | random = numpy.random.randint(2 ** 16, size=(5000, 5000, 3), dtype=numpy.uint16)
28 |
29 | bits = 16
30 | div_factor = int((2 ** bits) / 255)
31 | '''
32 |
33 | s1 = '''
34 | (random / div_factor).astype(numpy.uint8)
35 | '''
36 |
37 | setup_2 = '''
38 | import numpy
39 | random = numpy.random.randint(65535, size=(5000, 5000, 3), dtype=numpy.uint16)
40 |
41 | bits = 16
42 | shift_factor = bits - 8
43 | '''
44 |
45 | s2 = '''
46 | numpy.right_shift(random, shift_factor).astype(numpy.uint8)
47 | '''
48 |
49 |
50 | t_1 = timeit.timeit(stmt=s1, setup=setup_1, number=self.rounds)
51 | logger.info('Numpy division: %0.3fms', t_1 * 1000 / self.rounds)
52 |
53 | t_2 = timeit.timeit(stmt=s2, setup=setup_2, number=self.rounds)
54 | logger.info('Numpy shift: %0.3fms', t_2 * 1000 / self.rounds)
55 |
56 |
57 |
58 | if __name__ == "__main__":
59 | b = ShiftBench()
60 | b.main()
61 |
62 |
--------------------------------------------------------------------------------
/indi_allsky/focuser.py:
--------------------------------------------------------------------------------
1 | from .devices import focusers
2 | import logging
3 |
4 | logger = logging.getLogger('indi_allsky')
5 |
6 |
7 | class IndiAllSkyFocuserInterface(object):
8 |
9 | def __init__(self, config):
10 | self.config = config
11 |
12 |
13 | focuser_class_str = self.config.get('FOCUSER', {}).get('CLASSNAME', '')
14 |
15 | if not focuser_class_str:
16 | focuser_class_str = 'focuser_simulator'
17 |
18 | focuser_class = getattr(focusers, focuser_class_str)
19 |
20 |
21 | pin1 = self.config.get('FOCUSER', {}).get('GPIO_PIN_1', 'notdefined')
22 | pin2 = self.config.get('FOCUSER', {}).get('GPIO_PIN_2', 'notdefined')
23 | pin3 = self.config.get('FOCUSER', {}).get('GPIO_PIN_3', 'notdefined')
24 | pin4 = self.config.get('FOCUSER', {}).get('GPIO_PIN_4', 'notdefined')
25 |
26 | i2c_address = self.config.get('FOCUSER', {}).get('I2C_ADDRESS', '0x60')
27 |
28 |
29 | self.__focuser = focuser_class(
30 | self.config,
31 | pin_names=[pin1, pin2, pin3, pin4],
32 | i2c_address=i2c_address,
33 | )
34 |
35 |
36 | @property
37 | def focuser(self):
38 | return self.__focuser
39 |
40 |
41 | def move(self, direction, step):
42 | return self.focuser.move(direction, step)
43 |
44 |
45 | def deinit(self):
46 | self.focuser.deinit()
47 |
48 |
--------------------------------------------------------------------------------
/testing/net/openweathermap.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | import requests
5 | from pprint import pformat
6 | import logging
7 |
8 |
9 | LATITUDE = 33.0
10 | LONGITUDE = -84.0
11 |
12 | #UNITS = 'standard'
13 | UNITS = 'metric'
14 |
15 |
16 |
17 | logging.basicConfig(level=logging.INFO)
18 | logger = logging
19 |
20 |
21 | class OpenWeatherMapTest(object):
22 |
23 | def main(self, apikey):
24 | url = 'https://api.openweathermap.org/data/2.5/weather?lat={0:0.1f}&lon={1:0.1f}&units={2:s}&appid={3:s}'.format(LATITUDE, LONGITUDE, UNITS, apikey)
25 | #url = 'https://api.openweathermap.org/data/3.0/onecall?lat={0:0.1f}&lon={1:0.1f}&units={2:s}&appid={3:s}'.format(LATITUDE, LONGITUDE, UNITS, apikey)
26 |
27 | logger.warning('URL: %s', url)
28 | r = requests.get(url, verify=True, timeout=(15.0, 30.0))
29 |
30 | if r.status_code >= 400:
31 | logger.error('URL returned %d', r.status_code)
32 | return
33 |
34 | r_data = r.json()
35 |
36 | logger.info('Response: %s', pformat(r_data))
37 |
38 |
39 | if __name__ == "__main__":
40 | argparser = argparse.ArgumentParser()
41 | argparser.add_argument(
42 | 'apikey',
43 | help='apikey',
44 | type=str,
45 | )
46 |
47 | args = argparser.parse_args()
48 |
49 |
50 | o = OpenWeatherMapTest()
51 | o.main(args.apikey)
52 |
53 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/__init__.py:
--------------------------------------------------------------------------------
1 | from .dewHeaterSimulator import DewHeaterSimulator as dew_heater_simulator
2 | from .dewHeaterPwm import DewHeaterPwm as blinka_dew_heater_pwm
3 | from .dewHeaterStandard import DewHeaterStandard as blinka_dew_heater_standard
4 | from .dewHeaterStandard import DewHeaterStandard as blinka_dew_heater_digital
5 |
6 | from .dewHeaterSoftwarePwm import DewHeaterSoftwarePwmRpiGpio as rpigpio_dew_heater_software_pwm
7 | from .dewHeaterSoftwarePwm import DewHeaterSoftwarePwmGpiozero as gpiozero_dew_heater_software_pwm
8 |
9 | from .dewHeaterMotorKit import DewHeaterMotorKitPwm as motorkit_dew_heater_pwm
10 | from .dewHeaterDockerPi4ChannelRelay import DewHeaterDockerPi4ChannelRelay_I2C as dew_heater_dockerpi_4channel_relay
11 |
12 | from .dewHeaterMqtt import DewHeaterMqttStandard as mqtt_dew_heater_standard
13 | from .dewHeaterMqtt import DewHeaterMqttPwm as mqtt_dew_heater_pwm
14 |
15 | from .dewHeaterSerialPwm import DewHeaterSerialPwm as serial_dew_heater_pwm
16 |
17 |
18 | __all__ = (
19 | 'dew_heater_simulator',
20 | 'blinka_dew_heater_pwm',
21 | 'blinka_dew_heater_standard',
22 | 'blinka_dew_heater_digital', # legacy name
23 | 'dew_heater_dockerpi_4channel_relay',
24 | 'serial_dew_heater_pwm',
25 | 'rpigpio_dew_heater_software_pwm',
26 | 'gpiozero_dew_heater_software_pwm',
27 | 'motorkit_dew_heater_pwm',
28 | 'mqtt_dew_heater_standard',
29 | 'mqtt_dew_heater_pwm',
30 | )
31 |
--------------------------------------------------------------------------------
/testing/json_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | import io
5 | import json
6 | import tempfile
7 | from pprint import pprint
8 | from collections import OrderedDict
9 | import logging
10 |
11 |
12 | logging.basicConfig(level=logging.INFO)
13 | logger = logging
14 |
15 |
16 | class JsonTest(object):
17 |
18 | def __init__(self):
19 | self.data = OrderedDict({
20 | 'int' : 2,
21 | 'float' : 1.1,
22 | 'str' : 'abcd',
23 | 'bool' : True,
24 | 'special' : '°¡¢£¤¥§©«»®°±¹²³µ™¶¼½¾¿÷ƒΔÀËÏÑÜßãäëñöΩπ∞€',
25 | 'special2' : 'ברי צקלה',
26 | })
27 |
28 | def main(self):
29 |
30 | with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.json', encoding='utf-8') as temp_f:
31 | json.dump(
32 | self.data,
33 | temp_f,
34 | indent=4,
35 | ensure_ascii=False,
36 | )
37 |
38 | logger.info('Temp: %s', temp_f.name)
39 |
40 |
41 | with io.open(temp_f.name, 'r', encoding='utf-8') as temp_f:
42 | json_data = json.load(
43 | temp_f,
44 | object_pairs_hook=OrderedDict,
45 | )
46 |
47 |
48 | pprint(json_data)
49 |
50 | print()
51 | print("jq --argjson newfloat 1.2 '.float = $newfloat' {0:s}".format(temp_f.name))
52 |
53 |
54 | if __name__ == "__main__":
55 | JsonTest().main()
56 |
--------------------------------------------------------------------------------
/indi_allsky/timelapse_preprocessor/preProcessorBase.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import logging
3 |
4 | logger = logging.getLogger('indi_allsky')
5 |
6 |
7 | class PreProcessorBase(object):
8 |
9 | def __init__(self, *args, **kwargs):
10 | self.config = args[0]
11 |
12 |
13 | self._seqfolder = None
14 | self._keogram = None
15 | self._pre_scale = 100
16 |
17 |
18 | if self.config.get('IMAGE_FOLDER'):
19 | self.image_dir = Path(self.config['IMAGE_FOLDER']).absolute()
20 | else:
21 | self.image_dir = Path(__file__).parent.parent.joinpath('html', 'images').absolute()
22 |
23 |
24 | @property
25 | def seqfolder(self):
26 | return self._seqfolder
27 |
28 |
29 | @property
30 | def keogram(self):
31 | return self._keogram
32 |
33 | @keogram.setter
34 | def keogram(self, new_keogram):
35 | if isinstance(new_keogram, type(None)):
36 | self._keogram = None
37 | return
38 |
39 | self._keogram = Path(str(new_keogram)).absolute()
40 |
41 |
42 | @property
43 | def pre_scale(self):
44 | return self._pre_scale
45 |
46 | @pre_scale.setter
47 | def pre_scale(self, new_pre_scale):
48 | self._pre_scale = int(new_pre_scale)
49 | #logger.info('Setting timelapse image pre-scaler to %d%%', self._pre_scale)
50 |
51 |
52 | def main(self, *args, **kwargs):
53 | raise Exception()
54 |
55 |
--------------------------------------------------------------------------------
/indi_allsky/filetransfer/__init__.py:
--------------------------------------------------------------------------------
1 | from .paramiko_sftp import paramiko_sftp
2 | from .python_ftp import python_ftp
3 | from .python_ftpes import python_ftpes
4 |
5 | from .pycurl_sftp import pycurl_sftp
6 | from .pycurl_sftp import sftp
7 | from .pycurl_ftp import pycurl_ftp
8 | from .pycurl_ftp import ftp
9 | from .pycurl_ftps import pycurl_ftps
10 | from .pycurl_ftps import ftps
11 | from .pycurl_ftpes import pycurl_ftpes
12 | from .pycurl_ftpes import ftpes
13 | from .pycurl_webdav_https import pycurl_webdav_https
14 |
15 | from .paho_mqtt import paho_mqtt
16 |
17 | from .boto3_s3 import boto3_s3
18 | from .boto3_minio import boto3_minio
19 | from .libcloud_s3 import libcloud_s3
20 | from .gcp_storage import gcp_storage
21 | from .oci_storage import oci_storage
22 |
23 | #from .pycurl_syncapi_v1 import pycurl_syncapi_v1
24 | from .requests_syncapi_v1 import requests_syncapi_v1
25 |
26 | from .youtube_oauth2 import youtube_oauth2
27 |
28 |
29 | __all__ = (
30 | 'paramiko_sftp',
31 | 'python_ftp',
32 | 'python_ftpes',
33 | 'pycurl_sftp',
34 | 'pycurl_ftp',
35 | 'pycurl_ftps',
36 | 'pycurl_ftpes',
37 | 'sftp',
38 | 'ftp',
39 | 'ftps',
40 | 'ftpes',
41 | 'pycurl_webdav_https',
42 |
43 | 'paho_mqtt',
44 |
45 | 'boto3_s3',
46 | 'boto3_minio',
47 | 'libcloud_s3',
48 | 'gcp_storage',
49 | 'oci_storage',
50 |
51 | #'pycurl_syncapi_v1',
52 | 'requests_syncapi_v1',
53 |
54 | 'youtube_oauth2',
55 | )
56 |
--------------------------------------------------------------------------------
/indi_allsky/flask/static/svg/plus-square-dotted.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/cameras.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Cameras{% endblock %}
4 |
5 | {% block head %}
6 |
8 |
11 |
12 | {% endblock %}
13 |
14 | {% block content %}
15 |
16 |
17 |
ID
18 |
Camera
19 |
Connect Date
20 |
Size
21 |
Pixels
22 |
Bits
23 |
Gain
24 |
Exposure
25 |
26 | {% for camera in camera_list %}
27 |
28 |
{{ camera.id }}
29 |
{{ camera.name }}
30 |
{{ camera.connectDate.strftime('%Y-%m-%d %H:%M:%S') }}
31 |
{{ camera.width | int }}x{{ camera.height | int}}
32 |
{{ "%0.2f"|format(camera.pixelSize | float)}}
33 |
{{ camera.bits | int }}
34 |
{{ "%0.2f"|format(camera.minGain | float) }} - {{ "%0.2f"|format(camera.maxGain | float) }}
35 |
{{ "%0.6f"|format(camera.minExposure | float) }} - {{ "%0.1f"|format(camera.maxExposure | float) }}s
36 |
37 | {% endfor %}
38 |
39 | {% endblock %}
40 |
--------------------------------------------------------------------------------
/indi_allsky/devices/sensors/adafruit_mlx90615.py:
--------------------------------------------------------------------------------
1 | from adafruit_bus_device import i2c_device
2 | from micropython import const
3 |
4 | try:
5 | from busio import I2C
6 | except ImportError:
7 | pass
8 |
9 | # imports
10 |
11 | __version__ = "1.0.0"
12 |
13 | # Internal constants:
14 | _MLX90615_I2CADDR = const(0x5B)
15 |
16 | _MLX90615_TA = const(0x26)
17 | _MLX90615_TOBJ1 = const(0x27)
18 |
19 |
20 | class MLX90615:
21 |
22 | def __init__(self, i2c_bus: I2C, address: int = _MLX90615_I2CADDR) -> None:
23 | self._device = i2c_device.I2CDevice(i2c_bus, address)
24 | self.buf = bytearray(2)
25 |
26 |
27 | @property
28 | def ambient_temperature(self) -> float:
29 | """Ambient Temperature in Celsius."""
30 | return self._read_temp(_MLX90615_TA)
31 |
32 |
33 | @property
34 | def object_temperature(self) -> float:
35 | """Object Temperature in Celsius."""
36 | return self._read_temp(_MLX90615_TOBJ1)
37 |
38 |
39 | def _read_temp(self, register: int) -> float:
40 | temp = self._read_16(register)
41 | temp *= 0.02
42 | temp -= 273.15
43 | return temp
44 |
45 |
46 | def _read_16(self, register: int) -> int:
47 | # Read and return a 16-bit unsigned big endian value read from the
48 | # specified 16-bit register address.
49 | with self._device as i2c:
50 | self.buf[0] = register
51 | i2c.write_then_readinto(self.buf, self.buf, out_end=1)
52 | return self.buf[1] << 8 | self.buf[0]
53 |
--------------------------------------------------------------------------------
/docker/Dockerfile.indi_base_ubuntu2404:
--------------------------------------------------------------------------------
1 |
2 | FROM ubuntu:noble AS indi.base
3 |
4 | ARG TZ
5 | ARG INDI_VERSION
6 | ARG LIBCAMERA_TAG
7 | ARG RPICAM_APPS_TAG
8 | ARG DEBIAN_FRONTEND=noninteractive
9 |
10 | USER root
11 | RUN apt-get update
12 | RUN apt-get -y upgrade
13 |
14 | RUN apt-get -y install \
15 | build-essential \
16 | apt-utils \
17 | iproute2 \
18 | i2c-tools \
19 | locales \
20 | tzdata \
21 | procps \
22 | sudo \
23 | git \
24 | jq \
25 | software-properties-common
26 |
27 |
28 | RUN add-apt-repository -y ppa:mutlaqja/ppa
29 |
30 | # Only need indi development package here
31 | RUN apt-get -y install \
32 | libindi-dev
33 |
34 |
35 | # cleanup
36 | RUN apt-get clean
37 |
38 |
39 | # update timezone
40 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
41 | RUN dpkg-reconfigure -f noninteractive tzdata
42 |
43 |
44 | # need the uid and gid to match in all containers
45 | RUN groupadd --gid 10001 allsky
46 | RUN useradd --create-home --no-user-group --uid 10001 --gid allsky --groups dialout,video,adm --home-dir /home/allsky --shell /bin/bash allsky
47 | RUN if getent group gpio >/dev/null 2>&1; then usermod -a -G gpio allsky; fi
48 | RUN if getent group i2c >/dev/null 2>&1; then usermod -a -G i2c allsky; fi
49 | RUN if getent group spi >/dev/null 2>&1; then usermod -a -G spi allsky; fi
50 |
51 | RUN echo "allsky ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/allsky
52 | RUN chmod 0440 /etc/sudoers.d/allsky
53 |
54 |
55 | #USER allsky
56 | #WORKDIR /home/allsky
57 |
58 | #RUN rm -fR /home/allsky/Projects
59 |
--------------------------------------------------------------------------------
/testing/nm/nm-settings.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | ### Alternative
5 | # nmcli -f all c show
6 | # busctl -j call org.freedesktop.NetworkManager /org/freedesktop/NetworkManager/Settings/13 org.freedesktop.NetworkManager.Settings.Connection GetSettings
7 |
8 |
9 | import dbus
10 | import logging
11 |
12 |
13 | logging.basicConfig(level=logging.INFO)
14 | logger = logging
15 |
16 |
17 |
18 | class NetworkManagerSettings(object):
19 |
20 | def main(self):
21 | bus = dbus.SystemBus()
22 |
23 |
24 | nm_settings = bus.get_object("org.freedesktop.NetworkManager",
25 | "/org/freedesktop/NetworkManager/Settings")
26 |
27 |
28 | settingspath_list = nm_settings.Get("org.freedesktop.NetworkManager.Settings",
29 | "Connections",
30 | dbus_interface=dbus.PROPERTIES_IFACE)
31 |
32 |
33 | for settings_path in settingspath_list:
34 | settings = bus.get_object("org.freedesktop.NetworkManager",
35 | settings_path)
36 |
37 |
38 | settings_connection = dbus.Interface(settings,
39 | "org.freedesktop.NetworkManager.Settings.Connection")
40 |
41 | settings_dict = settings_connection.GetSettings()
42 |
43 |
44 | settings_dict = settings_connection.GetSettings()
45 | logger.info('Settings: %s', settings_dict)
46 |
47 |
48 | if __name__ == "__main__":
49 | NetworkManagerSettings().main()
50 |
51 |
--------------------------------------------------------------------------------
/testing/astrometrics/sun_moon_sep.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import math
4 | from datetime import datetime
5 | from datetime import timedelta
6 | from datetime import timezone
7 | import ephem
8 | import logging
9 |
10 | logging.basicConfig(level=logging.INFO)
11 | logger = logging
12 |
13 |
14 |
15 | LATITUDE = 33
16 | LONGITUDE = -84
17 |
18 |
19 | obs = ephem.Observer()
20 | obs.lon = math.radians(LONGITUDE)
21 | obs.lat = math.radians(LATITUDE)
22 |
23 | #obs.elevation = -6371000 # center of earth
24 | #obs.pressure = 0 # disable atmospheric diffraction
25 |
26 | sun = ephem.Sun()
27 | moon = ephem.Moon()
28 |
29 |
30 | utcnow = datetime.now(tz=timezone.utc) - timedelta(days=30)
31 |
32 | for x in range(40000):
33 | utcnow = utcnow + timedelta(hours=1)
34 |
35 | obs.date = utcnow
36 |
37 | sun.compute(obs)
38 | moon.compute(obs)
39 |
40 | sun_alt = math.degrees(sun.alt)
41 |
42 | moon_phase = moon.moon_phase * 100.0
43 |
44 | # separation of 1-3 degrees means a possible eclipse
45 | sun_moon_sep = abs((ephem.separation(moon, sun) / (math.pi / 180)) - 180)
46 |
47 | if sun_moon_sep < 1.25:
48 | # Lunar
49 |
50 | if sun_alt > -6:
51 | # not night time
52 | continue
53 |
54 | logger.info('Lunar: %s, separation: %0.3f, phase %0.2f', utcnow, sun_moon_sep, moon_phase)
55 | if sun_moon_sep > 179.0:
56 | # Solar
57 |
58 | if sun_alt < -6:
59 | # not day time
60 | continue
61 |
62 | logger.info('Solar: %s, separation: %0.3f, phase %0.2f', utcnow, sun_moon_sep, moon_phase)
63 |
64 |
--------------------------------------------------------------------------------
/requirements/requirements_latest_armv6l.txt:
--------------------------------------------------------------------------------
1 | ### This file is targeted to Raspbian/Debian 11 and Python 3.9 (armv7l)
2 | # https://www.piwheels.org/project/astropy/
3 | astropy
4 | # https://www.piwheels.org/project/numpy/
5 | numpy >= 1.22.2, <= 1.24.3
6 | # https://www.piwheels.org/project/opencv-python-headless/
7 | opencv-python-headless <= 4.6.0.66
8 | # https://www.piwheels.org/project/scipy/
9 | # vulnerable https://www.cve.org/CVERecord?id=CVE-2023-25399
10 | scipy <= 1.8.1
11 | # https://www.piwheels.org/project/ccdproc/
12 | ccdproc
13 | # https://www.piwheels.org/project/scikit-image/
14 | scikit-image <= 0.19.3
15 | astroalign
16 | bottleneck
17 | python-dateutil
18 | ephem
19 | skyfield
20 | paramiko
21 | # https://www.piwheels.org/project/pycurl/
22 | pycurl
23 | # https://www.piwheels.org/project/Pillow/
24 | Pillow
25 | piexif
26 | imageio
27 | imageio-ffmpeg
28 | simplejpeg
29 | # https://www.piwheels.org/project/imagecodecs/
30 | #imagecodecs
31 | cython
32 | #rawpy # installed later
33 | pygifsicle
34 | gunicorn[gthread] >= 23.0.0
35 | inotify
36 | psutil
37 | Flask >= 3.1.1
38 | Flask-SQLAlchemy >= 3.1.1
39 | Flask-Migrate >= 4.0.5
40 | Flask-WTF >= 1.2.1
41 | Flask-Login >= 0.6.3
42 | werkzeug
43 | is-safe-url
44 | certifi >= 2023.5.7
45 | # https://www.piwheels.org/project/cryptography/
46 | # segfault in newer versions
47 | cryptography == 37.0.4
48 | dbus-python
49 | paho-mqtt >= 2.0.0
50 | setuptools-rust
51 | # https://www.piwheels.org/project/bcrypt/
52 | # segfault in newer versions
53 | bcrypt == 3.2.2
54 | passlib[argon2] >= 1.7.4
55 | prettytable
56 | lxml
57 | shapely
58 | requests-toolbelt
59 | fish2pano
60 | pytz
61 | mysql-connector-python
62 |
--------------------------------------------------------------------------------
/requirements/requirements_latest_32.txt:
--------------------------------------------------------------------------------
1 | ### This file is targeted to Raspbian/Debian 12 and Python 3.10+
2 | # https://www.wheelodex.org/projects/astropy/
3 | astropy >= 6.1.0
4 | pyerfa >= 2.0.1.4
5 | # https://www.wheelodex.org/projects/numpy/
6 | numpy >= 2.1.0
7 | # https://www.wheelodex.org/projects/opencv-python-headless/
8 | opencv-python-headless >= 4.10.0.84
9 | # https://www.wheelodex.org/projects/scipy/
10 | scipy >= 1.11.3
11 | # https://www.wheelodex.org/projects/ccdproc/
12 | ccdproc >= 2.4.2
13 | # https://www.wheelodex.org/projects/scikit-image/
14 | scikit-image >= 0.25.0
15 | astroalign >= 2.6.0
16 | bottleneck >= 1.4.0
17 | python-dateutil
18 | ephem
19 | skyfield
20 | paramiko
21 | # https://www.wheelodex.org/projects/pycurl/ or https://www.piwheels.org/project/pycurl/
22 | pycurl
23 | # https://www.wheelodex.org/projects/pillow/ or https://www.piwheels.org/project/Pillow/
24 | Pillow
25 | piexif
26 | imageio
27 | imageio-ffmpeg
28 | simplejpeg
29 | # https://www.wheelodex.org/projects/imagecodecs/
30 | #imagecodecs
31 | cython
32 | #rawpy # installed later
33 | pygifsicle
34 | gunicorn[gthread] >= 23.0.0
35 | inotify
36 | psutil
37 | Flask >= 3.1.1
38 | Flask-SQLAlchemy >= 3.1.1
39 | Flask-Migrate >= 4.0.5
40 | Flask-WTF >= 1.2.1
41 | Flask-Login >= 0.6.3
42 | werkzeug
43 | is-safe-url
44 | certifi >= 2024.7.4
45 | # https://www.wheelodex.org/projects/cryptography/
46 | cryptography >= 44.0.1
47 | dbus-python
48 | paho-mqtt >= 2.0.0
49 | setuptools-rust
50 | # https://www.wheelodex.org/projects/bcrypt/
51 | bcrypt
52 | passlib[argon2] >= 1.7.4
53 | prettytable
54 | lxml
55 | shapely >= 2.0.6
56 | requests-toolbelt
57 | fish2pano >= 0.4.1
58 | pytz
59 | mysql-connector-python
60 |
--------------------------------------------------------------------------------
/indi_allsky/flask/misc.py:
--------------------------------------------------------------------------------
1 | from functools import wraps
2 |
3 | from flask import current_app as app
4 |
5 | from flask_login import current_user
6 |
7 |
8 | def login_optional(func):
9 | @wraps(func)
10 | def decorated_view(*args, **kwargs):
11 | if app.config.get("LOGIN_DISABLED"):
12 | pass
13 | elif not app.config.get('INDI_ALLSKY_AUTH_ALL_VIEWS'):
14 | # allow some views to be unauthenticated
15 | pass
16 | elif not current_user.is_authenticated:
17 | return app.login_manager.unauthorized()
18 |
19 | # flask 1.x compatibility
20 | # current_app.ensure_sync is only available in Flask >= 2.0
21 | if callable(getattr(app, "ensure_sync", None)):
22 | return app.ensure_sync(func)(*args, **kwargs)
23 | return func(*args, **kwargs)
24 |
25 | return decorated_view
26 |
27 |
28 | def login_optional_media(func):
29 | @wraps(func)
30 | def decorated_view(*args, **kwargs):
31 | if app.config.get("LOGIN_DISABLED"):
32 | pass
33 | elif not app.config.get('INDI_ALLSKY_AUTH_ALL_VIEWS') and not app.config.get('INDI_ALLSKY_AUTH_MEDIA_VIEWS'):
34 | # allow some views to be unauthenticated
35 | pass
36 | elif not current_user.is_authenticated:
37 | return app.login_manager.unauthorized()
38 |
39 | # flask 1.x compatibility
40 | # current_app.ensure_sync is only available in Flask >= 2.0
41 | if callable(getattr(app, "ensure_sync", None)):
42 | return app.ensure_sync(func)(*args, **kwargs)
43 | return func(*args, **kwargs)
44 |
45 | return decorated_view
46 |
47 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanStandard.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .fanBase import FanBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class FanStandard(FanBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(FanStandard, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | import digitalio
20 |
21 | logger.info('Initializing standard FAN device')
22 |
23 | pin1 = getattr(board, pin_1_name)
24 |
25 | self.pin = digitalio.DigitalInOut(pin1)
26 | self.pin.direction = digitalio.Direction.OUTPUT
27 |
28 |
29 | if invert_output:
30 | logger.warning('Fan logic reversed')
31 | self.ON = 0
32 | self.OFF = 1
33 | else:
34 | self.ON = 1
35 | self.OFF = 0
36 |
37 |
38 | self._state = 0
39 |
40 | time.sleep(1.0)
41 |
42 |
43 | @property
44 | def state(self):
45 | return self._state
46 |
47 |
48 | @state.setter
49 | def state(self, new_state):
50 | # any positive value is ON
51 | new_state_b = bool(new_state)
52 |
53 | if new_state_b:
54 | logger.warning('Set fan state: 100%')
55 | self.pin.value = self.ON
56 | self._state = 100
57 | else:
58 | logger.warning('Set fan state: 0%')
59 | self.pin.value = self.OFF
60 | self._state = 0
61 |
62 |
63 | def disable(self):
64 | self.state = 0
65 |
66 |
67 | def deinit(self):
68 | super(FanStandard, self).deinit()
69 | self.pin.deinit()
70 |
71 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/mask.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Mask Base{% endblock %}
4 |
5 | {% block head %}
6 |
7 |
16 |
20 | {% endblock %}
21 |
22 | {% block content %}
23 |
24 |
25 |
26 |
27 | This image is the original camera output. It is not rotated, flipped, or cropped.
28 |
29 |
30 |
![]()
31 |
32 |
33 | Generated: {{ mask_date }}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
65 |
66 | {% endblock %}
67 |
--------------------------------------------------------------------------------
/requirements/requirements_debian11.txt:
--------------------------------------------------------------------------------
1 | ### This file is targeted to Raspbian/Debian 11 and Python 3.9
2 | # https://www.wheelodex.org/projects/astropy/
3 | astropy >= 5.3.3, <= 6.0.1
4 | # https://www.wheelodex.org/projects/numpy/
5 | numpy >= 1.22.2, < 2.0
6 | # https://www.wheelodex.org/projects/opencv-python-headless/
7 | opencv-python-headless >= 4.8.1.78, <= 4.11.0.86
8 | # https://www.wheelodex.org/projects/scipy/
9 | scipy >= 1.11.3, <= 1.13.1
10 | # https://www.wheelodex.org/projects/ccdproc/
11 | ccdproc
12 | # https://www.wheelodex.org/projects/scikit-image/
13 | scikit-image <= 0.19.3
14 | astroalign
15 | bottleneck
16 | python-dateutil
17 | ephem
18 | skyfield
19 | paramiko
20 | # https://www.wheelodex.org/projects/pycurl/ or https://www.piwheels.org/project/pycurl/
21 | pycurl
22 | # https://www.wheelodex.org/projects/pillow/ or https://www.piwheels.org/project/Pillow/
23 | Pillow
24 | piexif
25 | imageio
26 | imageio-ffmpeg
27 | simplejpeg ; platform_machine == 'x86_64' or 'aarch64' in platform_machine
28 | simplejpeg <= 1.7.2 ; 'arm' in platform_machine
29 | # https://www.wheelodex.org/projects/imagecodecs/
30 | #imagecodecs
31 | cython
32 | #rawpy # not needed
33 | pygifsicle
34 | gunicorn[gthread] >= 23.0.0
35 | inotify
36 | psutil
37 | Flask >= 3.1.1
38 | Flask-SQLAlchemy >= 3.1.1
39 | Flask-Migrate >= 4.0.5
40 | Flask-WTF >= 1.2.1
41 | Flask-Login >= 0.6.3
42 | werkzeug
43 | is-safe-url
44 | certifi >= 2024.7.4
45 | # https://www.wheelodex.org/projects/cryptography/
46 | cryptography <= 42.0.8
47 | dbus-python
48 | paho-mqtt >= 2.0.0
49 | setuptools-rust
50 | # https://www.wheelodex.org/projects/bcrypt/
51 | bcrypt
52 | passlib[argon2] >= 1.7.4
53 | prettytable
54 | lxml
55 | shapely
56 | requests-toolbelt
57 | fish2pano >= 0.4.1
58 | pytz
59 | mysql-connector-python
60 |
--------------------------------------------------------------------------------
/requirements/requirements_latest.txt:
--------------------------------------------------------------------------------
1 | ### This file is targeted to Raspbian/Debian 12 and Python 3.10+
2 | # https://www.wheelodex.org/projects/astropy/
3 | astropy >= 6.1.0
4 | pyerfa >= 2.0.1.4
5 | # https://www.wheelodex.org/projects/numpy/
6 | numpy >= 2.1.0
7 | # https://www.wheelodex.org/projects/opencv-python-headless/
8 | opencv-python-headless >= 4.10.0.84
9 | # https://www.wheelodex.org/projects/scipy/
10 | scipy >= 1.11.3
11 | # https://www.wheelodex.org/projects/ccdproc/
12 | ccdproc >= 2.4.2
13 | # https://www.wheelodex.org/projects/scikit-image/
14 | scikit-image >= 0.25.0
15 | astroalign >= 2.6.0
16 | bottleneck >= 1.4.0
17 | python-dateutil
18 | ephem
19 | skyfield
20 | paramiko
21 | # https://www.wheelodex.org/projects/pycurl/ or https://www.piwheels.org/project/pycurl/
22 | pycurl
23 | # https://www.wheelodex.org/projects/pillow/ or https://www.piwheels.org/project/Pillow/
24 | Pillow
25 | piexif
26 | imageio
27 | imageio-ffmpeg
28 | simplejpeg
29 | # https://www.wheelodex.org/projects/imagecodecs/
30 | #imagecodecs
31 | cython
32 | rawpy >= 0.23.0 ; platform_machine == 'x86_64' or 'aarch64' in platform_machine
33 | # rawpy installed later on arm 32-bit
34 | pygifsicle
35 | gunicorn[gthread] >= 23.0.0
36 | inotify
37 | psutil
38 | Flask >= 3.1.1
39 | Flask-SQLAlchemy >= 3.1.1
40 | Flask-Migrate >= 4.0.5
41 | Flask-WTF >= 1.2.1
42 | Flask-Login >= 0.6.3
43 | werkzeug
44 | is-safe-url
45 | certifi >= 2024.7.4
46 | # https://www.wheelodex.org/projects/cryptography/
47 | cryptography >= 44.0.1
48 | dbus-python
49 | paho-mqtt >= 2.0.0
50 | setuptools-rust
51 | # https://www.wheelodex.org/projects/bcrypt/
52 | bcrypt
53 | passlib[argon2] >= 1.7.4
54 | prettytable
55 | lxml
56 | shapely >= 2.0.6
57 | requests-toolbelt
58 | fish2pano >= 0.4.1
59 | pytz
60 | mysql-connector-python
61 |
--------------------------------------------------------------------------------
/indi_allsky/devices/controllers/dockerpi.py:
--------------------------------------------------------------------------------
1 | from micropython import const
2 | import adafruit_bus_device.i2c_device as i2cdevice
3 |
4 |
5 | try:
6 | from busio import I2C
7 | except ImportError:
8 | pass
9 |
10 |
11 | class DockerPi4ChannelRelay(object):
12 |
13 | RELAY1 = const(0x01)
14 | RELAY2 = const(0x02)
15 | RELAY3 = const(0x03)
16 | RELAY4 = const(0x04)
17 |
18 | RELAY_OFF = const(0x00)
19 | RELAY_ON = const(0xFF)
20 |
21 | _relay_list = (RELAY1, RELAY2, RELAY3, RELAY4)
22 | _state_list = (RELAY_OFF, RELAY_ON)
23 |
24 |
25 | def __init__(self, i2c_bus: I2C, address: int = 0x10) -> None:
26 | self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
27 |
28 | self._buffer = bytearray(2)
29 |
30 |
31 | self._relay_states = {
32 | self.RELAY1 : 0,
33 | self.RELAY2 : 0,
34 | self.RELAY3 : 0,
35 | self.RELAY4 : 0,
36 | }
37 |
38 | # set all relays off
39 | for relay in self._relay_list:
40 | self.set_relay(relay, False)
41 |
42 |
43 | def get_relay(self, relay):
44 | if relay not in self._relay_list:
45 | raise ValueError('Invalid relay')
46 |
47 | return self._relay_states[relay]
48 |
49 |
50 | def set_relay(self, relay, new_state):
51 | if relay not in self._relay_list:
52 | raise ValueError('Invalid relay')
53 |
54 |
55 | self._buffer[0] = relay
56 |
57 | if new_state:
58 | self._buffer[1] = self.RELAY_ON
59 | else:
60 | self._buffer[1] = self.RELAY_OFF
61 |
62 |
63 | with self.i2c_device as i2c:
64 | i2c.write(self._buffer, end=2)
65 |
66 | self._relay_states[relay] = int(new_state)
67 |
--------------------------------------------------------------------------------
/testing/astrometrics/transit_longitude_test.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | # The theory behind timezones is the sun should be relatively near zenith at noon
4 | # If there is significant (6+ hours) offset between noon and the suns next transit, the longitude is probably wrong
5 |
6 |
7 | import math
8 | from datetime import datetime
9 | import ephem
10 | import logging
11 |
12 |
13 | LATITUDE = 33
14 | LONGITUDE = -85
15 |
16 |
17 |
18 | logging.basicConfig(level=logging.INFO)
19 | logger = logging
20 |
21 |
22 | class EquinoxTest(object):
23 |
24 | def main(self):
25 | now = datetime.now()
26 | utc_offset = now.astimezone().utcoffset()
27 |
28 |
29 | noon = datetime.strptime(now.strftime('%Y%m%d12'), '%Y%m%d%H')
30 | midnight = datetime.strptime(now.strftime('%Y%m%d00'), '%Y%m%d%H')
31 | midnight_utc = midnight - utc_offset
32 |
33 |
34 | obs = ephem.Observer()
35 | obs.lon = math.radians(LONGITUDE)
36 | obs.lat = math.radians(LATITUDE)
37 |
38 | sun = ephem.Sun()
39 |
40 | obs.date = midnight_utc
41 | sun.compute(obs)
42 |
43 |
44 | next_sun_transit = obs.next_transit(sun)
45 | local_next_sun_transit = next_sun_transit.datetime() + utc_offset
46 |
47 |
48 | if noon > local_next_sun_transit:
49 | transit_noon_diff_hours = (noon - local_next_sun_transit).seconds / 3600
50 | else:
51 | transit_noon_diff_hours = (local_next_sun_transit - noon).seconds / 3600
52 |
53 |
54 | logger.info('Sun will transit: %s', str(local_next_sun_transit))
55 | logger.info('Noon: %s', str(noon))
56 | logger.info('Diff: %0.1f', transit_noon_diff_hours)
57 |
58 |
59 | if __name__ == "__main__":
60 | EquinoxTest().main()
61 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterStandard.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .dewHeaterBase import DewHeaterBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class DewHeaterStandard(DewHeaterBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(DewHeaterStandard, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | import digitalio
20 |
21 | logger.info('Initializing standard DEW HEATER device')
22 |
23 | pin1 = getattr(board, pin_1_name)
24 |
25 | self.pin = digitalio.DigitalInOut(pin1)
26 | self.pin.direction = digitalio.Direction.OUTPUT
27 |
28 |
29 | if invert_output:
30 | logger.warning('Dew heater logic reversed')
31 | self.ON = 0
32 | self.OFF = 1
33 | else:
34 | self.ON = 1
35 | self.OFF = 0
36 |
37 |
38 | self._state = -1
39 |
40 | time.sleep(1.0)
41 |
42 |
43 | @property
44 | def state(self):
45 | return self._state
46 |
47 |
48 | @state.setter
49 | def state(self, new_state):
50 | # any positive value is ON
51 | new_state_b = bool(new_state)
52 |
53 | if new_state_b:
54 | logger.warning('Set dew heater state: 100%')
55 | self.pin.value = self.ON
56 | self._state = 100
57 | else:
58 | logger.warning('Set dew heater state: 0%')
59 | self.pin.value = self.OFF
60 | self._state = 0
61 |
62 |
63 | def disable(self):
64 | self.state = 0
65 |
66 |
67 | def deinit(self):
68 | super(DewHeaterStandard, self).deinit()
69 | self.pin.deinit()
70 |
71 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/taskqueue.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Task Queue{% endblock %}
4 |
5 | {% block head %}
6 |
8 |
9 |
10 |
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
19 |
20 |
21 |
22 | | ID |
23 | Date |
24 | Queue |
25 | Action |
26 | State |
27 | Result |
28 |
29 |
30 |
31 | {% for task in task_list %}
32 |
33 | | {{ task.id }} |
34 | {{ task.createDate.strftime('%Y-%m-%d %H:%M:%S') }} |
35 | {{ task.queue }} |
36 | {{ task.action }} |
37 | {{ task.state }} |
38 | {{ task.result }} |
39 |
40 | {% endfor %}
41 |
42 |
43 |
44 |
45 |
67 | {% endblock %}
68 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/users.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Users{% endblock %}
4 |
5 | {% block head %}
6 |
8 |
9 |
10 |
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
19 |
20 |
21 |
22 | | Create |
23 | Username |
24 | Name |
25 | Active |
26 | Staff |
27 | Admin |
28 |
29 |
30 |
31 | {% for user in user_list %}
32 |
33 | | {{ user.createDate.strftime('%Y-%m-%d %H:%M:%S') }} |
34 | {{ user.username }} |
35 | {{ user.name }} |
36 | {{ user.active }} |
37 | {{ user.staff }} |
38 | {{ user.admin }} |
39 |
40 | {% endfor %}
41 |
42 |
43 |
44 |
45 |
67 |
68 |
69 | {% endblock %}
70 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanPwm.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .fanBase import FanBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class FanPwm(FanBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(FanPwm, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 | self.invert_output = kwargs['invert_output']
17 |
18 | import board
19 | import pwmio
20 |
21 | logger.info('Initializing PWM FAN device')
22 |
23 | if self.invert_output:
24 | logger.warning('Fan logic reversed')
25 |
26 | pwm_pin = getattr(board, pin_1_name)
27 |
28 | self.pwm = pwmio.PWMOut(pwm_pin)
29 |
30 | self._state = -1
31 |
32 | time.sleep(1.0)
33 |
34 |
35 | @property
36 | def state(self):
37 | return self._state
38 |
39 |
40 | @state.setter
41 | def state(self, new_state):
42 | # duty cycle must be a percentage between 0 and 100
43 | new_state_i = int(new_state)
44 |
45 | if new_state_i < 0:
46 | logger.error('Duty cycle must be 0 or greater')
47 | return
48 |
49 | if new_state_i > 100:
50 | logger.error('Duty cycle must be 100 or less')
51 | return
52 |
53 |
54 | if self.invert_output:
55 | new_duty_cycle = int(((2 ** 16) - 1) * (100 - new_state_i) / 100)
56 | else:
57 | new_duty_cycle = int(((2 ** 16) - 1) * new_state_i / 100)
58 |
59 |
60 | logger.warning('Set fan state: %d%%', new_state_i)
61 | self.pwm.duty_cycle = new_duty_cycle
62 |
63 | self._state = new_state_i
64 |
65 |
66 | def disable(self):
67 | self.state = 0
68 |
69 |
70 | def deinit(self):
71 | super(FanPwm, self).deinit()
72 | self.pwm.deinit()
73 |
74 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/notifications.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Notifications{% endblock %}
4 |
5 | {% block head %}
6 |
8 |
9 |
10 |
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
19 |
20 |
21 |
22 | | ID |
23 | Category |
24 | Create |
25 | Expire |
26 | Acked |
27 | Notification |
28 |
29 |
30 |
31 | {% for notice in notice_list %}
32 |
33 | | {{ notice.id }} |
34 | {{ notice.category }} |
35 | {{ notice.createDate.strftime('%Y-%m-%d %H:%M:%S') }} |
36 | {{ notice.expireDate.strftime('%Y-%m-%d %H:%M:%S') }} |
37 | {{ notice.ack }} |
38 | {{ notice.notification }} |
39 |
40 | {% endfor %}
41 |
42 |
43 |
44 |
45 |
67 |
68 | {% endblock %}
69 |
--------------------------------------------------------------------------------
/service/cgi_indi-allsky.py:
--------------------------------------------------------------------------------
1 | #!/home/pi/indi-allsky/virtualenv/indi-allsky/bin/python3
2 |
3 | #################################
4 | # CGI interface for indi-allsky #
5 | # #
6 | # WARNING: very slow #
7 | #################################
8 |
9 | ### Setup ###
10 | # cp service/cgi_indi-allsky.py /var/www/html/cgi-bin
11 | # chmod 755 /var/www/html/cgi-bin/cgi_indi-allsky.py
12 | #####################
13 |
14 | ### Apache config ###
15 | #
16 | # Require all granted
17 | # Options -Indexes
18 | #
19 | #
20 | #
21 | # Require all granted
22 | # Options +ExecCGI
23 | #
24 | #
25 | #
26 | # Require all granted
27 | # Options -Indexes
28 | #
29 | #
30 | # Alias /ia/indi-allsky/images /var/www/html/allsky/images
31 | # Alias /ia/indi-allsky/static /home/aaron/git/indi-allsky/indi_allsky/flask/static
32 | #
33 | # RewriteCond %{REQUEST_URI} ^/ia/.*/images/ [OR]
34 | # RewriteCond %{REQUEST_URI} ^/ia/.*/static/
35 | # RewriteRule .* - [L]
36 | #
37 | # RewriteCond %{REQUEST_URI} ^/ia
38 | # RewriteRule ^/ia(.*)$ /cgi-bin/cgi_indi-allsky.py/$1 [L]
39 | #
40 | # AddHandler cgi-script .py
41 | #####################
42 |
43 | ### URL will be something like https://hostname/ia/indi-allsky/
44 |
45 | import os # noqa: F401
46 | import sys
47 | from wsgiref.handlers import CGIHandler
48 |
49 |
50 | # This path needs to be where indi-allsky is installed
51 | sys.path.append('/home/pi/indi-allsky')
52 |
53 | # If the flask config is not located at /etc/indi-allsky/flask.json
54 | #os.environ['INDI_ALLSKY_FLASK_CONFIG'] = '/home/pi/flask.json'
55 |
56 |
57 | from indi_allsky.flask import create_app
58 | application = create_app()
59 |
60 |
61 | CGIHandler().run(application)
62 |
63 |
--------------------------------------------------------------------------------
/misc/add_indi_allsky_pth.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | #####################################################################
3 | # This script adds the indi-allsky virtualenv to the sys.path for #
4 | # the current user #
5 | #####################################################################
6 |
7 |
8 | import sys
9 | import site
10 | import io
11 | from pathlib import Path
12 | import logging
13 |
14 |
15 | logging.basicConfig(level=logging.INFO)
16 | logger = logging
17 |
18 |
19 | class SetupUserSitePth(object):
20 |
21 | def main(self):
22 | indi_allsky_dir = Path(__file__).parent.parent.absolute()
23 | logger.info('indi-allsky folder: %s', indi_allsky_dir)
24 |
25 |
26 | venv_p = Path(__file__).parent.parent.joinpath('virtualenv', 'indi-allsky').absolute()
27 | if not venv_p.is_dir():
28 | logger.error('indi-allsky virtualenv is not created')
29 | sys.exit(1)
30 |
31 | #logger.info('Virtualenv folder: %s', venv_p)
32 |
33 |
34 | venv_site_packages_p = venv_p.joinpath('lib', 'python{0:d}.{1:d}'.format(*sys.version_info), 'site-packages')
35 | if not venv_site_packages_p.is_dir():
36 | logger.error('Cannot find virtualenv site-package folder')
37 | sys.exit() # normal exit
38 |
39 |
40 | user_site_p = Path(site.getusersitepackages())
41 | logger.info('User Site Package Dir: %s', user_site_p)
42 |
43 |
44 | if not user_site_p.is_dir():
45 | user_site_p.mkdir(parents=True)
46 |
47 |
48 | pth_file = user_site_p.joinpath('indi-allsky.pth')
49 | logger.info('Creating pth: %s', pth_file)
50 |
51 | with io.open(pth_file, 'w') as f_pth:
52 | f_pth.write(str(venv_site_packages_p))
53 |
54 |
55 | pth_file.chmod(0o644)
56 |
57 |
58 | if __name__ == "__main__":
59 | SetupUserSitePth().main()
60 |
--------------------------------------------------------------------------------
/indi_allsky/devices/focusers/focuserSerial28byj.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import time
3 | import logging
4 |
5 | from .focuserBase import FocuserBase
6 | from ..exceptions import DeviceControlException
7 |
8 | logger = logging.getLogger('indi_allsky')
9 |
10 |
11 | class FocuserSerial28byj(FocuserBase):
12 |
13 | BAUD_RATE = 9600
14 |
15 | # override in child class
16 | STEP_DEGREES = {}
17 |
18 |
19 | def __init__(self, *args, **kwargs):
20 | super(FocuserSerial28byj, self).__init__(*args, **kwargs)
21 |
22 | pin_names = kwargs['pin_names']
23 |
24 | serial_port_name = pin_names[0]
25 |
26 | self.serial_port = Path('/dev').joinpath(serial_port_name)
27 |
28 | if not self.serial_port.exists():
29 | raise DeviceControlException('Serial port does not exist: {0:s}'.format(str(self.serial_port)))
30 |
31 |
32 | def move(self, direction, degrees):
33 | import serial
34 |
35 | steps = self.STEP_DEGREES[degrees]
36 |
37 |
38 | if direction == 'ccw':
39 | steps *= -1 # negative for CCW
40 |
41 |
42 | try:
43 | self.send_serial_stepper(steps)
44 | except serial.SerialException as e:
45 | raise DeviceControlException(str(e)) from e
46 |
47 |
48 | # need to give the stepper a chance to move
49 | time.sleep((abs(degrees) / 10))
50 |
51 |
52 | return steps
53 |
54 |
55 | def send_serial_stepper(self, steps):
56 | import serial
57 |
58 | with serial.Serial(str(self.serial_port), self.BAUD_RATE, timeout=1) as ser:
59 | ser.write(('S' + str(steps) + '\n').encode())
60 |
61 |
62 | class FocuserSerial28byj_64(FocuserSerial28byj):
63 | # 1/64 ratio
64 |
65 | STEP_DEGREES = {
66 | 6 : 8,
67 | 12 : 17,
68 | 24 : 34,
69 | 45 : 64,
70 | 90 : 128,
71 | 180 : 256,
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterPwm.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .dewHeaterBase import DewHeaterBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class DewHeaterPwm(DewHeaterBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(DewHeaterPwm, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 | self.invert_output = kwargs['invert_output']
17 |
18 | import board
19 | import pwmio
20 |
21 | logger.info('Initializing PWM DEW HEATER device')
22 |
23 | if self.invert_output:
24 | logger.warning('Dew heater logic reversed')
25 |
26 | pwm_pin = getattr(board, pin_1_name)
27 |
28 | self.pwm = pwmio.PWMOut(pwm_pin)
29 |
30 | self._state = -1
31 |
32 | time.sleep(1.0)
33 |
34 |
35 | @property
36 | def state(self):
37 | return self._state
38 |
39 |
40 | @state.setter
41 | def state(self, new_state):
42 | # duty cycle must be a percentage between 0 and 100
43 | new_state_i = int(new_state)
44 |
45 | if new_state_i < 0:
46 | logger.error('Duty cycle must be 0 or greater')
47 | return
48 |
49 | if new_state_i > 100:
50 | logger.error('Duty cycle must be 100 or less')
51 | return
52 |
53 |
54 | if self.invert_output:
55 | new_duty_cycle = int(((2 ** 16) - 1) * (100 - new_state_i) / 100)
56 | else:
57 | new_duty_cycle = int(((2 ** 16) - 1) * new_state_i / 100)
58 |
59 |
60 | logger.warning('Set dew heater state: %d%%', new_state_i)
61 | self.pwm.duty_cycle = new_duty_cycle
62 |
63 | self._state = new_state_i
64 |
65 |
66 | def disable(self):
67 | self.state = 0
68 |
69 |
70 | def deinit(self):
71 | super(DewHeaterPwm, self).deinit()
72 | self.pwm.deinit()
73 |
74 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/gpioStandard.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .genericBase import GenericBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class GpioStandard(GenericBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(GpioStandard, self).__init__(*args, **kwargs)
14 |
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | import digitalio
20 |
21 | logger.info('Initializing standard GPIO device')
22 |
23 | pin1 = getattr(board, pin_1_name)
24 |
25 | self.pin = digitalio.DigitalInOut(pin1)
26 | self.pin.direction = digitalio.Direction.OUTPUT
27 |
28 |
29 | if invert_output:
30 | logger.warning('GPIO logic reversed')
31 | self.ON = 0
32 | self.OFF = 1
33 | self.ON_LEVEL = 'low'
34 | self.OFF_LEVEL = 'high'
35 | else:
36 | self.ON = 1
37 | self.OFF = 0
38 | self.ON_LEVEL = 'high'
39 | self.OFF_LEVEL = 'low'
40 |
41 |
42 | self._state = -1
43 |
44 | time.sleep(1.0)
45 |
46 |
47 | @property
48 | def state(self):
49 | return self._state
50 |
51 |
52 | @state.setter
53 | def state(self, new_state):
54 | # any positive value is ON
55 | new_state_b = bool(new_state)
56 |
57 | if new_state_b:
58 | logger.warning('Set GPIO state: ON (%s)', self.ON_LEVEL)
59 | self.pin.value = self.ON
60 | self._state = 1
61 | else:
62 | logger.warning('Set GPIO state: OFF (%s)', self.OFF_LEVEL)
63 | self.pin.value = self.OFF
64 | self._state = 0
65 |
66 |
67 | def disable(self):
68 | self.state = 0
69 |
70 |
71 | def deinit(self):
72 | super(GpioStandard, self).deinit()
73 | self.pin.deinit()
74 |
75 |
--------------------------------------------------------------------------------
/requirements/requirements_debian10.txt:
--------------------------------------------------------------------------------
1 | #################################
2 | ### !!!!!!!! WARNING !!!!!!!! ###
3 | ### Debian 10 Buster is ###
4 | ### nearing end of life. ###
5 | ### Python 3.7 is going out ###
6 | ### of support soon and the ###
7 | ### modules necessary for ###
8 | ### indi-allsky have ###
9 | ### unfixable security ###
10 | ### vulnerabilities ###
11 | #################################
12 |
13 | ### This file is targeted to Raspbian/Debian 10 and Python 3.7
14 | # https://www.piwheels.org/project/astropy/
15 | astropy
16 | # https://www.piwheels.org/project/numpy/
17 | numpy <= 1.21.4
18 | # https://www.piwheels.org/project/opencv-python-headless/
19 | opencv-python-headless <= 4.7.0.72
20 | # https://www.piwheels.org/project/scipy/
21 | scipy <= 1.7.3
22 | # https://www.piwheels.org/project/ccdproc/
23 | # 2.4.0 requires astropy 5.0, not compatible with python 3.7
24 | ccdproc <= 2.3.1
25 | # https://www.piwheels.org/project/scikit-image/
26 | scikit-image <= 0.19.3
27 | astroalign
28 | bottleneck
29 | python-dateutil
30 | ephem
31 | skyfield
32 | paramiko
33 | # https://www.piwheels.org/project/pycurl/
34 | pycurl
35 | # https://www.piwheels.org/project/Pillow/
36 | Pillow
37 | piexif
38 | imageio
39 | imageio-ffmpeg
40 | simplejpeg <= 1.7.2
41 | # https://www.piwheels.org/project/imagecodecs/
42 | #imagecodecs
43 | #rawpy # not needed
44 | pygifsicle
45 | gunicorn[gthread] >= 22.0.0
46 | inotify
47 | psutil
48 | # Last version supporting Python 3.7
49 | Flask == 2.2.5
50 | Flask-SQLAlchemy
51 | Flask-Migrate
52 | Flask-WTF
53 | Flask-Login
54 | werkzeug
55 | is-safe-url
56 | certifi
57 | # https://www.piwheels.org/project/cryptography/
58 | cryptography >= 42.0.5
59 | dbus-python
60 | paho-mqtt >= 2.0.0
61 | setuptools-rust
62 | # https://www.piwheels.org/project/bcrypt/
63 | bcrypt
64 | passlib[argon2]
65 | prettytable
66 | lxml
67 | shapely
68 | requests-toolbelt
69 | fish2pano >= 0.4.1
70 | pytz
71 | mysql-connector-python
72 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/watch_video.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: {{ title }}{% endblock %}
4 |
5 | {% block head %}
6 |
7 |
9 |
10 |
16 | {% endblock %}
17 |
18 | {% block content %}
19 |
{{ dayDate_full }} - {{ timeofday}}
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
37 |
38 |
68 |
69 |
70 | {% endblock %}
71 |
--------------------------------------------------------------------------------
/examples/example.php:
--------------------------------------------------------------------------------
1 | _conn = $this->_dbConnect();
22 | }
23 |
24 | private function _dbConnect() {
25 | $conn = new PDO($this->db_uri);
26 | $conn->exec('PRAGMA journal_mode=WAL');
27 | return($conn);
28 | }
29 |
30 | public function getLatestImages() {
31 | $stmt_files = $this->_conn->prepare("
32 | SELECT
33 | image.filename AS image_filename
34 | FROM image
35 | JOIN camera
36 | ON camera.id = image.camera_id
37 | WHERE
38 | camera.id = :cameraId
39 | ORDER BY
40 | image.createDate DESC
41 | LIMIT
42 | :limit
43 | ");
44 | $stmt_files->bindParam(':cameraId', $this->cameraId, PDO::PARAM_INT);
45 | $stmt_files->bindParam(':limit', $this->limit, PDO::PARAM_INT);
46 | $stmt_files->execute();
47 |
48 |
49 | $image_list = array();
50 | while($row = $stmt_files->fetch()) {
51 | $filename = $row['image_filename'];
52 |
53 | if (! file_exists($filename)) {
54 | continue;
55 | }
56 |
57 | $relpath = str_replace($this->rootpath, '', $filename);
58 |
59 | $image_list[] = $relpath;
60 | }
61 |
62 | return($image_list);
63 | }
64 | }
65 |
66 | $x = new IndiAllSkyLatestImages();
67 |
68 | $json_data = $x->getLatestImages();
69 |
70 | print(json_encode($json_data));
71 | ?>
72 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanSerialPwm.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import time
3 | import logging
4 |
5 | from .fanBase import FanBase
6 | from ..exceptions import DeviceControlException
7 |
8 |
9 | logger = logging.getLogger('indi_allsky')
10 |
11 |
12 | class FanSerialPwm(FanBase):
13 |
14 | BAUD_RATE = 9600
15 |
16 |
17 | def __init__(self, *args, **kwargs):
18 | super(FanSerialPwm, self).__init__(*args, **kwargs)
19 |
20 | serial_port_name = kwargs['pin_1_name']
21 |
22 | logger.info('Initializing serial controlled FAN device')
23 |
24 | self.serial_port = Path('/dev').joinpath(serial_port_name)
25 |
26 | if not self.serial_port.exists():
27 | raise DeviceControlException('Serial port does not exist: {0:s}'.format(str(self.serial_port)))
28 |
29 |
30 | self._state = -1
31 |
32 | time.sleep(1.0)
33 |
34 |
35 | @property
36 | def state(self):
37 | return self._state
38 |
39 |
40 | @state.setter
41 | def state(self, new_state):
42 | import serial
43 |
44 | # duty cycle must be a percentage between 0 and 100
45 | new_state_i = int(new_state)
46 |
47 | if new_state_i < 0:
48 | logger.error('Duty cycle must be 0 or greater')
49 | return
50 |
51 | if new_state_i > 100:
52 | logger.error('Duty cycle must be 100 or less')
53 | return
54 |
55 |
56 | logger.warning('Set fan state: %d%%', new_state_i)
57 |
58 | try:
59 | self.send_serial_fan(new_state)
60 | except serial.SerialException as e:
61 | raise DeviceControlException(str(e)) from e
62 |
63 | self._state = new_state_i
64 |
65 |
66 | def disable(self):
67 | self.state = 0
68 |
69 |
70 | def send_serial_fan(self, duty_cycle):
71 | import serial
72 |
73 | with serial.Serial(str(self.serial_port), self.BAUD_RATE, timeout=1) as ser:
74 | ser.write(('F' + str(duty_cycle) + '\n').encode())
75 |
76 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanDockerPi4ChannelRelay.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .fanBase import FanBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class FanDockerPi4ChannelRelay_I2C(FanBase):
11 | def __init__(self, *args, **kwargs):
12 | super(FanDockerPi4ChannelRelay_I2C, self).__init__(*args, **kwargs)
13 |
14 | i2c_address_str = kwargs['i2c_address']
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | #import busio
20 | from ..controllers import dockerpi
21 |
22 | i2c_address = int(i2c_address_str, 16) # string in config
23 |
24 | logger.info('Initializing Docker Pi 4 Channel Relay I2C FAN control device @ %s', hex(i2c_address))
25 |
26 | i2c = board.I2C()
27 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
28 |
29 | self.fan_controller = dockerpi.DockerPi4ChannelRelay(i2c, address=i2c_address)
30 | self.relay = getattr(dockerpi.DockerPi4ChannelRelay, pin_1_name)
31 |
32 |
33 | if invert_output:
34 | logger.warning('Fan logic reversed')
35 | self.ON = 0
36 | self.OFF = 1
37 | else:
38 | self.ON = 1
39 | self.OFF = 0
40 |
41 |
42 | self._state = -1
43 |
44 | time.sleep(1.0)
45 |
46 |
47 | @property
48 | def state(self):
49 | return self._state
50 |
51 |
52 | @state.setter
53 | def state(self, new_state):
54 | # any positive value is ON
55 | new_state_b = bool(new_state)
56 |
57 | if new_state_b:
58 | logger.warning('Set fan state: 100%')
59 | self.fan_controller.set_relay(self.relay, self.ON)
60 | self._state = 100
61 | else:
62 | logger.warning('Set fan state: 0%')
63 | self.fan_controller.set_relay(self.relay, self.OFF)
64 | self._state = 0
65 |
66 |
67 | def disable(self):
68 | self.state = 0
69 |
70 |
71 |
--------------------------------------------------------------------------------
/docker/nginx.local.conf:
--------------------------------------------------------------------------------
1 | upstream indi_allsky_server {
2 | server gunicorn.indi.allsky:8000 fail_timeout=0;
3 | }
4 |
5 | server {
6 | listen [::]:80 ipv6only=off;
7 | return 302 https://$host:8443$request_uri;
8 | }
9 |
10 | server {
11 | listen [::]:443 ssl ipv6only=off;
12 |
13 | http2 on;
14 |
15 | root /var/www/html/allsky;
16 |
17 | ssl_certificate /etc/ssl/certs/nginx.crt;
18 | ssl_certificate_key /etc/ssl/private/nginx.key;
19 |
20 | ssl_session_timeout 1d;
21 | ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
22 | ssl_session_tickets off;
23 |
24 | ssl_protocols TLSv1.3;
25 | ssl_prefer_server_ciphers on;
26 |
27 | ### 1 week HSTS header
28 | #add_header Strict-Transport-Security "max-age=604800; includeSubDomains" always;
29 |
30 | rewrite ^/$ https://$host:8443/indi-allsky;
31 |
32 | client_max_body_size 1024M;
33 |
34 |
35 | location /indi-allsky/images {
36 | alias %INDIALLSKY_IMAGE_FOLDER%;
37 | autoindex off;
38 |
39 | location ~* \.(jpe?g|png|tiff?|bmp|gif|fits?|webp|jp2|mp4|webm)$ {
40 | expires 90d;
41 | add_header Pragma public;
42 | add_header Cache-Control "public";
43 | }
44 | }
45 |
46 | location /indi-allsky/static {
47 | alias /home/allsky/indi-allsky/indi_allsky/flask/static;
48 | autoindex off;
49 |
50 | location ~* \.(js|css|svg)$ {
51 | expires 1d;
52 | add_header Pragma public;
53 | add_header Cache-Control "public";
54 | }
55 | }
56 |
57 |
58 | location /indi-allsky {
59 | try_files $uri @proxy_to_indi_allsky;
60 | }
61 |
62 |
63 | proxy_read_timeout 180s;
64 |
65 | location @proxy_to_indi_allsky {
66 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
67 | proxy_set_header X-Forwarded-Proto $scheme;
68 | proxy_set_header Host $http_host;
69 | proxy_redirect off;
70 | proxy_pass http://indi_allsky_server;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/misc/example_image_postsave_hook.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Example of a post-save image hook
3 | # The post-save script is executed after the image is processed and saved.
4 | #
5 | # STDOUT and STDERR are ignored
6 |
7 |
8 | import sys
9 | from pathlib import Path
10 | import argparse
11 | import signal
12 | import logging
13 |
14 |
15 | logging.basicConfig(level=logging.INFO)
16 | logger = logging
17 |
18 |
19 | # Available environment variables with data. Environment variables are strings, therefore
20 | # it requires using int() or float() to convert to numbers
21 | #EXPOSURE : float(os.environ['EXPOSURE'])
22 | #GAIN : int(os.environ['GAIN'])
23 | #BIN : int(os.environ['BIN'])
24 | #SUNALT : float(os.environ['SUNALT'])
25 | #MOONALT : float(os.environ['MOONALT'])
26 | #MOONPHASE : float(os.environ['MOONPHASE'])
27 | #NIGHT : int(os.environ['NIGHT'])
28 | #MOONMODE : int(os.environ['MOONMODE'])
29 | #LATITUDE : float(os.environ['LATITUDE'])
30 | #LONGITUDE : float(os.environ['LONGITUDE'])
31 | #ELEVATION : int(os.environ['ELEVATION'])
32 | #SENSOR_TEMP_0 - SENSOR_TEMP_59 : float(os.environ['SENSOR_TEMP_##'])
33 | #SENSOR_USER_0 - SENSOR_USER_59 : float(os.environ['SENSOR_USER_##'])
34 |
35 |
36 | def sigint_handler(signum, frame):
37 | # this prevents a keyboard interrupt from stopping the script
38 | pass
39 |
40 |
41 | signal.signal(signal.SIGINT, sigint_handler)
42 |
43 |
44 | #############
45 | ### START ###
46 | #############
47 |
48 |
49 | argparser = argparse.ArgumentParser()
50 | argparser.add_argument(
51 | 'image_file',
52 | help='Image File',
53 | type=str,
54 | )
55 |
56 | args = argparser.parse_args()
57 |
58 |
59 | image_path = Path(args.image_file)
60 |
61 |
62 | if not image_path.is_file():
63 | logger.error('Image does not exist')
64 | sys.exit(1)
65 |
66 |
67 | # At this stage of the script, you may read the image, transfer the image, fire ze missiles, etc
68 | # It is recommended not to alter the image
69 |
70 |
71 | # script must return exit code 0 for success
72 | sys.exit(0)
73 |
--------------------------------------------------------------------------------
/docker/Dockerfile.indi_base_debian12:
--------------------------------------------------------------------------------
1 | FROM debian:bookworm-slim AS indi.base
2 |
3 | ARG TZ
4 | ARG INDI_CORE_VERSION
5 | ARG LIBCAMERA_TAG
6 | ARG RPICAM_APPS_TAG
7 | ARG DEBIAN_FRONTEND=noninteractive
8 |
9 | RUN export DEBIAN_FRONTEND=noninteractive \
10 | && apt-get update \
11 | && apt-get -y upgrade \
12 | && apt-get -y install \
13 | --no-install-recommends \
14 | --no-install-suggests \
15 | build-essential \
16 | iproute2 \
17 | apt-utils \
18 | i2c-tools \
19 | locales \
20 | tzdata \
21 | procps \
22 | sudo \
23 | git \
24 | jq \
25 | && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
26 |
27 |
28 | # update timezone
29 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
30 | RUN dpkg-reconfigure -f noninteractive tzdata
31 |
32 |
33 | # need the uid and gid to match in all containers
34 | RUN groupadd --gid 10001 allsky
35 | RUN useradd --create-home --no-user-group --uid 10001 --gid allsky --groups dialout,video,adm --home-dir /home/allsky --shell /bin/bash allsky
36 | RUN if getent group gpio >/dev/null 2>&1; then usermod -a -G gpio allsky; fi
37 | RUN if getent group i2c >/dev/null 2>&1; then usermod -a -G i2c allsky; fi
38 | RUN if getent group spi >/dev/null 2>&1; then usermod -a -G spi allsky; fi
39 |
40 | RUN echo "allsky ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/allsky
41 | RUN chmod 0440 /etc/sudoers.d/allsky
42 |
43 | COPY --chown=allsky:allsky misc/build_indi.sh /home/allsky
44 | RUN chmod 755 /home/allsky/build_indi.sh
45 |
46 | USER allsky
47 | WORKDIR /home/allsky
48 |
49 |
50 | ENV BUILD_INDI_SETTINGS=manual
51 | ENV BUILD_INDI_CORE=true
52 | ENV BUILD_INDI_3RDPARTY=false
53 | ENV BUILD_INDI_OS_PACKAGE_UPGRADE=false
54 | RUN bash build_indi.sh $INDI_CORE_VERSION null \
55 | && rm -fR /home/allsky/Projects \
56 | && sudo apt-get clean && sudo rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
57 |
58 |
59 | ### libcamera build is prone to failure
60 | #RUN ./build_libcamera.sh $LIBCAMERA_TAG $RPICAM_APPS_TAG
61 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterSerialPwm.py:
--------------------------------------------------------------------------------
1 | from pathlib import Path
2 | import time
3 | import logging
4 |
5 | from .dewHeaterBase import DewHeaterBase
6 | from ..exceptions import DeviceControlException
7 |
8 |
9 | logger = logging.getLogger('indi_allsky')
10 |
11 |
12 | class DewHeaterSerialPwm(DewHeaterBase):
13 |
14 | BAUD_RATE = 9600
15 |
16 |
17 | def __init__(self, *args, **kwargs):
18 | super(DewHeaterSerialPwm, self).__init__(*args, **kwargs)
19 |
20 | serial_port_name = kwargs['pin_1_name']
21 |
22 | logger.info('Initializing serial controlled DEW HEATER device')
23 |
24 | self.serial_port = Path('/dev').joinpath(serial_port_name)
25 |
26 | if not self.serial_port.exists():
27 | raise DeviceControlException('Serial port does not exist: {0:s}'.format(str(self.serial_port)))
28 |
29 |
30 | self._state = -1
31 |
32 | time.sleep(1.0)
33 |
34 |
35 | @property
36 | def state(self):
37 | return self._state
38 |
39 |
40 | @state.setter
41 | def state(self, new_state):
42 | import serial
43 |
44 | # duty cycle must be a percentage between 0 and 100
45 | new_state_i = int(new_state)
46 |
47 | if new_state_i < 0:
48 | logger.error('Duty cycle must be 0 or greater')
49 | return
50 |
51 | if new_state_i > 100:
52 | logger.error('Duty cycle must be 100 or less')
53 | return
54 |
55 |
56 | logger.warning('Set dew heater state: %d%%', new_state_i)
57 |
58 | try:
59 | self.send_serial_dew_heater(new_state)
60 | except serial.SerialException as e:
61 | raise DeviceControlException(str(e)) from e
62 |
63 |
64 | self._state = new_state_i
65 |
66 |
67 | def disable(self):
68 | self.state = 0
69 |
70 |
71 | def send_serial_dew_heater(self, duty_cycle):
72 | import serial
73 |
74 | with serial.Serial(str(self.serial_port), self.BAUD_RATE, timeout=1) as ser:
75 | ser.write(('H' + str(duty_cycle) + '\n').encode())
76 |
77 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/lag.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Image Lag{% endblock %}
4 |
5 | {% block head %}
6 |
11 |
12 |
13 |
16 |
17 | {% endblock %}
18 |
19 | {% block content %}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | | Date |
28 | Exposure |
29 | Elapsed |
30 | Delta |
31 | Period |
32 | Processing |
33 |
34 |
35 |
36 | {% for row in image_lag_q %}
37 |
38 | | {{ row.createDate.strftime('%Y-%m-%d %H:%M:%S') }} |
39 | {{ "%0.7f"|format(row.exposure) }} |
40 | {{ "%0.2f"|format(row.exp_elapsed | float) }} |
41 | {{ "%+0.3f"|format(row.delta | float) }} |
42 | {{ row.lag_diff }} |
43 | {{ "%0.2f"|format(row.process_elapsed | float) }} |
44 |
45 | {% endfor %}
46 |
47 |
48 |
49 |
50 |
51 |
74 |
75 | {% endblock %}
76 |
--------------------------------------------------------------------------------
/misc/backup_database.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | from pathlib import Path
5 | import time
6 | import logging
7 |
8 | from sqlalchemy.orm.exc import NoResultFound
9 |
10 |
11 | sys.path.insert(0, str(Path(__file__).parent.absolute().parent))
12 |
13 |
14 | from indi_allsky.flask import create_app
15 | from indi_allsky.config import IndiAllSkyConfig
16 | from indi_allsky.backup import IndiAllskyDatabaseBackup
17 | from indi_allsky.exceptions import BackupFailure
18 |
19 | # setup flask context for db access
20 | app = create_app()
21 | app.app_context().push()
22 |
23 |
24 | logger = logging.getLogger('indi_allsky')
25 | logger.setLevel(logging.INFO)
26 |
27 |
28 |
29 | LOG_FORMATTER_STREAM = logging.Formatter('[%(levelname)s]: %(message)s')
30 |
31 | LOG_HANDLER_STREAM = logging.StreamHandler()
32 | LOG_HANDLER_STREAM.setFormatter(LOG_FORMATTER_STREAM)
33 |
34 | logger.handlers.clear() # remove syslog
35 | logger.addHandler(LOG_HANDLER_STREAM)
36 |
37 |
38 | class BackupDatabase(object):
39 |
40 | def __init__(self):
41 | try:
42 | self._config_obj = IndiAllSkyConfig()
43 | #logger.info('Loaded config id: %d', self._config_obj.config_id)
44 | except NoResultFound:
45 | logger.error('No config file found, please import a config')
46 | sys.exit(1)
47 |
48 | self.config = self._config_obj.config
49 |
50 |
51 | def main(self):
52 | if not app.config['SQLALCHEMY_DATABASE_URI'].startswith('sqlite'):
53 | logger.error('Only sqlite backups are supported')
54 | sys.exit(1)
55 |
56 |
57 | backup_start = time.time()
58 |
59 | backup = IndiAllskyDatabaseBackup(self.config)
60 |
61 |
62 | try:
63 | backup.db_backup()
64 | except BackupFailure as e:
65 | logger.error('Backup failed: %s', str(e))
66 | sys.exit(1)
67 |
68 |
69 | backup_elapsed_s = time.time() - backup_start
70 | logger.info('Backup completed in %0.2fs', backup_elapsed_s)
71 |
72 |
73 | if __name__ == "__main__":
74 | BackupDatabase().main()
75 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterDockerPi4ChannelRelay.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .dewHeaterBase import DewHeaterBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class DewHeaterDockerPi4ChannelRelay_I2C(DewHeaterBase):
11 | def __init__(self, *args, **kwargs):
12 | super(DewHeaterDockerPi4ChannelRelay_I2C, self).__init__(*args, **kwargs)
13 |
14 | i2c_address_str = kwargs['i2c_address']
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | #import busio
20 | from ..controllers import dockerpi
21 |
22 | i2c_address = int(i2c_address_str, 16) # string in config
23 |
24 | logger.info('Initializing Docker Pi 4 Channel Relay I2C Dew Heater control device @ %s', hex(i2c_address))
25 |
26 | i2c = board.I2C()
27 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
28 |
29 | self.dh_controller = dockerpi.DockerPi4ChannelRelay(i2c, address=i2c_address)
30 | self.relay = getattr(dockerpi.DockerPi4ChannelRelay, pin_1_name)
31 |
32 |
33 | if invert_output:
34 | logger.warning('Dew heater logic reversed')
35 | self.ON = 0
36 | self.OFF = 1
37 | else:
38 | self.ON = 1
39 | self.OFF = 0
40 |
41 |
42 | self._state = -1
43 |
44 | time.sleep(1.0)
45 |
46 |
47 | @property
48 | def state(self):
49 | return self._state
50 |
51 |
52 | @state.setter
53 | def state(self, new_state):
54 | # any positive value is ON
55 | new_state_b = bool(new_state)
56 |
57 | if new_state_b:
58 | logger.warning('Set dew heater state: 100%')
59 | self.dh_controller.set_relay(self.relay, self.ON)
60 | self._state = 100
61 | else:
62 | logger.warning('Set dew heater state: 0%')
63 | self.dh_controller.set_relay(self.relay, self.OFF)
64 | self._state = 0
65 |
66 |
67 | def disable(self):
68 | self.state = 0
69 |
70 |
71 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/adu.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Image Historical ADU{% endblock %}
4 |
5 | {% block head %}
6 |
11 |
12 |
13 |
16 |
17 | {% endblock %}
18 |
19 | {% block content %}
20 |
21 |
22 |
Image stats in 15 minute blocks
23 |
24 |
25 |
26 |
27 |
28 |
29 | | Date |
30 | Count |
31 | Exposure Avg |
32 | ADU Avg |
33 | SQM Avg |
34 | Stars Avg |
35 |
36 |
37 |
38 | {% for row in rolling_adu_q %}
39 |
40 | | {{ row.dt.strftime('%Y-%m-%d %H:%M') }}
41 | | {{ row.i_count }}
42 | | {{ "%0.4f"|format(row.exposure_avg) }}
43 | | {{ "%0.2f"|format(row.adu_avg) }}
44 | | {{ "%0.2f"|format(row.sqm_avg) }}
45 | | {{ "%0.1f"|format(row.stars_avg) }}
46 | |
47 | {% endfor %}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
78 |
79 | {% endblock %}
80 |
--------------------------------------------------------------------------------
/misc/image_folder_perms.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | #######################################################
3 | # This script shows the owner and permissions of the #
4 | # image folder and all parent folders #
5 | #######################################################
6 |
7 | import sys
8 | from pathlib import Path
9 | import logging
10 |
11 | from sqlalchemy.orm.exc import NoResultFound
12 |
13 |
14 | sys.path.insert(0, str(Path(__file__).parent.absolute().parent))
15 |
16 |
17 | from indi_allsky.config import IndiAllSkyConfig
18 | from indi_allsky.flask import create_app
19 |
20 |
21 | # setup flask context for db access
22 | app = create_app()
23 | app.app_context().push()
24 |
25 |
26 | logger = logging.getLogger('indi_allsky')
27 | logger.setLevel(logging.INFO)
28 |
29 |
30 | LOG_FORMATTER_STREAM = logging.Formatter('[%(levelname)s]: %(message)s')
31 |
32 | LOG_HANDLER_STREAM = logging.StreamHandler()
33 | LOG_HANDLER_STREAM.setFormatter(LOG_FORMATTER_STREAM)
34 |
35 | logger.handlers.clear() # remove syslog
36 | logger.addHandler(LOG_HANDLER_STREAM)
37 |
38 |
39 | class ImageFolderPermissions(object):
40 |
41 | def __init__(self):
42 | try:
43 | self._config_obj = IndiAllSkyConfig()
44 | #logger.info('Loaded config id: %d', self._config_obj.config_id)
45 | except NoResultFound:
46 | logger.error('No config file found, please import a config')
47 | sys.exit(1)
48 |
49 | self.config = self._config_obj.config
50 |
51 |
52 | if self.config.get('IMAGE_FOLDER'):
53 | self.image_dir = Path(self.config['IMAGE_FOLDER']).absolute()
54 | else:
55 | self.image_dir = Path(__file__).parent.parent.joinpath('html', 'images').absolute()
56 |
57 |
58 |
59 | def main(self):
60 | folder = self.image_dir
61 |
62 | while True:
63 | logger.info('Folder: %s, Owner: %s, Group: %s, Mode: %s', folder, folder.owner(), folder.group(), oct(folder.stat().st_mode))
64 |
65 | folder = folder.parent
66 | if folder == Path('/'):
67 | break
68 |
69 |
70 | if __name__ == "__main__":
71 | ImageFolderPermissions().main()
72 |
73 |
--------------------------------------------------------------------------------
/testing/gpio/28byj_step_blinka.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import curses
4 | import time
5 | import board
6 | import digitalio
7 | import logging
8 |
9 |
10 | IN1 = board.D17
11 | IN2 = board.D18
12 | IN3 = board.D27
13 | IN4 = board.D22
14 |
15 |
16 | logging.basicConfig(level=logging.INFO)
17 | logger = logging
18 |
19 |
20 | class Stepper(object):
21 | SEQ = [
22 | [1, 0, 0, 0],
23 | [1, 1, 0, 0],
24 | [0, 1, 0, 0],
25 | [0, 1, 1, 0],
26 | [0, 0, 1, 0],
27 | [0, 0, 1, 1],
28 | [0, 0, 0, 1],
29 | [1, 0, 0, 1],
30 | ]
31 |
32 |
33 | def __init__(self):
34 | self.pins = [
35 | digitalio.DigitalInOut(IN1),
36 | digitalio.DigitalInOut(IN2),
37 | digitalio.DigitalInOut(IN3),
38 | digitalio.DigitalInOut(IN4),
39 | ]
40 |
41 | for pin in self.pins:
42 | # set all pins to output
43 | pin.direction = digitalio.Direction.OUTPUT
44 |
45 |
46 | def set_step(self, w1, w2, w3, w4):
47 | self.pins[0].value = w1
48 | self.pins[1].value = w2
49 | self.pins[2].value = w3
50 | self.pins[3].value = w4
51 |
52 |
53 | def step(self, direction, steps):
54 | if direction == 'cw':
55 | seq = self.SEQ[::-1]
56 | elif direction == 'ccw':
57 | seq = self.SEQ
58 |
59 | for i in range(steps):
60 | for j in seq:
61 | self.set_step(*j)
62 | time.sleep(0.005)
63 |
64 |
65 | def control_motor(self, stdscr):
66 | self.set_step(0, 0, 0, 0) # reset
67 |
68 | stdscr.clear()
69 | stdscr.addstr("Use up and down arrow keys")
70 | stdscr.refresh()
71 |
72 | while True:
73 | key = stdscr.getch()
74 | if key == curses.KEY_UP:
75 | self.step('cw', 10)
76 | elif key == curses.KEY_DOWN:
77 | self.step('ccw', 10)
78 | elif key == ord('q'):
79 | break
80 |
81 | time.sleep(0.005)
82 |
83 | self.set_step(0, 0, 0, 0) # reset
84 |
85 |
86 | if __name__ == "__main__":
87 | s = Stepper()
88 | curses.wrapper(s.control_motor)
89 |
--------------------------------------------------------------------------------
/indi_allsky/devices/generic/gpioDockerPi4ChannelRelay.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .genericBase import GenericBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class GpioDockerPi4ChannelRelay_I2C(GenericBase):
11 | def __init__(self, *args, **kwargs):
12 | super(GpioDockerPi4ChannelRelay_I2C, self).__init__(*args, **kwargs)
13 |
14 | i2c_address_str = kwargs['i2c_address']
15 | pin_1_name = kwargs['pin_1_name']
16 | invert_output = kwargs['invert_output']
17 |
18 | import board
19 | #import busio
20 | from ..controllers import dockerpi
21 |
22 | i2c_address = int(i2c_address_str, 16) # string in config
23 |
24 | logger.info('Initializing Docker Pi 4 Channel Relay I2C GPIO control device @ %s', hex(i2c_address))
25 |
26 | i2c = board.I2C()
27 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
28 |
29 | self.gpio_controller = dockerpi.DockerPi4ChannelRelay(i2c, address=i2c_address)
30 | self.relay = getattr(dockerpi.DockerPi4ChannelRelay, pin_1_name)
31 |
32 |
33 | if invert_output:
34 | logger.warning('GPIO logic reversed')
35 | self.ON = 0
36 | self.OFF = 1
37 | self.ON_LEVEL = 'low'
38 | self.OFF_LEVEL = 'high'
39 | else:
40 | self.ON = 1
41 | self.OFF = 0
42 | self.ON_LEVEL = 'high'
43 | self.OFF_LEVEL = 'low'
44 |
45 |
46 | self._state = -1
47 |
48 | time.sleep(1.0)
49 |
50 |
51 | @property
52 | def state(self):
53 | return self._state
54 |
55 |
56 | @state.setter
57 | def state(self, new_state):
58 | # any positive value is ON
59 | new_state_b = bool(new_state)
60 |
61 | if new_state_b:
62 | logger.warning('Set GPIO state: ON (%s)', self.ON_LEVEL)
63 | self.gpio_controller.set_relay(self.relay, self.ON)
64 | self._state = 100
65 | else:
66 | logger.warning('Set GPIO state: OFF (%s)', self.OFF_LEVEL)
67 | self.gpio_controller.set_relay(self.relay, self.OFF)
68 | self._state = 0
69 |
70 |
71 | def disable(self):
72 | self.state = 0
73 |
74 |
75 |
--------------------------------------------------------------------------------
/indi_allsky/devices/sensors/lightSensorBh1750.py:
--------------------------------------------------------------------------------
1 | #import time
2 | import logging
3 |
4 | from .sensorBase import SensorBase
5 | from ... import constants
6 | from ..exceptions import SensorReadException
7 |
8 |
9 | logger = logging.getLogger('indi_allsky')
10 |
11 |
12 | class LightSensorBh1750(SensorBase):
13 |
14 | def update(self):
15 |
16 | try:
17 | lux = float(self.bh1750.lux) # can be None
18 | except RuntimeError as e:
19 | raise SensorReadException(str(e)) from e
20 | except TypeError as e:
21 | raise SensorReadException(str(e)) from e
22 |
23 |
24 | logger.info('[%s] BH1750 - lux: %0.4f', self.name, lux)
25 |
26 |
27 | try:
28 | sqm_mag = self.lux2mag(lux)
29 | except ValueError as e:
30 | logger.error('SQM calculation error - ValueError: %s', str(e))
31 | sqm_mag = 0.0
32 |
33 |
34 | data = {
35 | 'sqm_mag' : sqm_mag,
36 | 'data' : (
37 | lux,
38 | sqm_mag,
39 | ),
40 | }
41 |
42 | return data
43 |
44 |
45 | class LightSensorBh1750_I2C(LightSensorBh1750):
46 |
47 | METADATA = {
48 | 'name' : 'BH1750 (i2c)',
49 | 'description' : 'BH1750 i2c Light Sensor',
50 | 'count' : 2,
51 | 'labels' : (
52 | 'Lux',
53 | 'SQM',
54 | ),
55 | 'types' : (
56 | constants.SENSOR_LIGHT_LUX,
57 | constants.SENSOR_LIGHT_MISC,
58 | ),
59 | }
60 |
61 |
62 | def __init__(self, *args, **kwargs):
63 | super(LightSensorBh1750_I2C, self).__init__(*args, **kwargs)
64 |
65 | i2c_address_str = kwargs['i2c_address']
66 |
67 | import board
68 | #import busio
69 | import adafruit_bh1750
70 |
71 | i2c_address = int(i2c_address_str, 16) # string in config
72 |
73 | logger.warning('Initializing [%s] BH1750 I2C light sensor device @ %s', self.name, hex(i2c_address))
74 | i2c = board.I2C()
75 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
76 | #i2c = busio.I2C(board.D1, board.D0, frequency=100000) # Raspberry Pi i2c bus 0 (pins 28/27)
77 | self.bh1750 = adafruit_bh1750.BH1750(i2c, address=i2c_address)
78 |
79 |
80 |
--------------------------------------------------------------------------------
/flask.json_template:
--------------------------------------------------------------------------------
1 | {
2 | "SQLALCHEMY_DATABASE_URI" : "sqlite:////var/lib/indi-allsky/indi-allsky.sqlite",
3 |
4 | "SQLALCHEMY_TRACK_MODIFICATIONS" : false,
5 | "SQLALCHEMY_ENGINE_OPTIONS" : {
6 | "echo" : false,
7 | "pool_size" : 25
8 | },
9 |
10 | "MIGRATION_FOLDER" : "/var/lib/indi-allsky/migrations",
11 |
12 | "SESSION_COOKIE_SECURE_comment" : "True will prevent using http-only mode",
13 | "SESSION_COOKIE_SECURE" : true,
14 | "SESSION_COOKIE_HTTPONLY" : true,
15 | "SESSION_COOKIE_SAMESITE" : "Lax",
16 | "PERMANENT_SESSION_LIFETIME" : 2678400,
17 | "SESSION_REFRESH_EACH_REQUEST" : true,
18 | "TEMPLATES_AUTO_RELOAD" : true,
19 |
20 | "WTF_CSRF_TIME_LIMIT" : 86400,
21 | "SECRET_KEY" : "CHANGEME",
22 |
23 | "REMEMBER_COOKIE_DURATION" : 2678400,
24 | "REMEMBER_COOKIE_SECURE_comment" : "True will prevent using http-only mode",
25 | "REMEMBER_COOKIE_SECURE" : true,
26 | "REMEMBER_COOKIE_HTTPONLY" : true,
27 | "REMEMBER_COOKIE_SAMESITE" : "Lax",
28 | "SESSION_PROTECTION" : "strong",
29 |
30 | "SEND_FILE_MAX_AGE_DEFAULT" : 7776000,
31 |
32 | "INDI_ALLSKY_DOCROOT" : "/var/www/html/allsky",
33 | "INDI_ALLSKY_IMAGE_FOLDER" : "/var/www/html/allsky/images",
34 |
35 | "INDI_ALLSKY_AUTH_ALL_VIEWS" : false,
36 | "INDI_ALLSKY_AUTH_MEDIA_VIEWS" : false,
37 | "LOGIN_DISABLED_comment" : "DANGER Set true to disable all authentication. This will allow anonymous persons to delete data and shutdown your system",
38 | "LOGIN_DISABLED" : false,
39 |
40 | "PASSWORD_KEY_comment" : "If you lose the PASSWORD_KEY passwords cannot be recovered",
41 | "PASSWORD_KEY" : "CHANGEME",
42 |
43 | "ADMIN_NETWORKS" : [
44 | "10.0.0.0/8",
45 | "192.168.0.0/16",
46 | "172.16.0.0/12",
47 | "169.254.0.0/16",
48 | "fe80::/64",
49 | "127.0.0.1/32",
50 | "::1/128"
51 | ],
52 |
53 | "INDISERVER_SERVICE_NAME" : "indiserver.service",
54 | "INDISERVER_TIMER_NAME" : "indiserver.timer",
55 | "ALLSKY_SERVICE_NAME" : "indi-allsky.service",
56 | "ALLSKY_TIMER_NAME" : "indi-allsky.timer",
57 | "UPGRADE_ALLSKY_SERVICE_NAME" : "upgrade-indi-allsky.service",
58 | "GUNICORN_SERVICE_NAME" : "gunicorn-indi-allsky.service",
59 | "GUNICORN_SOCKET_NAME" : "gunicorn-indi-allsky.socket"
60 | }
61 |
--------------------------------------------------------------------------------
/testing/benchmark/sat_tle_bench.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 |
4 | import timeit
5 | import logging
6 |
7 |
8 | logging.basicConfig(level=logging.INFO)
9 | logger = logging
10 |
11 |
12 | class SatBench(object):
13 | rounds = 500
14 |
15 | def __init__(self):
16 | pass
17 |
18 | def main(self):
19 | setup_pyephem = '''
20 | from datetime import datetime
21 | from datetime import timezone
22 | import io
23 | import math
24 | import ephem
25 |
26 | obs = ephem.Observer()
27 | obs.lat = math.radians(33.0)
28 | obs.lon = math.radians(-84.0)
29 |
30 | # disable atmospheric refraction calcs
31 | obs.pressure = 0
32 |
33 | with io.open('/tmp/iss_27272897.txt', 'r') as f_tle:
34 | tle_data = f_tle.readlines()
35 | '''
36 |
37 | s_pyephem = '''
38 | iss = ephem.readtle(*tle_data)
39 | obs.date = datetime.now(tz=timezone.utc)
40 | iss.compute(obs)
41 | iss_next_pass = obs.next_pass(iss)
42 | #math.degrees(iss.alt)
43 | #math.degrees(iss.az)
44 | #ephem.localtime(iss_next_pass[0])
45 | #ephem.localtime(iss_next_pass[2])
46 | #ephem.localtime(iss_next_pass[4])
47 | '''
48 |
49 |
50 | setup_skyfield = '''
51 | import io
52 | from datetime import datetime
53 | from datetime import timedelta
54 | from datetime import timezone
55 |
56 | from skyfield.api import load
57 | from skyfield.api import wgs84
58 | from skyfield.api import EarthSatellite
59 |
60 | ts = load.timescale()
61 |
62 | location = wgs84.latlon(33.0, -84.0)
63 |
64 | with io.open('/tmp/iss_27272897.txt', 'r') as f_tle:
65 | tle_data = f_tle.readlines()
66 | '''
67 |
68 | s_skyfield = '''
69 | satellite = EarthSatellite(tle_data[1], tle_data[2], tle_data[0], ts)
70 |
71 | now = datetime.now(tz=timezone.utc)
72 | t0 = ts.from_datetime(now)
73 | t1 = ts.from_datetime(now + timedelta(hours=24))
74 |
75 | t, events = satellite.find_events(location, t0, t1, altitude_degrees=0.0)
76 | '''
77 |
78 |
79 | t_pyephem = timeit.timeit(stmt=s_pyephem, setup=setup_pyephem, number=self.rounds)
80 | logger.info('PyEphem calc: %0.3fms', t_pyephem * 1000 / self.rounds)
81 |
82 | t_skyfield = timeit.timeit(stmt=s_skyfield, setup=setup_skyfield, number=self.rounds)
83 | logger.info('Skyfield calc: %0.3fms', t_skyfield * 1000 / self.rounds)
84 |
85 |
86 |
87 | if __name__ == "__main__":
88 | ib = SatBench()
89 | ib.main()
90 |
91 |
--------------------------------------------------------------------------------
/docker/env_template:
--------------------------------------------------------------------------------
1 | ### copy this file to .env ###
2 |
3 | # shellcheck disable=SC2034
4 |
5 | TZ=America/New_York
6 |
7 | INDIALLSKY_INDI_CCD_DRIVER=indi_simulator_ccd
8 | INDIALLSKY_INDI_GPS_DRIVER=
9 |
10 | # This folder needs to be shared to the capture, gunicorn, and nginx containers
11 | INDIALLSKY_IMAGE_FOLDER=/var/www/html/allsky/images
12 |
13 | # Set to "true" (without quotes) to take dark frames instead of normal image capture
14 | INDIALLSKY_DARK_CAPTURE_ENABLE=false
15 | INDIALLSKY_DARK_CAPTURE_MODE=sigmaclip
16 | INDIALLSKY_DARK_CAPTURE_BITMAX=16
17 | INDIALLSKY_DARK_CAPTURE_DAYTIME=--daytime
18 |
19 | # Folder for temporary files in capture process. Uncomment to activate
20 | #CAPTURE_TMPDIR=/tmp
21 |
22 | INDIALLSKY_FLASK_AUTH_ALL_VIEWS=false
23 | INDIALLSKY_FLASK_SECRET_KEY=%INDIALLSKY_FLASK_SECRET_KEY%
24 | INDIALLSKY_FLASK_PASSWORD_KEY=%INDIALLSKY_FLASK_PASSWORD_KEY%
25 |
26 | INDIALLSKY_WEB_USER=%WEB_USER%
27 | INDIALLSKY_WEB_PASS=%WEB_PASS%
28 | INDIALLSKY_WEB_NAME=%WEB_NAME%
29 | INDIALLSKY_WEB_EMAIL=%WEB_EMAIL%
30 |
31 | INDIALLSKY_MOSQUITTO_USER=%WEB_USER%
32 | INDIALLSKY_MOSQUITTO_PASS=%WEB_PASS%
33 |
34 | # Set to "true" (without quotes) to generate a users API key
35 | # remember to set back to "false" after first run
36 | INDIALLSKY_WEB_GENERATE_APIKEY=false
37 |
38 | # Only used on the debian build
39 | INDIALLSKY_INDI_CORE_VERSION=v2.1.7
40 | INDIALLSKY_INDI_3RDPARTY_VERSION=v2.1.7.1
41 |
42 | # "supported" includes all supported camera drivers except libcamera
43 | # asi, qhy, playerone, sx, touptek, svbony, libcamera, gphoto, webcam
44 | INDIALLSKY_CAMERA_VENDOR=supported
45 |
46 | # libcamera is commented out of Dockerfile.indi_base_debian12
47 | INDIALLSKY_LIBCAMERA_TAG=v0.6.0+rpt20251202
48 | INDIALLSKY_RPICAM_APPS_TAG=v1.10.1
49 |
50 | INDIALLSKY_MARIADB_HOST=mariadb.indi.allsky
51 | INDIALLSKY_MARIADB_PORT=3306
52 | INDIALLSKY_MARIADB_SSL=false
53 | INDIALLSKY_MARIADB_CHARSET=utf8mb4
54 | INDIALLSKY_MARIADB_COLLATION=utf8mb4_unicode_ci
55 |
56 | MARIADB_RANDOM_ROOT_PASSWORD=yes
57 | MARIADB_DATABASE=indi_allsky
58 | MARIADB_USER=indi_allsky_own
59 | MARIADB_PASSWORD=%MARIADB_PASSWORD%
60 |
61 | # gunicorn security setting. Fixes some spurious https -> http redirects
62 | # May need to be locked down for a remote reverse proxy
63 | FORWARDED_ALLOW_IPS="*"
64 |
65 | # For testing
66 | #INDIALLSKY_CAPTURE_NO_WAIT=1
67 | #INDIALLSKY_GUNICORN_NO_WAIT=1
68 |
--------------------------------------------------------------------------------
/indi_allsky/devices/fans/fanMotorKit.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .fanBase import FanBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class FanMotorKitPwm(FanBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(FanMotorKitPwm, self).__init__(*args, **kwargs)
14 |
15 | i2c_address_str = kwargs['i2c_address']
16 | pin_1_name = kwargs['pin_1_name']
17 | self.invert_output = kwargs['invert_output']
18 |
19 |
20 | import board
21 | #import busio
22 | from adafruit_motorkit import MotorKit
23 |
24 |
25 | i2c_address = int(i2c_address_str, 16) # string in config
26 |
27 | # pin 1 should be the name for the motor
28 | motor_name = str(pin_1_name)
29 |
30 |
31 | logger.info('Initializing MotorKit FAN device %s @ %s', motor_name, i2c_address_str)
32 |
33 | if self.invert_output:
34 | logger.warning('Fan logic reversed')
35 |
36 |
37 | i2c = board.I2C()
38 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
39 | #i2c = busio.I2C(board.D1, board.D0, frequency=100000) # Raspberry Pi i2c bus 0 (pins 28/27)
40 | kit = MotorKit(i2c=i2c, address=i2c_address)
41 |
42 | self.motor = getattr(kit, motor_name)
43 |
44 | self._state = -1
45 |
46 | time.sleep(1.0)
47 |
48 |
49 | @property
50 | def state(self):
51 | return self._state
52 |
53 |
54 | @state.setter
55 | def state(self, new_state):
56 | # duty cycle must be a percentage between 0 and 100
57 | new_state_i = int(new_state)
58 |
59 | if new_state_i < 0:
60 | logger.error('Duty cycle must be 0 or greater')
61 | return
62 |
63 | if new_state_i > 100:
64 | logger.error('Duty cycle must be 100 or less')
65 | return
66 |
67 |
68 | if self.invert_output:
69 | new_duty_cycle = (100 - new_state_i) / 100
70 | else:
71 | new_duty_cycle = new_state_i / 100
72 |
73 |
74 | logger.warning('Set fan state: %d%%', new_state_i)
75 | self.motor.throttle = new_duty_cycle
76 |
77 | self._state = new_state_i
78 |
79 |
80 | def disable(self):
81 | self.state = 0
82 |
83 |
84 | def deinit(self):
85 | super(FanMotorKitPwm, self).deinit()
86 |
87 |
--------------------------------------------------------------------------------
/indi_allsky/filetransfer/generic.py:
--------------------------------------------------------------------------------
1 | #from pathlib import Path
2 | import string
3 | import random
4 | import logging
5 |
6 | logger = logging.getLogger('indi_allsky')
7 |
8 |
9 | class GenericFileTransfer(object):
10 | def __init__(self, *args, **kwargs):
11 | self.config = args[0]
12 | self.delete = kwargs.get('delete', False)
13 |
14 | self._port = 0
15 | self._connect_timeout = 10.0
16 | self._timeout = 60.0
17 | self._atomic = False
18 |
19 |
20 | self._client = None
21 |
22 |
23 | @property
24 | def port(self):
25 | return self._port
26 |
27 | @port.setter
28 | def port(self, new_port):
29 | self._port = int(new_port)
30 |
31 |
32 | @property
33 | def timeout(self):
34 | return self._timeout
35 |
36 | @timeout.setter
37 | def timeout(self, new_timeout):
38 | self._timeout = float(new_timeout)
39 |
40 |
41 | @property
42 | def connect_timeout(self):
43 | return self._connect_timeout
44 |
45 | @connect_timeout.setter
46 | def connect_timeout(self, new_connect_timeout):
47 | self._connect_timeout = float(new_connect_timeout)
48 |
49 |
50 | @property
51 | def atomic(self):
52 | return self._atomic
53 |
54 | @atomic.setter
55 | def atomic(self, new_atomic):
56 | self._atomic = bool(new_atomic)
57 |
58 |
59 | def connect(self, *args, **kwargs):
60 | #hostname = kwargs['hostname']
61 | #username = kwargs['username']
62 | #password = kwargs['password']
63 |
64 | #logger.info('Connecting to %s (%d) as %s with %s', hostname, self._port, username, self.__class__.__name__)
65 |
66 | pass
67 |
68 |
69 | def close(self):
70 | pass
71 |
72 |
73 | def put(self, *args, **kwargs):
74 | if self.delete:
75 | # perform delete instead of upload
76 | return self.delete()
77 |
78 |
79 | local_file = kwargs['local_file']
80 | logger.info('Uploading %s', local_file)
81 |
82 |
83 | def delete(self, *args, **kwargs):
84 | pass
85 |
86 |
87 | def tempname(self, suffix='.bin', size=8, chars=string.ascii_letters + string.digits):
88 | # generate random filename
89 | return 'tmp{0:s}{1:s}'.format(''.join(random.choice(chars) for _ in range(size)), suffix) # suffix usually includes dot
90 |
91 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import argparse
4 | import logging
5 |
6 | from indi_allsky.config import IndiAllSkyConfigUtil
7 |
8 |
9 | logger = logging.getLogger('indi_allsky')
10 | logger.setLevel(logging.INFO)
11 |
12 | LOG_FORMATTER_STREAM = logging.Formatter('%(asctime)s [%(levelname)s] %(processName)s %(module)s.%(funcName)s() [%(lineno)d]: %(message)s')
13 | LOG_HANDLER_STREAM = logging.StreamHandler()
14 | LOG_HANDLER_STREAM.setFormatter(LOG_FORMATTER_STREAM)
15 |
16 | logger.addHandler(LOG_HANDLER_STREAM)
17 |
18 |
19 |
20 | if __name__ == "__main__":
21 | argparser = argparse.ArgumentParser()
22 | argparser.add_argument(
23 | 'action',
24 | help='configuration management actions',
25 | choices=(
26 | 'bootstrap', # load initial config
27 | 'list', # list configs
28 | 'load', # load exported config
29 | 'dump', # export config to STDOUT
30 | 'dumpfile', # export config to file
31 | 'update_level', # update config functional level
32 | 'edit', # edit config in cli
33 | 'revert', # revert to an older config --id
34 | 'user_count', # return count of active users to STDOUT
35 | 'delete', # deletes config by --id
36 | 'flush', # deletes all configs
37 | ),
38 | )
39 | argparser.add_argument(
40 | '--config',
41 | '-c',
42 | help='config file',
43 | type=argparse.FileType('r', encoding='utf-8'),
44 | )
45 | argparser.add_argument(
46 | '--outfile',
47 | '-o',
48 | help='output file',
49 | type=str,
50 | default='',
51 | )
52 | argparser.add_argument(
53 | '--id',
54 | '-i',
55 | help='config id (revert/dump)',
56 | type=int,
57 | )
58 | argparser.add_argument(
59 | '--force',
60 | help='force changes',
61 | dest='force',
62 | action='store_true',
63 | )
64 | argparser.set_defaults(force=False)
65 |
66 | args = argparser.parse_args()
67 |
68 |
69 | iacu = IndiAllSkyConfigUtil()
70 | action_func = getattr(iacu, args.action)
71 | action_func(
72 | config=args.config,
73 | outfile=args.outfile,
74 | config_id=args.id,
75 | force=args.force,
76 | )
77 |
78 |
--------------------------------------------------------------------------------
/indi_allsky/devices/dew_heaters/dewHeaterMotorKit.py:
--------------------------------------------------------------------------------
1 | import time
2 | import logging
3 |
4 | from .dewHeaterBase import DewHeaterBase
5 |
6 |
7 | logger = logging.getLogger('indi_allsky')
8 |
9 |
10 | class DewHeaterMotorKitPwm(DewHeaterBase):
11 |
12 | def __init__(self, *args, **kwargs):
13 | super(DewHeaterMotorKitPwm, self).__init__(*args, **kwargs)
14 |
15 | i2c_address_str = kwargs['i2c_address']
16 | pin_1_name = kwargs['pin_1_name']
17 | self.invert_output = kwargs['invert_output']
18 |
19 |
20 | import board
21 | #import busio
22 | from adafruit_motorkit import MotorKit
23 |
24 |
25 | i2c_address = int(i2c_address_str, 16) # string in config
26 |
27 | # pin 1 should be the name for the motor
28 | motor_name = str(pin_1_name)
29 |
30 |
31 | logger.info('Initializing MotorKit DEW HEATER device %s @ %s', motor_name, i2c_address_str)
32 |
33 | if self.invert_output:
34 | logger.warning('Dew heater logic reversed')
35 |
36 |
37 | i2c = board.I2C()
38 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
39 | #i2c = busio.I2C(board.D1, board.D0, frequency=100000) # Raspberry Pi i2c bus 0 (pins 28/27)
40 | kit = MotorKit(i2c=i2c, address=i2c_address)
41 |
42 | self.motor = getattr(kit, motor_name)
43 |
44 | self._state = -1
45 |
46 | time.sleep(1.0)
47 |
48 |
49 | @property
50 | def state(self):
51 | return self._state
52 |
53 |
54 | @state.setter
55 | def state(self, new_state):
56 | # duty cycle must be a percentage between 0 and 100
57 | new_state_i = int(new_state)
58 |
59 | if new_state_i < 0:
60 | logger.error('Duty cycle must be 0 or greater')
61 | return
62 |
63 | if new_state_i > 100:
64 | logger.error('Duty cycle must be 100 or less')
65 | return
66 |
67 |
68 | if self.invert_output:
69 | new_duty_cycle = (100 - new_state_i) / 100
70 | else:
71 | new_duty_cycle = new_state_i / 100
72 |
73 |
74 | logger.warning('Set dew heater state: %d%%', new_state_i)
75 | self.motor.throttle = new_duty_cycle
76 |
77 | self._state = new_state_i
78 |
79 |
80 | def disable(self):
81 | self.state = 0
82 |
83 |
84 | def deinit(self):
85 | super(DewHeaterMotorKitPwm, self).deinit()
86 |
87 |
--------------------------------------------------------------------------------
/service/90-indi-allsky.rules:
--------------------------------------------------------------------------------
1 | polkit.addRule(function(action, subject) {
2 | // Allow indi-allsky user to shutdown
3 | const shutdown_reboot_actions = [
4 | "org.freedesktop.login1.power-off",
5 | "org.freedesktop.login1.power-off-multiple-sessions",
6 | "org.freedesktop.login1.reboot",
7 | "org.freedesktop.login1.reboot-multiple-sessionsaction.id",
8 | ];
9 |
10 | if (shutdown_reboot_actions.indexOf(action.id) > -1) {
11 | if (subject.user == "%ALLSKY_USER%") {
12 | return polkit.Result.YES;
13 | }
14 | }
15 |
16 |
17 | // Allow indi-allsky user to manage network
18 | const network_actions = [
19 | "org.freedesktop.NetworkManager.enable-disable-wifi",
20 | "org.freedesktop.NetworkManager.network-control",
21 | "org.freedesktop.NetworkManager.wifi.share.protected",
22 | "org.freedesktop.NetworkManager.settings.modify.system",
23 | "org.freedesktop.NetworkManager.wifi.scan",
24 | "org.freedesktop.NetworkManager.settings.modify.own",
25 | ];
26 |
27 | if (network_actions.indexOf(action.id) > -1) {
28 | if (subject.user == "%ALLSKY_USER%") {
29 | return polkit.Result.YES;
30 | }
31 | }
32 |
33 |
34 | // Allow indi-allsky user to mount USB disks
35 | const mount_usb_actions = [
36 | "org.freedesktop.udisks2.filesystem-mount-system",
37 | "org.freedesktop.udisks2.filesystem-mount",
38 | "org.freedesktop.udisks2.filesystem-mount-other-seat",
39 | "org.freedesktop.udisks2.power-off-drive",
40 | "org.freedesktop.udisks2.power-off-drive-other-seat",
41 | "org.freedesktop.udisks2.eject-media",
42 | "org.freedesktop.udisks2.eject-media-other-seat",
43 | "org.freedesktop.udisks2.filesystem-unmount-others",
44 | ];
45 |
46 | if (mount_usb_actions.indexOf(action.id) > -1) {
47 | if (subject.user == "%ALLSKY_USER%") {
48 | return polkit.Result.YES;
49 | }
50 | }
51 |
52 |
53 | // Allow indi-allsky user to set system time
54 | const set_time_actions = [
55 | "org.freedesktop.timedate1.set-time",
56 | "org.freedesktop.timedate1.set-timezone",
57 | "org.freedesktop.timedate1.set-ntp",
58 | ];
59 |
60 | if (set_time_actions.indexOf(action.id) > -1) {
61 | if (subject.user == "%ALLSKY_USER%") {
62 | return polkit.Result.YES;
63 | }
64 | }
65 | });
66 |
--------------------------------------------------------------------------------
/indi_allsky/flask/templates/config_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block title %}indi-allsky: Config History{% endblock %}
4 |
5 | {% block head %}
6 |
8 |
9 |
10 |
13 |
14 | {% endblock %}
15 |
16 | {% block content %}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | | ID |
25 | Create |
26 | User |
27 | Level |
28 | Encrypted Passwords |
29 | Link |
30 | Note |
31 |
32 |
33 |
34 | {% for config in config_list %}
35 |
36 | | {{ config.id}} |
37 | {{ config.createDate.strftime('%Y-%m-%d %H:%M:%S') }} |
38 | {{ config.username }} |
39 | {{ config.level }} |
40 | {{ config.encrypted }} |
41 | Download |
42 | {{ config.note }} |
43 |
44 | {% endfor %}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Note: Configs from deleted users will not be displayed, but they are still in the database
53 |
54 |
55 |
56 |
57 |
58 |
81 |
82 | {% endblock %}
83 |
84 |
--------------------------------------------------------------------------------
/indi_allsky/devices/sensors/tempSensorDs18x20.py:
--------------------------------------------------------------------------------
1 | import io
2 | from pathlib import Path
3 | import logging
4 |
5 | from .sensorBase import SensorBase
6 | from ... import constants
7 | from ..exceptions import SensorReadException
8 |
9 |
10 | logger = logging.getLogger('indi_allsky')
11 |
12 |
13 | class TempSensorDs18x20(SensorBase):
14 |
15 | METADATA = {
16 | 'name' : 'DS18x20',
17 | 'description' : 'DS18x20 1-wire Temperature Sensor',
18 | 'count' : 1,
19 | 'labels' : (
20 | 'Temperature',
21 | ),
22 | 'types' : (
23 | constants.SENSOR_TEMPERATURE,
24 | ),
25 | }
26 |
27 |
28 | def __init__(self, *args, **kwargs):
29 | super(TempSensorDs18x20, self).__init__(*args, **kwargs)
30 |
31 | logger.warning('Initializing [%s] DS18x20 temperature device', self.name)
32 |
33 |
34 | w1_base_dir = Path('/sys/bus/w1/devices/')
35 |
36 | if not w1_base_dir.is_dir():
37 | raise Exception('1-Wire interface is not enabled')
38 |
39 |
40 | # Get all folders beginning with 28
41 | device_folders = list(w1_base_dir.glob('28*'))
42 |
43 |
44 | if len(device_folders) == 0:
45 | raise Exception('DS18x20 device not found')
46 | if len(device_folders) > 1:
47 | logger.warning('Multiple DS18x20 devices detected')
48 |
49 |
50 | # multiple devices might be available, we only want one
51 | self.ds_temp_file = device_folders[0].joinpath('temperature')
52 |
53 |
54 | if not self.ds_temp_file.is_file():
55 | raise Exception('DS18x20 temperature property not found')
56 |
57 |
58 | def update(self):
59 |
60 | with io.open(self.ds_temp_file, 'r') as f_temp:
61 | temp_str = f_temp.readline().rstrip()
62 |
63 |
64 | try:
65 | temp_c = int(temp_str) / 1000.0
66 | except RuntimeError as e:
67 | raise SensorReadException(str(e)) from e
68 |
69 |
70 | logger.info('[%s] DS18x20 - temp: %0.1fc', self.name, temp_c)
71 |
72 |
73 | if self.config.get('TEMP_DISPLAY') == 'f':
74 | current_temp = self.c2f(temp_c)
75 | elif self.config.get('TEMP_DISPLAY') == 'k':
76 | current_temp = self.c2k(temp_c)
77 | else:
78 | current_temp = temp_c
79 |
80 |
81 | data = {
82 | 'data' : (
83 | current_temp,
84 | ),
85 | }
86 |
87 | return data
88 |
89 |
--------------------------------------------------------------------------------
/indi_allsky/devices/sensors/tempSensorMlx90614.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from .sensorBase import SensorBase
4 | from ... import constants
5 | from ..exceptions import SensorReadException
6 |
7 |
8 | logger = logging.getLogger('indi_allsky')
9 |
10 |
11 | class TempSensorMlx90614(SensorBase):
12 |
13 | def update(self):
14 |
15 | try:
16 | temp_c = float(self.mlx.ambient_temperature)
17 | object_temp_c = float(self.mlx.object_temperature)
18 | except RuntimeError as e:
19 | raise SensorReadException(str(e)) from e
20 |
21 |
22 | logger.info('[%s] MLX90614 - ambient temp: %0.1fc, object temp: %0.1fc', self.name, temp_c, object_temp_c)
23 |
24 |
25 | if self.config.get('TEMP_DISPLAY') == 'f':
26 | current_temp = self.c2f(temp_c)
27 | object_temp = self.c2f(object_temp_c)
28 | elif self.config.get('TEMP_DISPLAY') == 'k':
29 | current_temp = self.c2k(temp_c)
30 | object_temp = self.c2k(object_temp_c)
31 | else:
32 | current_temp = temp_c
33 | object_temp = object_temp_c
34 |
35 |
36 | data = {
37 | 'data' : (
38 | current_temp,
39 | object_temp,
40 | ),
41 | }
42 |
43 | return data
44 |
45 |
46 | class TempSensorMlx90614_I2C(TempSensorMlx90614):
47 |
48 | METADATA = {
49 | 'name' : 'MLX90614 (i2c)',
50 | 'description' : 'MLX90614 i2c Sky Temperature Sensor',
51 | 'count' : 2,
52 | 'labels' : (
53 | 'Temperature',
54 | 'Sky Temperature',
55 | ),
56 | 'types' : (
57 | constants.SENSOR_TEMPERATURE,
58 | constants.SENSOR_TEMPERATURE,
59 | ),
60 | }
61 |
62 |
63 | def __init__(self, *args, **kwargs):
64 | super(TempSensorMlx90614_I2C, self).__init__(*args, **kwargs)
65 |
66 | i2c_address_str = kwargs['i2c_address']
67 |
68 | import board
69 | #import busio
70 | import adafruit_mlx90614
71 |
72 | i2c_address = int(i2c_address_str, 16) # string in config
73 |
74 | logger.warning('Initializing [%s] MLX90614 I2C temperature device @ %s', self.name, hex(i2c_address))
75 | i2c = board.I2C()
76 | #i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
77 | #i2c = busio.I2C(board.D1, board.D0, frequency=100000) # Raspberry Pi i2c bus 0 (pins 28/27)
78 | self.mlx = adafruit_mlx90614.MLX90614(i2c, address=i2c_address)
79 |
80 |
--------------------------------------------------------------------------------