├── 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 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/lightning-charge-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/subtract.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/terminal-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/layout-three-columns.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/hdd-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/input-cursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/toggles.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/camera-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/aspect-ratio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/stopwatch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/cloud-moon-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/grid-3x2-gap-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 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 | 2 | 3 | 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 | 2 | 3 | 4 | 5 | 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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /indi_allsky/flask/static/svg/stars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 2 | 3 | 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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for task in task_list %} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% endfor %} 41 | 42 |
IDDateQueueActionStateResult
{{ task.id }}{{ task.createDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ task.queue }}{{ task.action }}{{ task.state }}{{ task.result }}
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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for user in user_list %} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% endfor %} 41 | 42 |
CreateUsernameNameActiveStaffAdmin
{{ user.createDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ user.username }}{{ user.name }}{{ user.active }}{{ user.staff }}{{ user.admin }}
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 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% for notice in notice_list %} 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {% endfor %} 41 | 42 |
IDCategoryCreateExpireAckedNotification
{{ notice.id }}{{ notice.category }}{{ notice.createDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ notice.expireDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ notice.ack }}{{ notice.notification }}
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 |
32 |
33 | 34 | 35 |
36 |
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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for row in image_lag_q %} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | {% endfor %} 46 | 47 |
DateExposureElapsedDeltaPeriodProcessing
{{ row.createDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ "%0.7f"|format(row.exposure) }}{{ "%0.2f"|format(row.exp_elapsed | float) }}{{ "%+0.3f"|format(row.delta | float) }}{{ row.lag_diff }}{{ "%0.2f"|format(row.process_elapsed | float) }}
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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% for row in rolling_adu_q %} 39 | 40 | 47 | {% endfor %} 48 | 49 |
DateCountExposure AvgADU AvgSQM AvgStars Avg
{{ 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 |
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 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | {% for config in config_list %} 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | {% endfor %} 45 | 46 |
IDCreateUserLevelEncrypted
Passwords
LinkNote
{{ config.id}}{{ config.createDate.strftime('%Y-%m-%d %H:%M:%S') }}{{ config.username }}{{ config.level }}{{ config.encrypted }}Download{{ config.note }}
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 | --------------------------------------------------------------------------------