diff --git a/README.md b/README.md index 28845ed72..1d1b266a5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Allsky Camera ![Release 0.7](https://img.shields.io/badge/Release-0.7-green.svg) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MEBU2KN75G2NG&source=url) +# Allsky Camera ![Release 0.8](https://img.shields.io/badge/Release-0.8-green.svg) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MEBU2KN75G2NG&source=url) @@ -58,7 +58,7 @@ There is no 1-click update yet so until then, the easiest is to backup your conf Here's a quick overview of the configuration files. -the first one is called **settings.json**. It contains the camera parameters such as exposure, gain but also latitude, longitude, etc. +the first one is called **settings.json**. It contains the camera parameters such as exposure, gain but also latitude, longitude, etc. Many settings have both a daytime ("dayXXXX") and nighttime ("nightXXXX") version. ```shell nano settings.json @@ -68,28 +68,34 @@ nano settings.json | ----------- | ----------- | ----------------| | width | 0 | 0 means max width. Look up your camera specifications to know what values are supported | | height | 0 | 0 means max height. Look up your camera specifications to know what values are supported | -| exposure | 10000 | **Night** time exposure in milliseconds. During the day, auto-exposure is used. | -| maxexposure | 20000 | This is the maximum exposure for **night** images when using auto-exposure. During the day, auto-exposure is always used. | -| autoexposure | 1 | Set to 0 to disable auto-exposure at **night**. Auto-exposure delivers properly exposed images throughout the night even if the overall brightness of the sky changes (cloud cover, moon, aurora, etc). When set to 1, *maxexposure* value will be used as the delay between timelapse frames. | -| gain | 50 | Gain for **Night** images. Varies from 0 to 600. During the day, gain is always set to 0 | -| maxgain | 200 | Maximum gain for **night** images when using auto-gain.| -| autogain | 0 | Set to 1 to allow auto-gain at **night**. This mode will adjust the gain of night images when the overall brightness of the sky changes (cloud cover, moon, aurora, etc). **Avoid using autoexposure and autogain together** as it produces unpredicatble results (dark frames, but not always).| -| gamma | 50 | Varies between 0 and 100. This setting increases or decreases contrast between dark and bright areas. | -| brightness | 50 | Varies between 0 and 100. This setting changes the amount of light in the image. | +| dayautoexposure | 1 | Set to 0 to disable auto-exposure during **daytime**. Auto-exposure delivers properly exposed images throughout the day even if the overall brightness of the sky changes (cloud cover, sun, etc). Since daytime exposures are short, there is not daytime "maxexposure". This option is usually only disabled for testing. | +| dayexposure | 1 | **Day** time manual exposure time in milliseconds. Normally daytime auto-exposure will be used; if so, this value is used as a starting exposure. | +| daybrightness | 50 | Varies between 0 and 600. This setting changes the amount of light in **daytime** images. | +| daydelay | 5000 | Time in milliseconds to wait between 2 frames during the day. | +| daybin | 1 | bin 2 collects the light from 2x2 photosites to form 1 pixel on the image. bin 3 uses 3x3 photosites, etc. Increasing the bin results in smaller images and reduces the need for long exposure. Look up your camera specifications to know what values are supported. This variable is usually only changed for testing. | +| nightautoexposure | 1 | Set to 0 to disable auto-exposure at **night**. Auto-exposure delivers properly exposed images throughout the night even if the overall brightness of the sky changes (cloud cover, moon, aurora, etc). When set to 1, *maxexposure* value will be used as the delay between timelapse frames. | +| nightmaxexposure | 20000 | This is the maximum exposure for **night** images when using auto-exposure. +| nightexposure | 10000 | **Night** time exposure in milliseconds. | +| nightautogain | 0 | Set to 1 to allow auto-gain at **night**. This mode will adjust the gain of night images when the overall brightness of the sky changes (cloud cover, moon, aurora, etc). **Avoid using autoexposure and autogain together** as it produces unpredicatble results (dark frames, but not always).| +| nightmaxgain | 200 | Maximum gain for **night** images when using auto-gain.| +| nightgain | 50 | Gain for **Night** images. Varies from 0 to 600. During the day, gain is always set to 0. | +| nightbin | 1 | Similar to "daybin" but for night. | +| nightbrightness | 50 | Varies between 0 and 600. This setting changes the amount of light in **nighttime** images. | +| gamma | 50 | Varies between 0 and 100. This setting increases or decreases contrast between dark and bright areas. This is not supported by all cameras. | +| autowhitebalance | 0 | Sets auto white balance. When used, "wbr" and "wbb" are used as starting points. | | wbr | 53 | Varies between 0 and 100. This is the intensity of the red component of the image. | | wbb | 90 | Varies between 0 and 100. This is the intensity of the blue component of the image. | -| bin | 1 | bin 2 collects the light from 2x2 photosites to form 1 pixel on the image. bin 3 uses 3x3 photosites, etc. Increasing the bin results in smaller images and reduces the need for long exposure. Look up your camera specifications to know what values are supported | | delay | 10 | Time in milliseconds to wait between 2 frames at night. | -| daytimeDelay | 5000 | Time in milliseconds to wait between 2 frames during the day. | | type | 1 | Image format. 0=RAW 8 bits, 1=RGB 24 bits, 2=RAW 16 bits | | quality | 95 | Compression of the image. 0(low quality) to 100(high quality) for JPG images, 0 to 9 for PNG | +| autousb | 0 | Set to 1 to enable auto USB bandwidth. This option is primarily for testing. | | usb | 40 | This is the USB bandwidth. Varies from 40 to 100. | | filename | image.jpg | this is the name used across the app. Supported extensions are JPG and PNG. | | flip | 0 | 0=Original, 1=Horizontal, 2=Vertical, 3=Both | | text | text | Text overlay. **Note**: It is replaced by timestamp if time=1 | | extratext | | (ZWO ONLY) The FULL path to a text file which will be displayed under the Exposure/Gain. The file can contain multiple lines which will be displayed underneath each other | | extratextage | 600 | (ZWO ONLY) If using the extra text file then it must be updated within this number of seconds, if not it will not be displayed. Set to 0 to ignore this check and always didplay it | -| textlineheight | 30 | (ZWO ONLY) The line height of the text displayed in the image, if you chnage the font size the adjust this value if required | +| textlineheight | 30 | (ZWO ONLY) The line height of the text displayed in the image, if you chnage the font size then adjust this value if required | | textx | 15 | Horizontal text placement from the left | | texty | 35 | Vertical text placement from the top | | fontname | 0 | Font type for the overlay. 0=Simplex, 1=Plain, 2=Duplex, 3=Complex, 4=Triplex, 5=Complex small, 6=Script simplex, 7=Script complex | @@ -98,12 +104,15 @@ nano settings.json | fontsize | 7 | Font size | | fonttype | 0 | Controls the smoothness of the fonts. 0=Antialiased, 1=8 Connected, 2=4 Connected. | | fontline | 1 | font line thickness | +| outlinefont | 0 | Set to 1 to add an outline to the text overlay to improve contrast. | | latitude | 60.7N | Latitude of the camera. N for North and S for South | longitude | 135.05W | longitude of the camera. E for East and W for West | | angle | -6 | Altitude of the sun above or below the horizon at which capture should start/stop. Can be negative (sun below horizon) or positive (sun above horizon). 0=Sunset, -6=Civil twilight, -12=Nautical twilight, -18=Astronomical twilight. -| time | 1 | Replaces the text overlay | -| darkframe | 0 | Set to 1 to enable dark frame capture. In this mode, overlays are hidden and the image is saved as dark.png by default | -| showDetails | 1 | Displays the exposure, gain and temperature in the overlay | +| time | 1 | Replaces the text overlay with the time the picture was taken. | +| timeformat | %Y%m%d %H:%M:%S | Determines the format of the displayed time. See strftime(3). Use _ (underscore) for spaces. | +| darkframe | 0 | Set to 1 to enable dark frame capture. In this mode, overlays are hidden. | +| showDetails | 1 | Displays the exposure, gain and temperature in the overlay (OBSOLETE) | +| notificationimages | 1 | Set to 0 to disable notification images, e.g., "Camera off during day" if daytime images are not being taken. | The second file called **config.sh** lets you configure the overall behavior of the camera. Options include functionalities such as upload, timelapse, dark frame location, keogram. @@ -120,6 +129,7 @@ nano config.sh | TIMELAPSE | true | Build a timelapse at the end of the night | | TIMELAPSEWIDTH | 0 | Overwrite the width of the generated timelapse, must be divisible by 2 | TIMELAPSEHEIGHT | 0 | Overwrite the height of the generated timelapse, must be divisible by 2 +| TIMELAPSE_BITRATE | 2000k | Bitrate of the timelapse video. Higher numbers produce better quality but bigger files. Don't forget to include the trailing "k". | FPS | 25 | The timelapse frame rate (frames per second) | KEOGRAM | true | Builds a keogram at the end of the night | | UPLOAD_KEOGRAM | false | Set to true to upload the keogram to your server | @@ -142,7 +152,20 @@ nano config.sh | AUTO_STRETCH | false | If enabled the captured image will be stretched | | AUTO_STRETCH_AMOUNT | 10 | Indicates how much to increase the contrast. For example, 0 is none, 3 is typical and 20 is a lot | | AUTO_STRETCH_MID_POINT | 10% | Indicates where the maximum change 'slope' in contrast should fall in the resultant image (0 is white; 50% is middle-gray; 100% is black). | -| CAMERA_SETTINGS | /home/pi/allsky/settings.json | Path to the camera settings file. **Note**: If using the GUI, this path will change to /etc/raspap/settings.json | +| RESIZE_UPLOADS | false | Set to true to resize uploaded pictures | +| RESIZE_UPLOADS_SIZE | 962x720 | Sets the width x height of resized images being uploaded | +| THUMBNAIL_SIZE_X | 100 | Sets the width of thumbnails | +| THUMBNAIL_SIZE_Y | 75 | Sets the height of thumbnails | +| REMOVE_BAD_IMAGES | false | Scan for, and remove corrupt or too bright/too dark images before generating keograms and startrails | +| REMOVE_BAD_IMAGES_THRESHOLD_LOW | 1 | Images whose mean brightness is below this percent will be removed | +| REMOVE_BAD_IMAGES_THRESHOLD_HIGH | 90 | Images whose mean brightness is above this percent will be removed (max: 100) | +| UHUBCTL_PATH | n/a | If you have the "uhubctl" command installed (it resets the USB bus), enter its path name | +| UHUBCTL_PORT | n/a | Enter the USB port the camera is on. Port 1 is USB 2.0 and port 2 is USB 3.0 | +| IMG_DIR | allsky | Location of the image the website will use. "allsky" is /var/www/html/allsky. Set to "current" to use /home/pi/allsky. | +| IMG_PREFIX | liveview- | An optional prefix on the website image file name, before "image.jpg" (or whatever your image is called) | +| TEMPERATURE | C | How to display the temperature in image overlays as well as on the "System" page of the web GUI. | +| CAMERA_SETTINGS_DIR | /etc/raspap | Path to the camera settings file | +| CAMERA_SETTINGS | /home/pi/allsky/settings.json | Name of the camera settings file. **Note**: If using the GUI, this path will change to /etc/raspap/settings.json | When using the cropping options the image is cropped from the center so you will need to experiment with the correct width and height values. Normally there will be no need to amend the offset values. @@ -273,7 +296,7 @@ GUI method: * Open the Camera Settings tab and set Dark Frame to Yes. * Hit the Save button * Dark frames are created in a `darks` directory. A new dark is created every time the sensor temperature changes by 1 degree C. -* On the Camera Settings tab and set Dark Frame to No. +* On the Camera Settings tab set Dark Frame to No. * Hit the Save button * Remove the cover from the lens/dome * Open the scripts editor tab, load `config.sh` and set `DARK_FRAME_SUBTRACTION` to true @@ -350,7 +373,7 @@ The startrails program is used by the `endOfNight.sh` script. The program takes 4 arguments: - Source directory - File extension -- Brightness treshold to avoid over-exposure: 0 (black) to 1 (white). +- Brightness threshold to avoid over-exposure: 0 (black) to 1 (white). - Output file Example when running the program manually: @@ -393,7 +416,7 @@ This will compile the new code and create a new binary. If you have set the upload options to true in `config.sh`, that means you probably already have a website. If you want to display a live view of your sky on your website like in this [example](http://www.thomasjacquin.com/allsky), you can donwload the source files from this repository: [https://github.com/thomasjacquin/allsky-website.git](https://github.com/thomasjacquin/allsky-website.git). -If you want to host the website on the raspberry Pi, run the following command. Note that this website is installed on the same webserver as the GUI. Currently, reinstalling the GUI will wipe you website. +If you want to host the website on the raspberry Pi, run the following command. Note that this website is installed on the same webserver as the GUI. Currently, reinstalling the GUI will wipe your website. ``` website/install.sh @@ -436,6 +459,18 @@ If you've built an allsky camera, please send me a message and I'll add you to t * Configuration variables to crop black area around image * Timelapse frame rate setting * Changed font size default value +* version **0.8**: Workaround for ZWO daytime autoexposure bug. + * Improved exposure transitions between day and night so there's not a huge change in brightness. + * Decrease in ZWO sensor temperature. + * Lots of new settings, including splitting some settings into day and night versions. + * Error checking and associated log messages added in many places. + * Ability to have "notification" images displayed, such as "Allsky is starting up" and "Taking dark frames". + * Ability to set size uploaded images are resized to. + * Ability to set thumbnail size. + * Ability to delete bad images (corrupt and too light/dark). + * Ability to set an image file name prefix. + * Ability to reset USB bus if ZWO camera isn't found (requires "uhubctl" command to be installed). + * Ability to specify format of time displayed on image and temperature displayed in Celcius, Fahrenheit, or both. ## Donation diff --git a/allsky.sh b/allsky.sh index 996534f1e..ab6ba8df4 100755 --- a/allsky.sh +++ b/allsky.sh @@ -6,42 +6,76 @@ then fi # reset auto camera selection, so $ALLSKY_HOME/config.sh do not pick up old camera selection +cd $ALLSKY_HOME echo "" > "$ALLSKY_HOME/autocam.sh" source $ALLSKY_HOME/config.sh +# Make it easy to find the beginning of this run in the log file. +echo " ***** Starting AllSky *****" echo "Making sure allsky.sh is not already running..." ps -ef | grep allsky.sh | grep -v $$ | xargs "sudo kill -9" 2>/dev/null +mv -f log.txt OLD_log.txt 2> /dev/null # save the prior log file for debugging +> log.txt + # old/regular manual camera selection mode => exit if no requested camera was found -RPiHQIsPresent=$(vcgencmd get_camera) -if [[ $CAMERA == "RPiHQ" && $RPiHQIsPresent != "supported=1 detected=1" ]]; then -echo "RPiHQ Camera not found. Exiting." >&2 +if [[ $(vcgencmd get_camera) == "supported=1 detected=1" ]]; then + RPiHQIsPresent=1 +else + RPiHQIsPresent=0 +fi +if [[ $CAMERA == "RPiHQ" && $RPiHQIsPresent -eq 0 ]]; then + echo "RPiHQ Camera not found. Exiting." >&2 sudo systemctl stop allsky exit 0 fi -ZWOIsPresent=$(lsusb -D $(lsusb | awk '/ 03c3:/ { bus=$2; dev=$4; gsub(/[^0-9]/,"",dev); print "/dev/bus/usb/"bus"/"dev;}') | grep -c 'iProduct .*ASI[0-9]') -if [[ $CAMERA == "ZWO" && $ZWOIsPresent -eq 0 ]]; then - echo "ZWO Camera not found. Exiting." >&2 - sudo systemctl stop allsky - exit 0 +if [[ $CAMERA != "RPiHQ" ]]; then + # Use two commands to better aid debugging when camera isn't found. + # xxxxx This doesn't catch cases where CAMERA is "auto" and we should use ZWO. + ZWOdev=$(lsusb | awk '/ 03c3:/ { bus=$2; dev=$4; gsub(/[^0-9]/,"",dev); print "/dev/bus/usb/"bus"/"dev;}') + ZWOIsPresent=$(lsusb -D ${ZWOdev} 2>/dev/null | grep -c 'iProduct .*ASI[0-9]') + if [[ $CAMERA == "ZWO" && $ZWOIsPresent -eq 0 ]]; then + echo "ZWO Camera not found..." >&2 + if [[ $ZWOdev == "" ]]; then + echo " and no USB entry found for it." >&2 + else + echo " but USB entry '$ZWOdev' found for it." >&2 + fi + if [ "$UHUBCTL_PATH" != "" ] ; then + if tty --silent ; then + echo " Resetting USB ports; restart allsky.sh when done." >&2 + else + echo " Resetting USB ports and restarting." >&2 + # The service will automatically restart this script. + fi + sudo "$UHUBCTL_PATH" -a cycle -l "$UHUBCTL_PORT" + exit 0 + else + echo " Exiting." >&2 + echo " If you have the 'uhubctl' command installed, add it to config.sh." >&2 + echo " In the meantime, try running it to reset the USB bus." >&2 + sudo systemctl stop allsky + exit 0 + fi + fi fi # CAMERA AUTOSELECT -# exit if no camare found at all -if [[ $CAMERA -eq "auto" ]]; then +# exit if no camera found at all +if [[ $CAMERA == "auto" ]]; then echo "Trying to automatically choose between ZWO and RPI camera" - if [[ $ZWOIsPresent -eq 0 && $RPiHQIsPresent != "supported=1 detected=1" ]]; then + if [[ $ZWOIsPresent -eq 0 && $RPiHQIsPresent -eq 0 ]]; then echo "None of RPI or ZWO Cameras were found. Exiting." >&2 sudo systemctl stop allsky exit 0 fi # prioritize ZWO camera if exists, and use RPI camera otherwise if [[ $ZWOIsPresent -eq 0 ]]; then - echo "No ZWO camera found. Choosing RPI" + # echo "No ZWO camera found. Choosing RPI" # CAMERA is displayed below; don't need it here too CAMERA="RPiHQ" else - echo "ZWO camera found. Choosing ZWO" + # echo "ZWO camera found. Choosing ZWO" # CAMERA is displayed below; don't need it here too CAMERA="ZWO" fi @@ -49,8 +83,13 @@ if [[ $CAMERA -eq "auto" ]]; then CAMERA_SETTINGS="$CAMERA_SETTINGS_DIR/settings_$CAMERA.json" fi +# Optionally display a notification image +USE_NOTIFICATION_IMAGES=$(jq -r '.notificationimages' "$CAMERA_SETTINGS") +if [ "$USE_NOTIFICATION_IMAGES" = "1" ] ; then + $ALLSKY_HOME/scripts/copy_notification_image.sh "StartingUp" 2>&1 +fi -echo "Settings check done" +# echo "Settings check done" echo "CAMERA: ${CAMERA}" echo "CAMERA_SETTINGS: ${CAMERA_SETTINGS}" # save auto camera selection for the current session, will be read in "$ALLSKY_HOME/config.sh" file @@ -60,27 +99,57 @@ echo "export CAMERA=$CAMERA" > "$ALLSKY_HOME/autocam.sh" source $ALLSKY_HOME/scripts/filename.sh echo "Starting allsky camera..." -cd $ALLSKY_HOME -# Building the arguments to pass to the capture binary -ARGUMENTS="" +# Building the arguments to pass to the capture binary. +# Want to allow spaces in arguments so need to put quotes around them, +# but in order for it to work need to make ARGUMENTS an array. +ARGUMENTS=() KEYS=( $(jq -r 'keys[]' $CAMERA_SETTINGS) ) for KEY in ${KEYS[@]} do - ARGUMENTS="$ARGUMENTS -$KEY `jq -r '.'$KEY $CAMERA_SETTINGS` " + K="`jq -r '.'$KEY $CAMERA_SETTINGS`" + ARGUMENTS+=(-$KEY "$K") done # When using a desktop environment (Remote Desktop, VNC, HDMI output, etc), a preview of the capture can be displayed in a separate window # The preview mode does not work if allsky.sh is started as a service or if the debian distribution has no desktop environment. if [[ $1 == "preview" ]] ; then - ARGUMENTS="$ARGUMENTS -preview 1" + ARGUMENTS+=(-preview 1) fi -ARGUMENTS="$ARGUMENTS -daytime $DAYTIME" +ARGUMENTS+=(-daytime $DAYTIME) -echo "$ARGUMENTS">>log.txt +# Determine if we're called from the service (tty will fail). +tty --silent +if [ $? -eq 0 ] ; then + TTY=1 +else + TTY=0 +fi +ARGUMENTS+=(-tty $TTY) +Z="" +for A in ${ARGUMENTS[@]} +do + Z="$Z $A" +done +echo "$Z" >> log.txt + +RETCODE=0 if [[ $CAMERA == "ZWO" ]]; then - $ALLSKY_HOME/capture $ARGUMENTS + $ALLSKY_HOME/capture "${ARGUMENTS[@]}" + RETCODE=$? + echo "capture exited with retcode=$RETCODE" + elif [[ $CAMERA == "RPiHQ" ]]; then - $ALLSKY_HOME/capture_RPiHQ $ARGUMENTS + $ALLSKY_HOME/capture_RPiHQ "${ARGUMENTS[@]}" + RETCODE=$? +else + exit 1 fi + +if [ "$USE_NOTIFICATION_IMAGES" = "1" -a "$RETCODE" -ne 0 ] ; then + # "capture" will do this if it exited with 0. + $ALLSKY_HOME/scripts/copy_notification_image.sh "NotRunning" 2>&1 +fi + +exit $RETCODE diff --git a/capture.cpp b/capture.cpp index f0060b12f..11f1a3e03 100644 --- a/capture.cpp +++ b/capture.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #define KNRM "\x1B[0m" #define KRED "\x1B[31m" @@ -34,12 +35,16 @@ #define KCYN "\x1B[36m" #define KWHT "\x1B[37m" +#define USE_HISTOGRAM // use the histogram code as a workaround to ZWO's bug + +#define US_IN_MS 1000 // microseconds in a millisecond +#define MS_IN_SEC 1000 // milliseconds in a second +#define US_IN_SEC (US_IN_MS * MS_IN_SEC) // microseconds in a second + //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- cv::Mat pRgb; -char nameCnt[128]; -char const *fileName = "image.jpg"; std::vector compression_parameters; bool bMain = true, bDisplay = false; std::string dayOrNight; @@ -48,7 +53,112 @@ bool bSaveRun = false, bSavingImg = false; pthread_mutex_t mtx_SaveImg; pthread_cond_t cond_SatrtSave; -int debugLevel = 0; +// These are global so they can be used by other routines. +#define NOT_SET -1 // signifies something isn't set yet +ASI_CONTROL_CAPS ControlCaps; +void *retval; +int gotSignal = 0; // did we get a SIGINT (from keyboard) or SIGTERM (from service)? +int iNumOfCtrl = 0; +int CamNum = 0; +pthread_t thread_display = 0; +pthread_t hthdSave = 0; +int numExposures = 0; // how many valid pictures have we taken so far? +int currentGain = NOT_SET; + +// Some command-line and other option definitions needed outside of main(): +int tty = 0; // 1 if we're on a tty (i.e., called from the shell prompt). +#define DEFAULT_NOTIFICATIONIMAGES 1 +int notificationImages = DEFAULT_NOTIFICATIONIMAGES; +#define DEFAULT_FILENAME "image.jpg" +char const *fileName = DEFAULT_FILENAME; +#define DEFAULT_TIMEFORMAT "%Y%m%d %H:%M:%S" // format the time should be displayed in +char const *timeFormat = DEFAULT_TIMEFORMAT; +#define DEFAULT_ASIDAYEXPOSURE 500 // microseconds - good starting point for daytime exposures +int asiDayExposure = DEFAULT_ASIDAYEXPOSURE; +#define DEFAULT_DAYAUTOEXPOSURE 1 +int asiDayAutoExposure = DEFAULT_DAYAUTOEXPOSURE; // is it on or off for daylight? +#define DEFAULT_DAYDELAY (5 * MS_IN_SEC) // 5 seconds +int dayDelay = DEFAULT_DAYDELAY; // Delay in milliseconds. +#define DEFAULT_NIGHTDELAY (10 * MS_IN_SEC) // 10 seconds +int nightDelay = DEFAULT_NIGHTDELAY; // Delay in milliseconds. +#define DEFAULT_ASINIGHTMAXEXPOSURE (10 * US_IN_MS) // 10 ms +int asiNightMaxExposure = DEFAULT_ASINIGHTMAXEXPOSURE; +#define DEFAULT_GAIN_TRANSITION_TIME 5 // user specifies minutes +int gainTransitionTime = DEFAULT_GAIN_TRANSITION_TIME; +ASI_BOOL currentAutoExposure = ASI_FALSE; // is Auto Exposure currently on or off? + +#ifdef USE_HISTOGRAM +long cameraMaxAutoExposureUS = NOT_SET; // camera's max auto exposure in us +#define DEFAULT_BOX_SIZEX 500 +#define DEFAULT_BOX_SIZEY 500 +int histogramBoxSizeX = DEFAULT_BOX_SIZEX; // 500 px x 500 px box. Must be a multiple of 2. +int histogramBoxSizeY = DEFAULT_BOX_SIZEY; +#define DEFAULT_BOX_FROM_LEFT 0.5 +#define DEFAULT_BOX_FROM_TOP 0.5 +// % from left/top side that the center of the box is. 0.5 == the center of the image's X/Y axis +float histogramBoxPercentFromLeft = DEFAULT_BOX_FROM_LEFT; +float histogramBoxPercentFromTop = DEFAULT_BOX_FROM_TOP; +#endif // USE_HISTOGRAM + +char debugText[500]; // buffer to hold debug messages displayed by displayDebugText() +int debugLevel = 0; +/** + * Helper function to display debug info +**/ +void displayDebugText(const char * text, int requiredLevel) { + if (debugLevel >= requiredLevel) { + printf("%s", text); + } +} + +// Make sure we don't try to update a non-updateable control, and check for errors. +ASI_ERROR_CODE setControl(int CamNum, ASI_CONTROL_TYPE control, long value, ASI_BOOL makeAuto) +{ + ASI_ERROR_CODE ret = ASI_SUCCESS; + int i; + for (i = 0; i < iNumOfCtrl && i <= control; i++) // controls are sorted 1 to n + { + ret = ASIGetControlCaps(CamNum, i, &ControlCaps); + +#ifdef USE_HISTOGRAM + // Keep track of the camera's max auto exposure so we don't try to exceed it. + if (ControlCaps.ControlType == ASI_AUTO_MAX_EXP && cameraMaxAutoExposureUS == NOT_SET) + { + // MaxValue is in MS so convert to microseconds + cameraMaxAutoExposureUS = ControlCaps.MaxValue * US_IN_MS; + } +#endif + + if (ControlCaps.ControlType == control) + { + if (ControlCaps.IsWritable) + { + if (value > ControlCaps.MaxValue) + { + printf("WARNING: Value of %ld greater than max value allowed (%ld) for control '%s' (#%d).\n", value, ControlCaps.MaxValue, ControlCaps.Name, ControlCaps.ControlType); + value = ControlCaps.MaxValue; + } else if (value < ControlCaps.MinValue) + { + printf("WARNING: Value of %ld less than min value allowed (%ld) for control '%s' (#%d).\n", value, ControlCaps.MinValue, ControlCaps.Name, ControlCaps.ControlType); + value = ControlCaps.MinValue; + } + if (makeAuto == ASI_TRUE && ControlCaps.IsAutoSupported == ASI_FALSE) + { + printf("WARNING: control '%s' (#%d) doesn't support auto mode.\n", ControlCaps.Name, ControlCaps.ControlType); + makeAuto = ASI_FALSE; + } + ret = ASISetControlValue(CamNum, control, value, makeAuto); + } else { + printf("ERROR: ControlCap: '%s' (#%d) not writable; not setting to %ld.\n", ControlCaps.Name, ControlCaps.ControlType, value); + ret = ASI_ERROR_INVALID_MODE; // this seemed like the closest error + } + return ret; + } + } + sprintf(debugText, "NOTICE: Camera does not support ControlCap # %d; not setting to %ld.\n", control, value); + displayDebugText(debugText, 3); + return ASI_ERROR_INVALID_CONTROL_TYPE; +} //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- @@ -64,7 +174,7 @@ void cvText(cv::Mat &img, const char *text, int x, int y, double fontsize, int l { if (imgtype == ASI_IMG_RAW16) { - unsigned long fontcolor16 = createRGB(fontcolor[2], fontcolor[1], fontcolor[0]); + unsigned long fontcolor16 = createRGB(fontcolor[2], fontcolor[1], fontcolor[0]); if (outlinefont) cv::putText(img, text, cvPoint(x, y), fontname, fontsize, cvScalar(0,0,0), linewidth+4, linetype); cv::putText(img, text, cvPoint(x, y), fontname, fontsize, fontcolor16, linewidth, linetype); @@ -78,20 +188,32 @@ void cvText(cv::Mat &img, const char *text, int x, int y, double fontsize, int l } } -char *getTime() +// Return the numeric time. +timeval getTimeval() { - static int seconds_last = 99; - static char TimeString[128]; timeval curTime; gettimeofday(&curTime, NULL); - if (seconds_last == curTime.tv_sec) - { - return 0; - } + return(curTime); +} - seconds_last = curTime.tv_sec; - strftime(TimeString, 80, "%Y%m%d %H:%M:%S", localtime(&curTime.tv_sec)); - return TimeString; +// Format a numeric time as a string. +char *formatTime(timeval t, char const *tf) +{ + static char TimeString[128]; + strftime(TimeString, 80, tf, localtime(&t.tv_sec)); + return(TimeString); +} + +// Return the current time as a string. Uses both functions above. +char *getTime(char const *tf) +{ + return(formatTime(getTimeval(), tf)); +} + +double timeDiff(int64 start, int64 end) +{ + double frequency = cvGetTickFrequency(); + return (double)(end - start) / frequency; // in Microseconds } std::string exec(const char *cmd) @@ -131,6 +253,14 @@ void *SaveImgThd(void *para) { pthread_mutex_lock(&mtx_SaveImg); pthread_cond_wait(&cond_SatrtSave, &mtx_SaveImg); + + if (gotSignal) + { + // we got a signal to exit, so don't save the (probably incomplete) image + pthread_mutex_unlock(&mtx_SaveImg); + break; + } + bSavingImg = true; if (pRgb.data) { @@ -143,26 +273,335 @@ void *SaveImgThd(void *para) { system("scripts/saveImageDay.sh &"); } + } else { + // This can happen if the program is closed before the first picture. + displayDebugText("----- SaveImgThd(): pRgb.data is null\n", 2); } bSavingImg = false; pthread_mutex_unlock(&mtx_SaveImg); } - printf("save thread over\n"); return (void *)0; } +char retCodeBuffer[100]; +// Display ASI errors in human-readable format +char *getRetCode(ASI_ERROR_CODE code) +{ + std::string ret; + if (code == ASI_SUCCESS) ret = "ASI_SUCCESS"; + else if (code == ASI_ERROR_INVALID_INDEX) ret = "ASI_ERROR_INVALID_INDEX"; + else if (code == ASI_ERROR_INVALID_ID) ret = "ASI_ERROR_INVALID_ID"; + else if (code == ASI_ERROR_INVALID_CONTROL_TYPE) ret = "ASI_ERROR_INVALID_CONTROL_TYPE"; + else if (code == ASI_ERROR_CAMERA_CLOSED) ret = "ASI_ERROR_CAMERA_CLOSED"; + else if (code == ASI_ERROR_CAMERA_REMOVED) ret = "ASI_ERROR_CAMERA_REMOVED"; + else if (code == ASI_ERROR_INVALID_PATH) ret = "ASI_ERROR_INVALID_PATH"; + else if (code == ASI_ERROR_INVALID_FILEFORMAT) ret = "ASI_ERROR_INVALID_FILEFORMAT"; + else if (code == ASI_ERROR_INVALID_SIZE) ret = "ASI_ERROR_INVALID_SIZE"; + else if (code == ASI_ERROR_INVALID_IMGTYPE) ret = "ASI_ERROR_INVALID_IMGTYPE"; + else if (code == ASI_ERROR_OUTOF_BOUNDARY) ret = "ASI_ERROR_OUTOF_BOUNDARY"; + else if (code == ASI_ERROR_TIMEOUT) ret = "ASI_ERROR_TIMEOUT"; + else if (code == ASI_ERROR_INVALID_SEQUENCE) ret = "ASI_ERROR_INVALID_SEQUENCE"; + else if (code == ASI_ERROR_BUFFER_TOO_SMALL) ret = "ASI_ERROR_BUFFER_TOO_SMALL"; + else if (code == ASI_ERROR_VIDEO_MODE_ACTIVE) ret = "ASI_ERROR_VIDEO_MODE_ACTIVE"; + else if (code == ASI_ERROR_EXPOSURE_IN_PROGRESS) ret = "ASI_ERROR_EXPOSURE_IN_PROGRESS"; + else if (code == ASI_ERROR_GENERAL_ERROR) ret = "ASI_ERROR_GENERAL_ERROR"; + else if (code == ASI_ERROR_END) ret = "ASI_ERROR_END"; + else if (code == -1) ret = "Non-ASI ERROR"; + else ret = "UNKNOWN ASI ERROR"; + + sprintf(retCodeBuffer, "%d (%s)", (int) code, ret.c_str()); + return(retCodeBuffer); +} + +int roundTo(int n, int roundTo) +{ + int a = (n / roundTo) * roundTo; // Smaller multiple + int b = a + roundTo; // Larger multiple + return (n - a > b - n)? b : a; // Return of closest of two +} + +int bytesPerPixel(ASI_IMG_TYPE imageType) { + switch (imageType) { + case ASI_IMG_RGB24: + return 3; + break; + case ASI_IMG_RAW16: + return 2; + break; + case ASI_IMG_RAW8: + case ASI_IMG_Y8: + default: + return 1; + } +} + +#ifdef USE_HISTOGRAM +// As of July 2021, ZWO's SDK (version 1.9) has a bug where autoexposure daylight shots' +// exposures jump all over the place. One is way too dark and the next way too light, etc. +// As a workaround, our histogram code replaces ZWO's code auto-exposure mechanism. +// We look at the mean brightness of an X by X rectangle in image, and adjust exposure based on that. + +void computeHistogram(unsigned char *imageBuffer, int width, int height, ASI_IMG_TYPE imageType, int *histogram) +{ + int h, i; + unsigned char *b = imageBuffer; + + // Clear the histogram array. + for (h = 0; h < 256; h++) { + histogram[h] = 0; + } + + // Different image types have a different number of bytes per pixel. + int bpp = bytesPerPixel(imageType); + width *= bpp; + int roiX1 = (width * histogramBoxPercentFromLeft) - (histogramBoxSizeX * bpp / 2); + int roiX2 = roiX1 + (bpp * histogramBoxSizeX); + int roiY1 = (height * histogramBoxPercentFromTop) - (histogramBoxSizeY / 2); + int roiY2 = roiY1 + histogramBoxSizeY; + + // Start off and end on a logical pixel boundries. + roiX1 = (roiX1 / bpp) * bpp; + roiX2 = (roiX2 / bpp) * bpp; + + // For RGB24, data for each pixel is stored in 3 consecutive bytes: blue, green, red. + // For all image types, each row in the image contains one row of pixels. + // bpp doesn't apply to rows, just columns. + switch (imageType) { + case ASI_IMG_RGB24: + case ASI_IMG_RAW8: + case ASI_IMG_Y8: + for (int y = roiY1; y < roiY2; y++) { + for (int x = roiX1; x < roiX2; x+=bpp) { + i = (width * y) + x; + int total = 0; + for (int z = 0; z < bpp; z++) + { + // For RGB24 this averages the blue, green, and red pixels. + total += b[i+z]; + } + int avg = total / bpp; + histogram[avg]++; + } + } + break; + case ASI_IMG_RAW16: + for (int y = roiY1; y < roiY2; y++) { + for (int x = roiX1; x < roiX2; x+=bpp) { + i = (width * y) + x; + int pixelValue; + // This assumes the image data is laid out in big endian format. + // We are going to grab the most significant byte + // and use that for the histogram value ignoring the + // least significant byte so we can use the 256 value histogram array. + // If t's acutally little endian then add a +1 to the array subscript for b[i]. + pixelValue = b[i]; + histogram[pixelValue]++; + } + } + break; + default: + sprintf(debugText, "*** ERROR: Received unspported value for ASI_IMG_TYPE: %d\n", imageType); + displayDebugText(debugText, 0); + } +} + +int calculateHistogramMean(int *histogram) { + int meanBin = 0; + int a = 0, b = 0; + for (int h = 0; h < 256; h++) { + a += (h+1) * histogram[h]; + b += histogram[h]; + } + + if (b == 0) + { + sprintf(debugText, "*** ERROR: calculateHistogramMean(): b==0\n"); + displayDebugText(debugText, 0); + return(0); + } + + meanBin = a/b - 1; + return meanBin; +} +#endif + +long actualExposureMicroseconds = 0; // actual exposure taken, per the camera +long actualGain = 0; // actual gain used, per the camera +long actualTemp = 0; // actual sensor temp, per the camera +ASI_BOOL bAuto = ASI_FALSE; // "auto" flag returned by ASIGetControlValue, when we don't care what it is + +ASI_BOOL wasAutoExposure = ASI_FALSE; +long bufferSize = NOT_SET; + +ASI_ERROR_CODE takeOneExposure( + int cameraId, + long exposureTimeMicroseconds, + unsigned char *imageBuffer, long width, long height, // where to put image and its size + ASI_IMG_TYPE imageType) +{ + if (imageBuffer == NULL) { + return (ASI_ERROR_CODE) -1; + } + + ASI_ERROR_CODE status; + // ZWO recommends timeout = (exposure*2) + 500 ms + long timeout = ((exposureTimeMicroseconds * 2) / US_IN_MS) + 500; // timeout is in ms + + sprintf(debugText, " > Exposure set to %'ld µs (%'.2f ms), timeout: %'ld ms\n", + exposureTimeMicroseconds, (float)exposureTimeMicroseconds/US_IN_MS, timeout); + displayDebugText(debugText, 2); + + setControl(cameraId, ASI_EXPOSURE, exposureTimeMicroseconds, currentAutoExposure); + + status = ASIStartVideoCapture(cameraId); + if (status == ASI_SUCCESS) { + status = ASIGetVideoData(cameraId, imageBuffer, bufferSize, timeout); + if (status != ASI_SUCCESS) { + sprintf(debugText, " > ERROR: Failed getting image, status = %s\n", getRetCode(status)); + displayDebugText(debugText, 0); + } + else { + ASIGetControlValue(cameraId, ASI_EXPOSURE, &actualExposureMicroseconds, &wasAutoExposure); + sprintf(debugText, " > Got image @ exposure: %'ld µs (%'.2f ms)\n", actualExposureMicroseconds, (float)actualExposureMicroseconds/US_IN_MS); + displayDebugText(debugText, 2); + + // If this was a manual exposure, make sure it took the correct exposure. + if (wasAutoExposure == ASI_FALSE && exposureTimeMicroseconds != actualExposureMicroseconds) + { + sprintf(debugText, " > WARNING: not correct exposure (requested: %'ld µs, actual: %'ld µs, diff: %'ld)\n", exposureTimeMicroseconds, actualExposureMicroseconds, actualExposureMicroseconds - exposureTimeMicroseconds); + displayDebugText(debugText, 0); + status = (ASI_ERROR_CODE) -1; + } + ASIGetControlValue(cameraId, ASI_GAIN, &actualGain, &bAuto); + ASIGetControlValue(cameraId, ASI_TEMPERATURE, &actualTemp, &bAuto); + } + ASIStopVideoCapture(cameraId); + } + else { + sprintf(debugText, " > ERROR: Not fetching exposure data because status is %s\n", getRetCode(status)); + displayDebugText(debugText, 0); + } + + return status; +} + +// Exit the program gracefully. +void closeUp(int e) +{ + static int closingUp = 0; // indicates if we're in the process of exiting. + // For whatever reason, we're sometimes called twice, but we should only execute once. + if (closingUp) return; + + closingUp = 1; + + ASIStopVideoCapture(CamNum); + + // Seems to hang on ASICloseCamera() if taking a picture when the signal is sent, + // until the exposure finishes, then it never returns so the remaining code doesn't + // get executed. Don't know a way around that, so don't bother closing the camera. + // Prior versions of allsky didn't do any cleanup, so it should be ok not to close the camera. + // ASICloseCamera(CamNum); + + if (bDisplay) + { + bDisplay = 0; + pthread_join(thread_display, &retval); + } + + if (bSaveRun) + { + bSaveRun = false; + pthread_mutex_lock(&mtx_SaveImg); + pthread_cond_signal(&cond_SatrtSave); + pthread_mutex_unlock(&mtx_SaveImg); + pthread_join(hthdSave, 0); + } + + // If we're not on a tty assume we were started by the service. + // Unfortunately we don't know if the service is stopping us, or restarting us. + // If it was restarting there'd be no reason to copy a notification image since it + // will soon be overwritten. Since we don't know, always copy it. + if (notificationImages) { + system("scripts/copy_notification_image.sh NotRunning &"); + // Sleep to give it a chance to print any messages so they (hopefully) get printed + // before the one below. This is only so it looks nicer in the log file. + sleep(3); + } + + printf(" ***** Stopping AllSky *****\n"); + exit(e); +} + void IntHandle(int i) { - bMain = false; + gotSignal = 1; + closeUp(0); +} + +// A user error was found. Wait for the user to fix it. +void waitToFix(char const *msg) +{ + printf("**********\n"); + printf(msg); + printf("\n"); + printf("*** After fixing, "); + if (tty) + printf("restart allsky.sh.\n"); + else + printf("restart the allsky service.\n"); + if (notificationImages) + system("scripts/copy_notification_image.sh Error &"); + sleep(5); // give time for image to be copied + printf("*** Sleeping until you fix the problem.\n"); + printf("**********\n"); + sleep(100000); // basically, sleep forever until the user fixes this. } +// Calculate if it is day or night void calculateDayOrNight(const char *latitude, const char *longitude, const char *angle) { char sunwaitCommand[128]; - sprintf(sunwaitCommand, "sunwait poll exit set angle %s %s %s", angle, latitude, longitude); + // don't need "exit" or "set". + sprintf(sunwaitCommand, "sunwait poll angle %s %s %s", angle, latitude, longitude); dayOrNight = exec(sunwaitCommand); dayOrNight.erase(std::remove(dayOrNight.begin(), dayOrNight.end(), '\n'), dayOrNight.end()); + + if (dayOrNight != "DAY" && dayOrNight != "NIGHT") + { + sprintf(debugText, "*** ERROR: dayOrNight isn't DAY or NIGHT, it's '%s'\n", dayOrNight.c_str()); + waitToFix(debugText); + closeUp(2); + } +} + +// Calculate how long until nighttime. +int calculateTimeToNightTime(const char *latitude, const char *longitude, const char *angle) +{ + std::string t; + char sunwaitCommand[128]; // returns "hh:mm, hh:mm" (sunrise, sunset) + sprintf(sunwaitCommand, "sunwait list angle %s %s %s | awk '{print $2}'", angle, latitude, longitude); + t = exec(sunwaitCommand); + t.erase(std::remove(t.begin(), t.end(), '\n'), t.end()); + + int h=0, m=0, secs; + sscanf(t.c_str(), "%d:%d", &h, &m); + secs = (h*60*60) + (m*60); + + char *now = getTime("%H:%M"); + int hNow=0, mNow=0, secsNow; + sscanf(now, "%d:%d", &hNow, &mNow); + secsNow = (hNow*60*60) + (mNow*60); + + // Handle the (probably rare) case where nighttime is tomorrow + if (secsNow > secs) + { + return(secs + (60*60*24) - secsNow); + } + else + { + return(secs - secsNow); + } } void writeToLog(int val) @@ -181,14 +620,149 @@ void writeTemperatureToFile(float val) outfile << "\n"; } -/** - * Helper function to display debug info -**/ -void displayDebugText(const char * text, int requiredLevel) { - if (debugLevel >= requiredLevel) { +// Simple function to make flags easier to read for humans. +char const *yes = "1 (yes)"; +char const *no = "0 (no)"; +char const *yesNo(int flag) +{ + if (flag) + return(yes); + else + return(no); +} - printf("%s", text); +bool adjustGain = false; // Should we adjust the gain? Set by user on command line. +bool currentAdjustGain = false; // Adjusting it right now? +int totalAdjustGain = 0; // The total amount to adjust gain. +int perImageAdjustGain = 0; // Amount of gain to adjust each image +int gainTransitionImages = 0; +int numGainChanges = 0; // This is reset at every day/night and night/day transition. + +// Reset the gain transition variables for the first transition image. +// This is called when the program first starts and at the beginning of every day/night transition. +// "dayOrNight" is the new value, e.g., if we just transitioned from day to night, it's "NIGHT". +bool resetGainTransitionVariables(int dayGain, int nightGain) +{ + // Many of the "xxx" messages below will go away once we're sure gain transition works. + sprintf(debugText, "xxx resetGainTransitionVariables(%d, %d) called at %s\n", dayGain, nightGain, dayOrNight.c_str()); + displayDebugText(debugText, 2); + + if (adjustGain == false) + { + // determineGainChange() will never be called so no need to set any variables. + sprintf(debugText,"xxx will not adjust gain - adjustGain == false\n"); + displayDebugText(debugText, 2); + return(false); + } + + if (numExposures == 0) + { + // we don't adjust when the program first starts since there's nothing to transition from + sprintf(debugText,"xxx will not adjust gain right now - numExposures == 0\n"); + displayDebugText(debugText, 2); + return(false); + } + + // Determine the amount to adjust gain per image. + // Do this once per day/night or night/day transition (i.e., numGainChanges == 0). + // First determine how long an exposure and delay is, in seconds. + // The user specifies the transition period in seconds, + // but day exposure is in microseconds, night max is in milliseconds, + // and delays are in milliseconds, so convert to seconds. + float totalTimeInSec; + if (dayOrNight == "DAY") + { + totalTimeInSec = (asiDayExposure / US_IN_SEC) + (dayDelay / MS_IN_SEC); + sprintf(debugText,"xxx totalTimeInSec=%.1fs, asiDayExposure=%'dµs , daydelay=%'dms\n", totalTimeInSec, asiDayExposure, dayDelay); + displayDebugText(debugText, 2); + } + else // NIGHT + { + // At nightime if the exposure is less than the max, we wait until max has expired, + // so use it instead of the exposure time. + totalTimeInSec = (asiNightMaxExposure / MS_IN_SEC) + (nightDelay / MS_IN_SEC); + sprintf(debugText, "xxx totalTimeInSec=%.1fs, asiNightMaxExposure=%'dms, nightDelay=%'dms\n", totalTimeInSec, asiNightMaxExposure, nightDelay); + displayDebugText(debugText, 2); + } + + gainTransitionImages = ceil(gainTransitionTime / totalTimeInSec); + if (gainTransitionImages == 0) + { + sprintf(debugText, "*** INFORMATION: Not adjusting gain - your 'gaintransitiontime' (%d seconds) is less than the time to take one image plus its delay (%.1f seconds).\n", gainTransitionTime, totalTimeInSec); + displayDebugText(debugText, 0); + + return(false); + } + + totalAdjustGain = nightGain - dayGain; + perImageAdjustGain = ceil(totalAdjustGain / gainTransitionImages); // spread evenly + if (perImageAdjustGain == 0) + perImageAdjustGain = totalAdjustGain; + else + { + // Since we can't adust gain by fractions, see if there's any "left over" after gainTransitionImages. + // For example, if totalAdjustGain is 7 and we're adjusting by 3 each of 2 times, + // we need an extra transition to get the remaining 1 ((7 - (3 * 2)) == 1). + if (gainTransitionImages * perImageAdjustGain < totalAdjustGain) + gainTransitionImages++; // this one will get the remaining amount + } + + sprintf(debugText,"xxx gainTransitionImages=%d, gainTransitionTime=%ds, perImageAdjustGain=%d, totalAdjustGain=%d\n", + gainTransitionImages, gainTransitionTime, perImageAdjustGain, totalAdjustGain); + displayDebugText(debugText, 2); + + return(true); +} + +// Determine the change in gain needed for smooth transitions between night and day. +// Gain during the day is usually 0 and at night is usually > 0. +// If auto exposure is on for both, the first several night frames may be too bright at night +// because of the sudden (often large) increase in gain, or too dark at the night-to-day +// transition. +// Try to mitigate that by changing the gain over several images at each transition. + +int determineGainChange(int dayGain, int nightGain) +{ + if (numGainChanges > gainTransitionImages || totalAdjustGain == 0) + { + // no more changes needed in this transition + sprintf(debugText, " xxxx No more gain changes needed.\n"); + displayDebugText(debugText, 2); + currentAdjustGain = false; + return(0); + } + + numGainChanges++; + int amt; // amount to adjust gain on next picture + if (dayOrNight == "DAY") + { + // During DAY, want to start out adding the full gain adjustment minus the increment on the first image, + // then DECREASE by totalAdjustGain each exposure. + // This assumes night gain is > day gain. + amt = totalAdjustGain - (perImageAdjustGain * numGainChanges); + if (amt < 0) + { + amt = 0; + totalAdjustGain = 0; // we're done making changes + } + } + else // NIGHT + { + // During NIGHT, want to start out (nightGain-perImageAdjustGain), + // then DECREASE by perImageAdjustGain each time, until we get to "nightGain". + // This last image was at dayGain and we wen't to increase each image. + amt = (perImageAdjustGain * numGainChanges) - totalAdjustGain; + if (amt > 0) + { + amt = 0; + totalAdjustGain = 0; // we're done making changes + } } + + sprintf(debugText, " xxxx Adjusting %s gain by %d on next picture to %d; will be gain change # %d of %d.\n", + dayOrNight.c_str(), amt, amt+currentGain, numGainChanges, gainTransitionImages); + displayDebugText(debugText, 2); + return(amt); } //------------------------------------------------------------------------------------------------------- @@ -197,78 +771,166 @@ void displayDebugText(const char * text, int requiredLevel) { int main(int argc, char *argv[]) { signal(SIGINT, IntHandle); + signal(SIGTERM, IntHandle); // The service sends SIGTERM to end this program. pthread_mutex_init(&mtx_SaveImg, 0); pthread_cond_init(&cond_SatrtSave, 0); - int fontname[] = { CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, - CV_FONT_HERSHEY_COMPLEX, CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, - CV_FONT_HERSHEY_SCRIPT_SIMPLEX, CV_FONT_HERSHEY_SCRIPT_COMPLEX }; - int fontnumber = 0; - int iStrLen, iTextX = 15, iTextY = 25; - int iTextLineHeight = 30; - char const *ImgText = ""; - char const *ImgExtraText = ""; - int extraFileAge = 0; - char textBuffer[1024] = { 0 }; - double fontsize = 7; - int linewidth = 1; - int outlinefont = 0; - int fontcolor[3] = { 255, 0, 0 }; - int smallFontcolor[3] = { 0, 0, 255 }; - int linetype[3] = { CV_AA, 8, 4 }; - int linenumber = 0; - - char buf[1024] = { 0 }; - char bufTime[128] = { 0 }; - char bufTemp[128] = { 0 }; - - int width = 0; - int height = 0; - int bin = 1; - int Image_type = 1; - int asiBandwidth = 40; - int asiExposure = 5000000; - int asiMaxExposure = 10000; - int asiAutoExposure = 0; - int asiGain = 150; - int asiMaxGain = 200; - int asiAutoGain = 0; - int delay = 10; // Delay in milliseconds. Default is 10ms - int daytimeDelay = 5000; // Delay in milliseconds. Default is 5000ms - int asiWBR = 65; - int asiWBB = 85; - int asiGamma = 50; - int asiBrightness = 50; - int asiFlip = 0; - int asiCoolerEnabled = 0; - long asiTargetTemp = 0; - char const *latitude = "60.7N"; //GPS Coordinates of Whitehorse, Yukon where the code was created - char const *longitude = "135.05W"; - char const *angle = "-6"; // angle of the sun with the horizon (0=sunset, -6=civil twilight, -12=nautical twilight, -18=astronomical twilight) - int preview = 0; - int Showtime = 1; - int darkframe = 0; - int showDetails = 0; - int daytimeCapture = 0; - int help = 0; - int quality = 200; - - char const *bayer[] = { "RG", "BG", "GR", "GB" }; - int CamNum = 0; + int fontname[] = { + CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, + CV_FONT_HERSHEY_COMPLEX, CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, + CV_FONT_HERSHEY_SCRIPT_SIMPLEX, CV_FONT_HERSHEY_SCRIPT_COMPLEX }; + char const *fontnames[] = { // Character representation of names for clarity: + "SIMPLEX", "PLAIN", "DUPEX", + "COMPLEX", "TRIPLEX", "COMPLEX_SMALL", + "SCRIPT_SIMPLEX", "SCRIPT_COMPLEX" }; + + char bufTime[128] = { 0 }; + char bufTemp[128] = { 0 }; + char bufTemp2[50] = { 0 }; + char const *bayer[] = { "RG", "BG", "GR", "GB" }; + bool endOfNight = false; int i; - void *retval; - bool endOfNight = false; - pthread_t hthdSave = 0; + ASI_ERROR_CODE asiRetCode; // used for return code from ASI functions. + + // Some settings have both day and night versions, some have only one version that applies to both, + // and some have either a day OR night version but not both. + // For settings with both versions we keep a "current" variable (e.g., "currentBin") that's either the day + // or night version so the code doesn't always have to check if it's day or night. + // The settings have either "day" or "night" in the name. + // In theory, almost every setting could have both day and night versions (e.g., width & height), + // but the chances of someone wanting different versions. + + // #define the defaults so we can use the same value in the help message. + +#define DEFAULT_LOCALE "en_US.UTF-8" +const char *locale = DEFAULT_LOCALE; + // All the font settings apply to both day and night. +#define DEFAULT_FONTNUMBER 0 + int fontnumber = DEFAULT_FONTNUMBER; +#define DEFAULT_ITEXTX 15 +#define DEFAULT_ITEXTY 25 + int iTextX = DEFAULT_ITEXTX; + int iTextY = DEFAULT_ITEXTY; +#define DEFAULT_ITEXTLINEHEIGHT 30 + int iTextLineHeight = DEFAULT_ITEXTLINEHEIGHT; + char const *ImgText = ""; + char const *ImgExtraText = ""; + int extraFileAge = 0; // 0 disables it + char textBuffer[1024] = { 0 }; +#define DEFAULT_FONTSIZE 7 + double fontsize = DEFAULT_FONTSIZE; +#define SMALLFONTSIZE_MULTIPLIER 0.08 +#define DEFAULT_LINEWIDTH 1 + int linewidth = DEFAULT_LINEWIDTH; +#define DEFAULT_OUTLINEFONT 0 + int outlinefont = DEFAULT_OUTLINEFONT; + int fontcolor[3] = { 255, 0, 0 }; + int smallFontcolor[3] = { 0, 0, 255 }; + int linetype[3] = { CV_AA, 8, 4 }; +#define DEFAULT_LINENUMBER 0 + int linenumber = DEFAULT_LINENUMBER; + +#define DEFAULT_WIDTH 0 +#define DEFAULT_HEIGHT 0 + int width = DEFAULT_WIDTH; int originalWidth = width; + int height = DEFAULT_HEIGHT; int originalHeight = height; + +#define DEFAULT_DAYBIN 1 // binning during the day probably isn't too useful... +#define DEFAULT_NIGHTBIN 1 + int dayBin = DEFAULT_DAYBIN; + int nightBin = DEFAULT_NIGHTBIN; + int currentBin = NOT_SET; + +#define DEFAULT_IMAGE_TYPE 1 +#define AUTO_IMAGE_TYPE 99 // needs to match what's in the camera_settings.json file + int Image_type = DEFAULT_IMAGE_TYPE; + +#define DEFAULT_ASIBANDWIDTH 40 + int asiBandwidth = DEFAULT_ASIBANDWIDTH; + int asiAutoBandwidth = 0; // is Auto Bandwidth on or off? + + // There is no max day autoexposure since daylight exposures are always pretty short. +#define DEFAULT_ASINIGHTEXPOSURE (5 * US_IN_SEC) // 5 seconds + long asiNightExposure = DEFAULT_ASINIGHTEXPOSURE; + long currentExposure = NOT_SET; +#define DEFAULT_NIGHTAUTOEXPOSURE 1 + int asiNightAutoExposure = DEFAULT_NIGHTAUTOEXPOSURE; // is it on or off for nighttime? + // currentAutoExposure is global so is defined outside of main() + +#define DEFAULT_ASIDAYGHTGAIN 0 + int asiDayGain = DEFAULT_ASIDAYGHTGAIN; + int asiDayAutoGain = 0; // is Auto Gain on or off for daytime? +#define DEFAULT_ASINIGHTGAIN 150 + int asiNightGain = DEFAULT_ASINIGHTGAIN; +#define DEFAULT_NIGHTAUTOGAIN 0 + int asiNightAutoGain = DEFAULT_NIGHTAUTOGAIN; // is Auto Gain on or off for nighttime? +#define DEFAULT_ASINIGHTMAXGAIN 200 + int asiNightMaxGain = DEFAULT_ASINIGHTMAXGAIN; + ASI_BOOL currentAutoGain = ASI_FALSE; + + int currentDelay = NOT_SET; + +#define DEFAULT_ASIWBR 65 + int asiWBR = DEFAULT_ASIWBR; +#define DEFAULT_ASIWBB 85 + int asiWBB = DEFAULT_ASIWBB; +#define DEFAULT_AUTOWHITEBALANCE 0 + int asiAutoWhiteBalance = DEFAULT_AUTOWHITEBALANCE; // is Auto White Balance on or off? + +#define DEFAULT_ASIGAMMA 50 // not supported by all cameras + int asiGamma = DEFAULT_ASIGAMMA; + +#define DEFAULT_BRIGHTNESS 50 + int asiDayBrightness = DEFAULT_BRIGHTNESS; +#define MAX_BRIGHTNESS 600 + int asiNightBrightness = DEFAULT_BRIGHTNESS; + int currentBrightness = NOT_SET; + +#define DEFAULT_LATITUDE "60.7N" //GPS Coordinates of Whitehorse, Yukon where the code was created + char const *latitude = DEFAULT_LATITUDE; +#define DEFAULT_LONGITUDE "135.05W" + char const *longitude = DEFAULT_LONGITUDE; +#define DEFAULT_ANGLE "-6" + // angle of the sun with the horizon + // (0=sunset, -6=civil twilight, -12=nautical twilight, -18=astronomical twilight) + char const *angle = DEFAULT_ANGLE; + + int preview = 0; +#define DEFAULT_SHOWTIME 1 + int showTime = DEFAULT_SHOWTIME; + int darkframe = 0; + char const *tempType = "C"; // Celsius + + int showDetails = 0; + // Allow for more granularity than showDetails, which shows everything: + int showTemp = 0; + int showExposure = 0; + int showGain = 0; + int showBrightness = 0; +#ifdef USE_HISTOGRAM + int showHistogram = 0; + int maxHistogramAttempts = 15; // max number of times we'll try for a better histogram mean + int showHistogramBox = 0; +#endif +#define DEFAULT_DAYTIMECAPTURE 0 + int daytimeCapture = DEFAULT_DAYTIMECAPTURE; // are we capturing daytime pictures? + + int help = 0; + int quality = NOT_SET; + int asiFlip = 0; + int asiCoolerEnabled = 0; + long asiTargetTemp = 0; //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- + setlinebuf(stdout); // Line buffer output so entries appear in the log immediately. printf("\n"); printf("%s ******************************************\n", KGRN); - printf("%s *** Allsky Camera Software v0.7 | 2020 ***\n", KGRN); + printf("%s *** Allsky Camera Software v0.8 | 2021 ***\n", KGRN); printf("%s ******************************************\n\n", KGRN); printf("\%sCapture images of the sky with a Raspberry Pi and an ASI Camera\n", KGRN); printf("\n"); - printf("%sAdd -h or -help for available options \n", KYEL); + printf("%sAdd -h or -help for available options\n", KYEL); printf("\n"); printf("\%sAuthor: ", KNRM); printf("Thomas Jacquin - \n\n"); @@ -280,363 +942,517 @@ int main(int argc, char *argv[]) printf("-Michael J. Kidd - \n"); printf("-Chris Kuethe\n\n"); + // The newer "allsky.sh" puts quotes around arguments so we can have spaces in them. + // If you are running the old allsky.sh, set this to false: + bool argumentsQuoted = true; + if (argc > 0) { - for (i = 0; i < argc - 1; i++) + // -h[elp] doesn't take an argument, but the "for" loop assumes every option does, + // so check separately, assuming the option is the first one. + // If it's not the first option, we'll find it in the "for" loop. + if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "-help") == 0) + { + help = 1; + i = 1; + } + else { + i = 0; + } + + // Many of the argument names changed to allow day and night values. + // However, still check for the old names in case the user didn't update their + // settings.json file. The old names should be removed below in a future version. + for ( ; i < argc - 1 ; i++) + { + // Check again in case "-h" isn't the first option. if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) { - help = atoi(argv[i + 1]); - i++; + help = 1; + } + else if (strcmp(argv[i], "-locale") == 0) + { + locale = argv[++i]; + } + else if (strcmp(argv[i], "-tty") == 0) + { + tty = atoi(argv[++i]); } else if (strcmp(argv[i], "-width") == 0) { - width = atoi(argv[i + 1]); - i++; + width = atoi(argv[++i]); } else if (strcmp(argv[i], "-height") == 0) { - height = atoi(argv[i + 1]); - i++; + height = atoi(argv[++i]); } else if (strcmp(argv[i], "-type") == 0) { - Image_type = atoi(argv[i + 1]); - i++; + Image_type = atoi(argv[++i]); } else if (strcmp(argv[i], "-quality") == 0) { - quality = atoi(argv[i + 1]); - i++; + quality = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-nightexposure") == 0 || strcmp(argv[i], "-exposure") == 0) + { + asiNightExposure = atoi(argv[++i]) * US_IN_MS; + } + else if (strcmp(argv[i], "-dayexposure") == 0) + { + asiDayExposure = atof(argv[++i]) * US_IN_MS; // allow fractions } - else if (strcmp(argv[i], "-exposure") == 0) + else if (strcmp(argv[i], "-nightmaxexposure") == 0 || strcmp(argv[i], "-maxexposure") == 0) { - asiExposure = atoi(argv[i + 1]) * 1000; - i++; + asiNightMaxExposure = atoi(argv[++i]); } - else if (strcmp(argv[i], "-maxexposure") == 0) + else if (strcmp(argv[i], "-dayautoexposure") == 0) { - asiMaxExposure = atoi(argv[i + 1]); - i++; + asiDayAutoExposure = atoi(argv[++i]); } - else if (strcmp(argv[i], "-autoexposure") == 0) + else if (strcmp(argv[i], "-nightautoexposure") == 0 || strcmp(argv[i], "-autoexposure") == 0) { - asiAutoExposure = atoi(argv[i + 1]); - i++; + asiNightAutoExposure = atoi(argv[++i]); } - else if (strcmp(argv[i], "-gain") == 0) + else if (strcmp(argv[i], "-nightgain") == 0 || strcmp(argv[i], "-gain") == 0) { - asiGain = atoi(argv[i + 1]); - i++; + asiNightGain = atoi(argv[++i]); } - else if (strcmp(argv[i], "-maxgain") == 0) + else if (strcmp(argv[i], "-nightmaxgain") == 0 || strcmp(argv[i], "-maxgain") == 0) { - asiMaxGain = atoi(argv[i + 1]); - i++; + asiNightMaxGain = atoi(argv[++i]); } - else if (strcmp(argv[i], "-autogain") == 0) + else if (strcmp(argv[i], "-nightautogain") == 0 || strcmp(argv[i], "-autogain") == 0) { - asiAutoGain = atoi(argv[i + 1]); - i++; + asiNightAutoGain = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-gaintransitiontime") == 0) + { + // user specifies minutes but we want seconds. + gainTransitionTime = atoi(argv[++i]) * 60; } else if (strcmp(argv[i], "-gamma") == 0) { - asiGamma = atoi(argv[i + 1]); - i++; + asiGamma = atoi(argv[++i]); } + // old "-brightness" applied to day and night else if (strcmp(argv[i], "-brightness") == 0) { - asiBrightness = atoi(argv[i + 1]); - i++; + asiDayBrightness = atoi(argv[++i]); + asiNightBrightness = asiDayBrightness; + } + else if (strcmp(argv[i], "-daybrightness") == 0) + { + asiDayBrightness = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-nightbrightness") == 0) + { + asiNightBrightness = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-daybin") == 0) + { + dayBin = atoi(argv[++i]); } - else if (strcmp(argv[i], "-bin") == 0) + else if (strcmp(argv[i], "-nightbin") == 0 || strcmp(argv[i], "-bin") == 0) { - bin = atoi(argv[i + 1]); - i++; + nightBin = atoi(argv[++i]); } - else if (strcmp(argv[i], "-delay") == 0) + else if (strcmp(argv[i], "-daydelay") == 0 || strcmp(argv[i], "-daytimeDelay") == 0) { - delay = atoi(argv[i + 1]); - i++; + dayDelay = atoi(argv[++i]); } - else if (strcmp(argv[i], "-daytimeDelay") == 0) + else if (strcmp(argv[i], "-nightdelay") == 0 || strcmp(argv[i], "-delay") == 0) { - daytimeDelay = atoi(argv[i + 1]); - i++; + nightDelay = atoi(argv[++i]); } else if (strcmp(argv[i], "-wbr") == 0) { - asiWBR = atoi(argv[i + 1]); - i++; + asiWBR = atoi(argv[++i]); } else if (strcmp(argv[i], "-wbb") == 0) { - asiWBB = atoi(argv[i + 1]); - i++; + asiWBB = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-autowhitebalance") == 0) + { + asiAutoWhiteBalance = atoi(argv[++i]); } else if (strcmp(argv[i], "-text") == 0) { - // Fix for blank text in camera settings - if ((char)argv[i + 1][0] != '-') { - ImgText = (argv[i + 1]); - i++; + if (argumentsQuoted) + { + ImgText = argv[++i]; + } + else + { + // In case the text is null and isn't quoted, check if the next argument + // starts with a "-". If so, the text is null, otherwise it's the text. + if ((char)argv[i + 1][0] != '-') { + ImgText = argv[++i]; + } } } else if (strcmp(argv[i], "-extratext") == 0) { - if ((char)argv[i + 1][0] != '-') { - ImgExtraText = (argv[i + 1]); - i++; + if (argumentsQuoted) + { + ImgExtraText = argv[++i]; + } + else + { + // In case the text is null and isn't quoted, check if the next argument + // starts with a "-". If so, the text is null, otherwise it's the text. + if ((char)argv[i + 1][0] != '-') { + ImgExtraText = argv[++i]; + } } - } - else if (strcmp(argv[i], "-textlineheight") == 0) - { - iTextLineHeight = atoi(argv[i + 1]); - i++; } else if (strcmp(argv[i], "-extratextage") == 0) { - extraFileAge = atoi(argv[i + 1]); - i++; + extraFileAge = atoi(argv[++i]); } - else if (strcmp(argv[i], "-debuglevel") == 0) + else if (strcmp(argv[i], "-textlineheight") == 0) { - debugLevel = atoi(argv[i + 1]); - i++; + iTextLineHeight = atoi(argv[++i]); } else if (strcmp(argv[i], "-textx") == 0) { - iTextX = atoi(argv[i + 1]); - i++; + iTextX = atoi(argv[++i]); } else if (strcmp(argv[i], "-texty") == 0) { - iTextY = atoi(argv[i + 1]); - i++; + iTextY = atoi(argv[++i]); } else if (strcmp(argv[i], "-fontname") == 0) { - fontnumber = atoi(argv[i + 1]); - i++; + fontnumber = atoi(argv[++i]); } else if (strcmp(argv[i], "-fontcolor") == 0) { - fontcolor[0] = atoi(argv[i + 1]); - i++; - fontcolor[1] = atoi(argv[i + 1]); - i++; - fontcolor[2] = atoi(argv[i + 1]); - i++; + if (argumentsQuoted) + { + sscanf(argv[++i], "%d %d %d", &fontcolor[0], &fontcolor[1], &fontcolor[2]); + } + else + { + fontcolor[0] = atoi(argv[++i]); + fontcolor[1] = atoi(argv[++i]); + fontcolor[2] = atoi(argv[++i]); + } } else if (strcmp(argv[i], "-smallfontcolor") == 0) { - smallFontcolor[0] = atoi(argv[i + 1]); - i++; - smallFontcolor[1] = atoi(argv[i + 1]); - i++; - smallFontcolor[2] = atoi(argv[i + 1]); - i++; + if (argumentsQuoted) + { + sscanf(argv[++i], "%d %d %d", &smallFontcolor[0], &smallFontcolor[1], &smallFontcolor[2]); + } + else + { + smallFontcolor[0] = atoi(argv[++i]); + smallFontcolor[1] = atoi(argv[++i]); + smallFontcolor[2] = atoi(argv[++i]); + } } else if (strcmp(argv[i], "-fonttype") == 0) { - linenumber = atoi(argv[i + 1]); - i++; + linenumber = atoi(argv[++i]); } else if (strcmp(argv[i], "-fontsize") == 0) { - fontsize = atof(argv[i + 1]); - i++; + fontsize = atof(argv[++i]); } else if (strcmp(argv[i], "-fontline") == 0) { - linewidth = atoi(argv[i + 1]); - i++; + linewidth = atoi(argv[++i]); } else if (strcmp(argv[i], "-outlinefont") == 0) { - outlinefont = atoi(argv[i + 1]); - if (outlinefont != 0) - outlinefont = 1; - i++; + outlinefont = atoi(argv[++i]); + if (outlinefont != 0) + outlinefont = 1; } else if (strcmp(argv[i], "-flip") == 0) { - asiFlip = atoi(argv[i + 1]); - i++; + asiFlip = atoi(argv[++i]); } else if (strcmp(argv[i], "-usb") == 0) { - asiBandwidth = atoi(argv[i + 1]); - i++; + asiBandwidth = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-autousb") == 0) + { + asiAutoBandwidth = atoi(argv[++i]); } else if (strcmp(argv[i], "-filename") == 0) { - fileName = (argv[i + 1]); - i++; + fileName = argv[++i]; } else if (strcmp(argv[i], "-latitude") == 0) { - latitude = argv[i + 1]; - i++; + latitude = argv[++i]; } else if (strcmp(argv[i], "-longitude") == 0) { - longitude = argv[i + 1]; - i++; + longitude = argv[++i]; } else if (strcmp(argv[i], "-angle") == 0) { - angle = argv[i + 1]; - i++; + angle = argv[++i]; + } + else if (strcmp(argv[i], "-notificationimages") == 0) + { + notificationImages = atoi(argv[++i]); } +#ifdef USE_HISTOGRAM + else if (strcmp(argv[i], "-histogrambox") == 0) + { + if (argumentsQuoted) + { + sscanf(argv[++i], "%d %d %f %f", &histogramBoxSizeX, &histogramBoxSizeY, &histogramBoxPercentFromLeft, &histogramBoxPercentFromTop); + histogramBoxPercentFromLeft /= 100; // user enters 0-100 + histogramBoxPercentFromTop /= 100; + } + else + { + histogramBoxSizeX = atoi(argv[++i]); + histogramBoxSizeY = atoi(argv[++i]); + histogramBoxPercentFromLeft = (float)atoi(argv[++i]) / 100; // user enters 0-100 + histogramBoxPercentFromTop = (float)atoi(argv[++i]) / 100; // user enters 0-100 + } + } + else if (strcmp(argv[i], "-showhistogrambox") == 0) + { + showHistogramBox = atoi(argv[++i]); + } +#endif else if (strcmp(argv[i], "-preview") == 0) { - preview = atoi(argv[i + 1]); - i++; + preview = atoi(argv[++i]); } - else if (strcmp(argv[i], "-time") == 0) + else if (strcmp(argv[i], "-debuglevel") == 0) { - Showtime = atoi(argv[i + 1]); - i++; + debugLevel = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-showTime") == 0 || strcmp(argv[i], "-time") == 0) + { + showTime = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-timeformat") == 0) + { + timeFormat = argv[++i]; } else if (strcmp(argv[i], "-darkframe") == 0) { - darkframe = atoi(argv[i + 1]); - i++; + darkframe = atoi(argv[++i]); } else if (strcmp(argv[i], "-showDetails") == 0) { - showDetails = atoi(argv[i + 1]); - i++; + showDetails = atoi(argv[++i]); + // showDetails is an obsolete variable that shows ALL details except time. + // It's been replaced by separate variables for various lines. + showTemp = showDetails; + showExposure = showDetails; + showGain = showDetails; + } + else if (strcmp(argv[i], "-showTemp") == 0) + { + showTemp = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-temptype") == 0) + { + tempType = argv[++i]; } + else if (strcmp(argv[i], "-showExposure") == 0) + { + showExposure = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-showGain") == 0) + { + showGain = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-showBrightness") == 0) + { + showBrightness = atoi(argv[++i]); + } +#ifdef USE_HISTOGRAM + else if (strcmp(argv[i], "-showHistogram") == 0) + { + showHistogram = atoi(argv[++i]); + } +#endif else if (strcmp(argv[i], "-daytime") == 0) { - daytimeCapture = atoi(argv[i + 1]); - i++; + daytimeCapture = atoi(argv[++i]); } else if (strcmp(argv[i], "-coolerEnabled") == 0) { - asiCoolerEnabled = atoi(argv[i + 1]); - i++; + asiCoolerEnabled = atoi(argv[++i]); } else if (strcmp(argv[i], "-targetTemp") == 0) { - asiTargetTemp = atol(argv[i + 1]); - i++; + asiTargetTemp = atol(argv[++i]); } } } if (help == 1) { - printf("%sAvailable Arguments: \n", KYEL); - printf(" -width - Default = Camera Max Width \n"); - printf(" -height - Default = Camera Max Height \n"); - printf(" -exposure - Default = 5000000 - Time in µs (equals to 5 sec) \n"); - printf(" -maxexposure - Default = 10000 - Time in ms (equals to 10 sec) \n"); - printf(" -autoexposure - Default = 0 - Set to 1 to enable auto Exposure \n"); - printf(" -gain - Default = 50 \n"); - printf(" -maxgain - Default = 200 \n"); - printf(" -autogain - Default = 0 - Set to 1 to enable auto Gain \n"); - printf(" -coolerEnabled - Set to 1 to enable cooler (works on cooled cameras only) \n"); - printf(" -targetTemp - Target temperature in degrees C (works on cooled cameras only) \n"); - printf(" -gamma - Default = 50 \n"); - printf(" -brightness - Default = 50 \n"); - printf(" -wbr - Default = 50 - White Balance Red \n"); - printf(" -wbb - Default = 50 - White Balance Blue \n"); - printf(" -bin - Default = 1 - 1 = binning OFF (1x1), 2 = 2x2 binning, 4 = 4x4 " - "binning\n"); - printf(" -delay - Default = 10 - Delay between images in milliseconds - 1000 = 1 " - "sec.\n"); - printf(" -daytimeDelay - Default = 5000 - Delay between images in milliseconds - 5000 = " - "5 sec.\n"); - printf(" -type = Image Type - Default = 0 - 0 = RAW8, 1 = RGB24, 2 = RAW16 \n"); + printf("%sAvailable Arguments:\n", KYEL); + printf(" -width - Default = %d = Camera Max Width\n", DEFAULT_WIDTH); + printf(" -height - Default = %d = Camera Max Height\n", DEFAULT_HEIGHT); + printf(" -daytime - Default = %d - Set to 1 to enable daytime images\n", DEFAULT_DAYTIMECAPTURE); + printf(" -dayexposure - Default = %'d - Time in µs (equals to %.4f sec)\n", DEFAULT_ASIDAYEXPOSURE, (float)DEFAULT_ASIDAYEXPOSURE/US_IN_SEC); + printf(" -nightexposure - Default = %'d - Time in µs (equals to %.4f sec)\n", DEFAULT_ASINIGHTEXPOSURE, (float)DEFAULT_ASINIGHTEXPOSURE/US_IN_SEC); + printf(" -nightmaxexposure - Default = %'d - Time in ms (equals to %.1f sec)\n", DEFAULT_ASINIGHTMAXEXPOSURE, (float)DEFAULT_ASINIGHTMAXEXPOSURE/US_IN_MS); + + printf(" -dayautoexposure - Default = %d - Set to 1 to enable daytime auto Exposure\n", DEFAULT_DAYAUTOEXPOSURE); + printf(" -nightautoexposure - Default = %d - Set to 1 to enable nighttime auto Exposure\n", DEFAULT_NIGHTAUTOEXPOSURE); + printf(" -nightgain - Default = %d\n", DEFAULT_ASINIGHTGAIN); + printf(" -nightmaxgain - Default = %d\n", DEFAULT_ASINIGHTMAXGAIN); + printf(" -nightautogain - Default = %d - Set to 1 to enable nighttime auto gain\n", DEFAULT_NIGHTAUTOGAIN); + printf(" -gaintransitiontime - Default = %'d - Seconds to transition gain from day-to-night or night-to-day. Set to 0 to disable\n", DEFAULT_GAIN_TRANSITION_TIME); + printf(" -coolerEnabled - Set to 1 to enable cooler (works on cooled cameras only)\n"); + printf(" -targetTemp - Target temperature in degrees C (works on cooled cameras only)\n"); + printf(" -gamma - Default = %d\n", DEFAULT_ASIGAMMA); + printf(" -daybrightness - Default = %d (range: 0 - 600)\n", DEFAULT_BRIGHTNESS); + printf(" -nightbrightness - Default = %d (range: 0 - 600)\n", DEFAULT_BRIGHTNESS); + printf(" -wbr - Default = %d - manual White Balance Red\n", DEFAULT_ASIWBR); + printf(" -wbb - Default = %d - manual White Balance Blue\n", DEFAULT_ASIWBB); + printf(" -autowhitebalance - Default = %d - Set to 1 to enable auto White Balance\n", DEFAULT_AUTOWHITEBALANCE); + printf(" -daybin - Default = %d - 1 = binning OFF (1x1), 2 = 2x2 binning, 4 = 4x4 binning\n", DEFAULT_DAYBIN); + printf(" -nightbin - Default = %d - 1 = binning OFF (1x1), 2 = 2x2 binning, 4 = 4x4 binning\n", DEFAULT_NIGHTBIN); + printf(" -dayDelay - Default = %'d - Delay between daytime images in milliseconds - 5000 = 5 sec.\n", DEFAULT_DAYDELAY); + printf(" -nightDelay - Default = %'d - Delay between night images in milliseconds - %d = 1 sec.\n", DEFAULT_NIGHTDELAY, MS_IN_SEC); + printf(" -type = Image Type - Default = %d - 0 = RAW8, 1 = RGB24, 2 = RAW16, 3 = Y8\n", DEFAULT_IMAGE_TYPE); printf(" -quality - Default PNG=3, JPG=95, Values: PNG=0-9, JPG=0-100\n"); - printf(" -usb = USB Speed - Default = 40 - Values between 40-100, This is " - "BandwidthOverload \n"); - printf(" -filename - Default = IMAGE.PNG \n"); + printf(" -usb = USB Speed - Default = %d - Values between 40-100, This is " + "BandwidthOverload\n", DEFAULT_ASIBANDWIDTH); + printf(" -autousb - Default = 0 - Set to 1 to enable auto USB Speed\n"); + printf(" -filename - Default = %s\n", DEFAULT_FILENAME); printf(" -flip - Default = 0 - 0 = Orig, 1 = Horiz, 2 = Verti, 3 = Both\n"); printf("\n"); - printf(" -text - Default = - Character/Text Overlay. Use Quotes. Ex. -c " - "\"Text Overlay\"\n"); - printf(" -extratext - Default = - Full Path to extra text to display\n"); - printf(" -extratextage - Default = 600 - If the extra file is not updated after this many seconds its contents will not be displayed. set to 0 to disable\n"); - printf(" -textlineheight - Default = 30 - Text Line Height in Pixels\n"); - printf( - " -textx - Default = 15 - Text Placement Horizontal from LEFT in Pixels\n"); - printf(" -texty = Text Y - Default = 25 - Text Placement Vertical from TOP in Pixels\n"); - printf(" -fontname = Font Name - Default = 0 - Font Types (0-7), Ex. 0 = simplex, 4 = triplex, " - "7 = script\n"); + printf(" -text - Default = \"\" - Character/Text Overlay\n"); + printf(" -extratext - Default = \"\" - Full Path to extra text to display\n"); + printf(" -extratextage - Default = 0 - If the extra file is not updated after this many seconds its contents will not be displayed. Set to 0 to disable\n"); + printf(" -textlineheight - Default = %d - Text Line Height in Pixels\n", DEFAULT_ITEXTLINEHEIGHT); + printf(" -textx = Text X - Default = %d - Text Placement Horizontal from LEFT in pixels\n", DEFAULT_ITEXTX); + printf(" -texty = Text Y - Default = %d - Text Placement Vertical from TOP in pixels\n", DEFAULT_ITEXTY); + printf(" -fontname = Font Name - Default = %d - Font Types (0-7), Ex. 0 = simplex, 4 = triplex, 7 = script\n", DEFAULT_FONTNUMBER); printf(" -fontcolor = Font Color - Default = 255 0 0 - Text blue (BGR)\n"); printf(" -smallfontcolor = Small Font Color - Default = 0 0 255 - Text red (BGR)\n"); - printf(" -fonttype = Font Type - Default = 0 - Font Line Type,(0-2), 0 = AA, 1 = 8, 2 = 4\n"); - printf(" -fontsize - Default = 7 - Text Font Size\n"); - printf(" -fontline - Default = 1 - Text Font Line Thickness\n"); + printf(" -fonttype = Font Type - Default = %d - Font Line Type,(0-2), 0 = AA, 1 = 8, 2 = 4\n", DEFAULT_LINENUMBER); + printf(" -fontsize - Default = %d - Text Font Size\n", DEFAULT_FONTSIZE); + printf(" -fontline - Default = %d - Text Font Line Thickness\n", DEFAULT_LINEWIDTH); + printf(" -outlinefont - Default = %d - TSet to 1 to enable outline font\n", DEFAULT_OUTLINEFONT); //printf(" -bgc = BG Color - Default = - Text Background Color in Hex. 00ff00 = Green\n"); //printf(" -bga = BG Alpha - Default = - Text Background Color Alpha/Transparency 0-100\n"); printf("\n"); printf("\n"); - printf(" -latitude - Default = 60.7N (Whitehorse) - Latitude of the camera.\n"); - printf(" -longitude - Default = 135.05W (Whitehorse) - Longitude of the camera\n"); - printf(" -angle - Default = -6 - Angle of the sun below the horizon. -6=civil " - "twilight, -12=nautical twilight, -18=astronomical twilight\n"); + printf(" -latitude - Default = %7s (Whitehorse) - Latitude of the camera.\n", DEFAULT_LATITUDE); + printf(" -longitude - Default = %7s (Whitehorse) - Longitude of the camera\n", DEFAULT_LONGITUDE); + printf(" -angle - Default = %s - Angle of the sun below the horizon.\n", DEFAULT_ANGLE); + printf(" -6=civil twilight\n -12=nautical twilight\n -18=astronomical twilight\n"); printf("\n"); - printf(" -preview - set to 1 to preview the captured images. Only works with a " - "Desktop Environment \n"); - printf(" -time - Adds the time to the image. Combine with Text X and Text Y for " - "placement \n"); - printf(" -darkframe - Set to 1 to disable time and text overlay \n"); - printf(" -showDetails - Set to 1 to display the metadata on the image \n"); - printf(" -debuglevel - Default = 0 Set to 1,2 or 3 for dbug level \n"); + printf(" -locale - Default = %s - Your locale, used to determine your thousands separator and decimal point. If you don't know it, type 'locale' at a command prompt.\n", DEFAULT_LOCALE); + printf(" -notificationimages - Set to 1 to enable notification images, for example, 'Camera is off during day'.\n"); +#ifdef USE_HISTOGRAM + printf(" -histogrambox - Default = %d %d %0.2f %0.2f (box width X, box width y, X offset percent (0-100), Y offset (0-100)\n", DEFAULT_BOX_SIZEX, DEFAULT_BOX_SIZEY, DEFAULT_BOX_FROM_LEFT * 100, DEFAULT_BOX_FROM_TOP * 100); + printf(" -showhistogrambox - Set to 1 to view an outline of the histogram box. Useful to help determine what parameters to use with -histogrambox.\n"); +#endif + printf(" -darkframe - Set to 1 to disable time and text overlay and take dark frames instead.\n"); + printf(" -preview - Set to 1 to preview the captured images. Only works with a Desktop Environment\n"); + printf(" -time - Set to 1 to add the time to the image. Combine with Text X and Text Y for placement\n"); + printf(" -timeformat - Format the optional time is displayed in; default is '%s'\n", DEFAULT_TIMEFORMAT); + printf(" -showDetails (obsolete) - Set to 1 to display sensor temp, exposure length, and gain metadata on the image.\n"); + printf(" -showTemp - Set to 1 to display the camera sensor temperature on the image.\n"); + printf(" -temptype - How to display temperature: 'C'elsius, 'F'ahrenheit, or 'B'oth.\n"); + printf(" -showExposure - Set to 1 to display the exposure length on the image.\n"); + printf(" -showGain - Set to 1 to display the gain on the image.\n"); + printf(" -showBrightness - Set to 1 to display the brightness on the image, if not the default.\n"); +#ifdef USE_HISTOGRAM + printf(" -showHistogram - Set to 1 to display the histogram mean on the image.\n"); +#endif + printf(" -debuglevel - Default = 0. Set to 1,2 or 3 for more debugging information.\n"); printf("%sUsage:\n", KRED); - printf(" ./capture -width 640 -height 480 -exposure 5000000 -gamma 50 -type 1 -bin 1 -filename " - "Lake-Laberge.PNG\n\n"); + printf(" ./capture -width 640 -height 480 -nightexposure 5000000 -gamma 50 -type 1 -nightbin 1 -filename Lake-Laberge.PNG\n\n"); } - printf("%s", KNRM); + printf("%s\n", KNRM); + setlocale(LC_NUMERIC, locale); + const char *imagetype = ""; const char *ext = strrchr(fileName, '.'); - if (strcmp(ext + 1, "jpg") == 0 || strcmp(ext + 1, "JPG") == 0 || strcmp(ext + 1, "jpeg") == 0 || - strcmp(ext + 1, "JPEG") == 0) + if (strcasecmp(ext + 1, "jpg") == 0 || strcasecmp(ext + 1, "jpeg") == 0) { + if (Image_type == ASI_IMG_RAW16) + { + waitToFix("*** ERROR: RAW16 images only work with .png files; either change the Image Type or the Filename.\n"); + exit(99); + } + + imagetype = "jpg"; compression_parameters.push_back(CV_IMWRITE_JPEG_QUALITY); - if (quality == 200) + if (quality == NOT_SET) { quality = 95; - } + } else if (quality > 100) + { + quality = 100; + } } - else + else if (strcasecmp(ext + 1, "png") == 0) { + imagetype = "png"; compression_parameters.push_back(CV_IMWRITE_PNG_COMPRESSION); - if (quality == 200) + if (quality == NOT_SET) { quality = 3; - } + } else if (quality > 9) + { + quality = 9; + } + } + else + { + sprintf(textBuffer, "*** ERROR: Unsupported image extension (%s); only .jpg and .png are supported.\n", ext); + waitToFix(textBuffer); + exit(99); } compression_parameters.push_back(quality); - int numDevices = ASIGetNumOfConnectedCameras(); - if (numDevices <= 0) + if (darkframe) { - printf("\nNo Connected Camera...\n"); - width = 1; //Set to 1 when NO Cameras are connected to avoid error: OpenCV Error: Insufficient memory - height = 1; //Set to 1 when NO Cameras are connected to avoid error: OpenCV Error: Insufficient memory + // To avoid overwriting the optional notification inage with the dark image, + // during dark frames we use a different file name. + static char darkFilename[200]; + sprintf(darkFilename, "dark.%s", imagetype); + fileName = darkFilename; } - else + + int numDevices = ASIGetNumOfConnectedCameras(); + if (numDevices <= 0) { - printf("\nListing Attached Cameras:\n"); + printf("*** ERROR: No Connected Camera...\n"); + // Don't wait here since it's possible the camera is physically connected + // but the software doesn't see it and the USB bus needs to be reset. + closeUp(1); // If there are no cameras we can't do anything. } + printf("\nListing Attached Cameras%s:\n", numDevices == 1 ? "" : " (using first one)"); + ASI_CAMERA_INFO ASICameraInfo; for (i = 0; i < numDevices; i++) { ASIGetCameraProperty(&ASICameraInfo, i); - printf("- %d %s\n", i, ASICameraInfo.Name); + printf(" - %d %s\n", i, ASICameraInfo.Name); } - if (ASIOpenCamera(CamNum) != ASI_SUCCESS) + asiRetCode = ASIOpenCamera(CamNum); + if (asiRetCode != ASI_SUCCESS) { - printf("Open Camera ERROR, Check that you have root permissions!\n"); + printf("*** ERROR opening camera, check that you have root permissions! (%s)\n", getRetCode(asiRetCode)); + closeUp(1); // Can't do anything so might as well exit. } printf("\n%s Information:\n", ASICameraInfo.Name); @@ -645,9 +1461,9 @@ int main(int argc, char *argv[]) iMaxWidth = ASICameraInfo.MaxWidth; iMaxHeight = ASICameraInfo.MaxHeight; pixelSize = ASICameraInfo.PixelSize; - printf("- Resolution:%dx%d\n", iMaxWidth, iMaxHeight); - printf("- Pixel Size: %1.1fμm\n", pixelSize); - printf("- Supported Bin: "); + printf(" - Resolution:%dx%d\n", iMaxWidth, iMaxHeight); + printf(" - Pixel Size: %1.1fμm\n", pixelSize); + printf(" - Supported Bin: "); for (int i = 0; i < 16; ++i) { if (ASICameraInfo.SupportedBins[i] == 0) @@ -660,165 +1476,197 @@ int main(int argc, char *argv[]) if (ASICameraInfo.IsColorCam) { - printf("- Color Camera: bayer pattern:%s\n", bayer[ASICameraInfo.BayerPattern]); + printf(" - Color Camera: bayer pattern:%s\n", bayer[ASICameraInfo.BayerPattern]); } else { - printf("- Mono camera\n"); + printf(" - Mono camera\n"); } if (ASICameraInfo.IsCoolerCam) { - printf("- Camera with cooling capabilities\n"); + printf(" - Camera with cooling capabilities\n"); } const char *ver = ASIGetSDKVersion(); - printf("- SDK version %s\n", ver); + printf(" - SDK version %s\n", ver); - if (ASIInitCamera(CamNum) == ASI_SUCCESS) + asiRetCode = ASIInitCamera(CamNum); + if (asiRetCode == ASI_SUCCESS) { - printf("- Initialise Camera OK\n"); + printf(" - Initialise Camera OK\n"); } else { - printf("- Initialise Camera ERROR\n"); + printf("*** ERROR: Unable to initialise camera: %s\n", getRetCode(asiRetCode)); + closeUp(1); // Can't do anything so might as well exit. } - ASI_CONTROL_CAPS ControlCaps; - int iNumOfCtrl = 0; ASIGetNumOfControls(CamNum, &iNumOfCtrl); - for (i = 0; i < iNumOfCtrl; i++) + if (debugLevel >= 3) // this is really only needed for debugging { - ASIGetControlCaps(CamNum, i, &ControlCaps); - //printf("- %s\n", ControlCaps.Name); + printf("Control Caps:\n"); + for (i = 0; i < iNumOfCtrl; i++) + { + ASIGetControlCaps(CamNum, i, &ControlCaps); + printf("- %s:\n", ControlCaps.Name); + printf(" - MinValue = %ld\n", ControlCaps.MinValue); + printf(" - MaxValue = %ld\n", ControlCaps.MaxValue); + printf(" - DefaultValue = %ld\n", ControlCaps.DefaultValue); + printf(" - IsAutoSupported = %d\n", ControlCaps.IsAutoSupported); + printf(" - IsWritable = %d\n", ControlCaps.IsWritable); + printf(" - ControlType = %d\n", ControlCaps.ControlType); + } } if (width == 0 || height == 0) { - width = iMaxWidth; - height = iMaxHeight; + width = iMaxWidth; originalWidth = width; + height = iMaxHeight; originalHeight = height; } - long ltemp = 0; - ASI_BOOL bAuto = ASI_FALSE; - ASIGetControlValue(CamNum, ASI_TEMPERATURE, <emp, &bAuto); - printf("- Sensor temperature:%02f\n", (float)ltemp / 10.0); + ASIGetControlValue(CamNum, ASI_TEMPERATURE, &actualTemp, &bAuto); + printf("- Sensor temperature:%0.2f\n", (float)actualTemp / 10.0); - // Adjusting variables for chosen binning - height = height / bin; - width = width / bin; - iTextX = iTextX / bin; - iTextY = iTextY / bin; - fontsize = fontsize / bin; - linewidth = linewidth / bin; + // Handle "auto" Image_type. + if (Image_type == AUTO_IMAGE_TYPE) + { + // If it's a color camera, create color pictures. + // If it's a mono camera use RAW16 if the image file is a .png, otherwise use RAW8. + // There is no good way to handle Y8 automatically so it has to be set manually. + if (ASICameraInfo.IsColorCam) + Image_type = ASI_IMG_RGB24; + else if (strcmp(imagetype, "png") == 0) + Image_type = ASI_IMG_RAW16; + else // jpg + Image_type = ASI_IMG_RAW8; + } - const char *sType; + const char *sType; // displayed in output if (Image_type == ASI_IMG_RAW16) { sType = "ASI_IMG_RAW16"; - pRgb.create(cvSize(width, height), CV_16UC1); } else if (Image_type == ASI_IMG_RGB24) { sType = "ASI_IMG_RGB24"; - pRgb.create(cvSize(width, height), CV_8UC3); } - else + else if (Image_type == ASI_IMG_RAW8) { - sType = "ASI_IMG_RAW8"; - pRgb.create(cvSize(width, height), CV_8UC1); + // Color cameras should use Y8 instead of RAW8. Y8 is the mono mode for color cameras. + if (ASICameraInfo.IsColorCam) + { + Image_type = ASI_IMG_Y8; + sType = "ASI_IMG_Y8 (not RAW8 for color cameras)"; + } + else + { + sType = "ASI_IMG_RAW8"; + } } - - if (Image_type != ASI_IMG_RGB24 && Image_type != ASI_IMG_RAW16) + else { - iStrLen = strlen(buf); - CvRect rect = cvRect(iTextX, iTextY - 15, iStrLen * 11, 20); - cv::Mat roi = pRgb(rect); - roi.setTo(cv::Scalar(180, 180, 180)); + sType = "ASI_IMG_Y8"; } //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- printf("%s", KGRN); - printf("\nCapture Settings: \n"); + printf("\nCapture Settings:\n"); printf(" Image Type: %s\n", sType); - printf(" Resolution: %dx%d \n", width, height); - printf(" Quality: %d \n", quality); - printf(" Exposure: %1.0fms\n", round(asiExposure / 1000)); - printf(" Max Exposure: %dms\n", asiMaxExposure); - printf(" Auto Exposure: %d\n", asiAutoExposure); - printf(" Gain: %d\n", asiGain); - printf(" Max Gain: %d\n", asiMaxGain); - printf(" Cooler Enabled: %d\n", asiCoolerEnabled); + printf(" Resolution (before any binning): %dx%d\n", width, height); + printf(" Quality: %d\n", quality); + printf(" Daytime capture: %s\n", yesNo(daytimeCapture)); + printf(" Exposure (day): %'1.3fms\n", (float)asiDayExposure / US_IN_MS); + printf(" Auto Exposure (day): %s\n", yesNo(asiDayAutoExposure)); + printf(" Exposure (night): %'1.0fms\n", round(asiNightExposure / US_IN_MS)); + printf(" Max Exposure (night): %'dms\n", asiNightMaxExposure); + printf(" Auto Exposure (night): %s\n", yesNo(asiNightAutoExposure)); + printf(" Delay (day): %'dms\n", dayDelay); + printf(" Delay (night): %'dms\n", nightDelay); + printf(" Gain (night only): %d\n", asiNightGain); + printf(" Auto Gain (night only): %s\n", yesNo(asiNightAutoGain)); + printf(" Max Gain (night only): %d\n", asiNightMaxGain); + printf(" Gain Transition Time: %'d seconds\n", gainTransitionTime); + printf(" Brightness (day): %d\n", asiDayBrightness); + printf(" Brightness (night): %d\n", asiNightBrightness); + printf(" Cooler Enabled: %s\n", yesNo(asiCoolerEnabled)); printf(" Target Temperature: %ldC\n", asiTargetTemp); - printf(" Auto Gain: %d\n", asiAutoGain); - printf(" Brightness: %d\n", asiBrightness); printf(" Gamma: %d\n", asiGamma); printf(" WB Red: %d\n", asiWBR); printf(" WB Blue: %d\n", asiWBB); - printf(" Binning: %d\n", bin); - printf(" Delay: %dms\n", delay); - printf(" Daytime Delay: %dms\n", daytimeDelay); + printf(" Auto WB: %s\n", yesNo(asiAutoWhiteBalance)); + printf(" Binning (day): %d\n", dayBin); + printf(" Binning (night): %d\n", nightBin); printf(" USB Speed: %d\n", asiBandwidth); - printf(" Text Overlay: %s\n", ImgText); - printf(" Text Extra Filename: %s\n", ImgExtraText); + printf(" Auto USB Speed: %s\n", yesNo(asiAutoBandwidth)); + printf(" Text Overlay: %s\n", ImgText[0] == '\0' ? "[none]" : ImgText); + printf(" Text Extra Filename: %s\n", ImgExtraText[0] == '\0' ? "[none]" : ImgExtraText); printf(" Text Extra Filename Age: %d\n", extraFileAge); - printf(" Text Line Height %dpx\n", iTextLineHeight); + printf(" Text Line Height %dpx\n", iTextLineHeight); printf(" Text Position: %dpx left, %dpx top\n", iTextX, iTextY); - printf(" Font Name: %d\n", fontname[fontnumber]); + printf(" Font Name: %d (%s)\n", fontname[fontnumber], fontnames[fontnumber]); printf(" Font Color: %d , %d, %d\n", fontcolor[0], fontcolor[1], fontcolor[2]); printf(" Small Font Color: %d , %d, %d\n", smallFontcolor[0], smallFontcolor[1], smallFontcolor[2]); printf(" Font Line Type: %d\n", linetype[linenumber]); printf(" Font Size: %1.1f\n", fontsize); printf(" Font Line: %d\n", linewidth); - printf(" Outline Font : %d\n", outlinefont); + printf(" Outline Font : %s\n", yesNo(outlinefont)); printf(" Flip Image: %d\n", asiFlip); printf(" Filename: %s\n", fileName); printf(" Latitude: %s\n", latitude); printf(" Longitude: %s\n", longitude); printf(" Sun Elevation: %s\n", angle); - printf(" Preview: %d\n", preview); - printf(" Time: %d\n", Showtime); - printf(" Darkframe: %d\n", darkframe); - printf(" Show Details: %d\n", showDetails); + printf(" Locale: %s\n", locale); + printf(" Notification Images: %s\n", yesNo(notificationImages)); +#ifdef USE_HISTOGRAM + printf(" Histogram Box: %d %d %0.0f %0.0f\n", histogramBoxSizeX, histogramBoxSizeY, + histogramBoxPercentFromLeft * 100, histogramBoxPercentFromTop * 100); + printf(" Show Histogram Box: %s\n", yesNo(showHistogramBox)); + printf(" Show Histogram Mean: %s\n", yesNo(showHistogram)); +#endif + printf(" Show Time: %s (format: %s)\n", yesNo(showTime), timeFormat); + printf(" Show Details: %s\n", yesNo(showDetails)); + printf(" Show Temperature: %s\n", yesNo(showTemp)); + printf(" Temperature Type: %s\n", tempType); + printf(" Show Exposure: %s\n", yesNo(showExposure)); + printf(" Show Gain: %s\n", yesNo(showGain)); + printf(" Show Brightness: %s\n", yesNo(showBrightness)); + printf(" Preview: %s\n", yesNo(preview)); + printf(" Darkframe: %s\n", yesNo(darkframe)); printf(" Debug Level: %d\n", debugLevel); - - printf("%s", KNRM); - - ASISetROIFormat(CamNum, width, height, bin, (ASI_IMG_TYPE)Image_type); + printf(" TTY: %s\n", yesNo(tty)); + printf("%s\n", KNRM); //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- - ASISetControlValue(CamNum, ASI_TEMPERATURE, 50 * 1000, ASI_FALSE); - ASISetControlValue(CamNum, ASI_BANDWIDTHOVERLOAD, asiBandwidth, ASI_FALSE); - ASISetControlValue(CamNum, ASI_EXPOSURE, asiExposure, asiAutoExposure == 1 ? ASI_TRUE : ASI_FALSE); - ASISetControlValue(CamNum, ASI_AUTO_MAX_EXP, asiMaxExposure, ASI_FALSE); - ASISetControlValue(CamNum, ASI_GAIN, asiGain, asiAutoGain == 1 ? ASI_TRUE : ASI_FALSE); - ASISetControlValue(CamNum, ASI_AUTO_MAX_GAIN, asiMaxGain, ASI_FALSE); - ASISetControlValue(CamNum, ASI_WB_R, asiWBR, ASI_FALSE); - ASISetControlValue(CamNum, ASI_WB_B, asiWBB, ASI_FALSE); - ASISetControlValue(CamNum, ASI_GAMMA, asiGamma, ASI_FALSE); - ASISetControlValue(CamNum, ASI_BRIGHTNESS, asiBrightness, ASI_FALSE); - ASISetControlValue(CamNum, ASI_FLIP, asiFlip, ASI_FALSE); + // These configurations apply to both day and night. + // Other calls to setControl() are done after we know if we're in daytime or nighttime. + setControl(CamNum, ASI_BANDWIDTHOVERLOAD, asiBandwidth, asiAutoBandwidth == 1 ? ASI_TRUE : ASI_FALSE); + setControl(CamNum, ASI_HIGH_SPEED_MODE, 0, ASI_FALSE); // ZWO sets this in their program + setControl(CamNum, ASI_WB_R, asiWBR, asiAutoWhiteBalance == 1 ? ASI_TRUE : ASI_FALSE); + setControl(CamNum, ASI_WB_B, asiWBB, asiAutoWhiteBalance == 1 ? ASI_TRUE : ASI_FALSE); + setControl(CamNum, ASI_GAMMA, asiGamma, ASI_FALSE); + setControl(CamNum, ASI_FLIP, asiFlip, ASI_FALSE); + if (ASICameraInfo.IsCoolerCam) { - ASI_ERROR_CODE err = ASISetControlValue(CamNum, ASI_COOLER_ON, asiCoolerEnabled == 1 ? ASI_TRUE : ASI_FALSE, ASI_FALSE); - if (err != ASI_SUCCESS) - { - printf("%s", KRED); - printf(" Could not enable cooler\n"); - printf("%s", KNRM); - } - err = ASISetControlValue(CamNum, ASI_TARGET_TEMP, asiTargetTemp, ASI_FALSE); - if (err != ASI_SUCCESS) + asiRetCode = setControl(CamNum, ASI_COOLER_ON, asiCoolerEnabled == 1 ? ASI_TRUE : ASI_FALSE, ASI_FALSE); + if (asiRetCode != ASI_SUCCESS) + { + printf("%s", KRED); + printf(" WARNING: Could not enable cooler: %s, but continuing without it.\n", getRetCode(asiRetCode)); + printf("%s", KNRM); + } + asiRetCode = setControl(CamNum, ASI_TARGET_TEMP, asiTargetTemp, ASI_FALSE); + if (asiRetCode != ASI_SUCCESS) { - printf("%s", KRED); - printf(" Could not set cooler temperature\n"); - printf("%s", KNRM); + printf("%s", KRED); + printf(" WARNING: Could not set cooler temperature: %s, but continuing without it.\n", getRetCode(asiRetCode)); + printf("%s", KNRM); } } - pthread_t thread_display = 0; if (preview == 1) { bDisplay = 1; @@ -835,262 +1683,827 @@ int main(int argc, char *argv[]) } // Initialization - int currentExposure = asiExposure; - int exp_ms = 0; - long autoGain = 0; - long autoExp = 0; - int useDelay = 0; + int exitCode = 0; // Exit code for main() + int numErrors = 0; // Number of errors in a row. + int maxErrors = 2; // Max number of errors in a row before we exit + int originalITextX = iTextX; + int originalITextY = iTextY; + int originalFontsize = fontsize; + int originalLinewidth = linewidth; + int displayedNoDaytimeMsg = 0; // Have we displayed "not taking picture during day" message, if applicable? + int gainChange = 0; // how much to change gain up or down + + // If autogain is on, our adjustments to gain will get overwritten by the camera + // so don't transition. + // gainTransitionTime of 0 means don't adjust gain. + // No need to adjust gain if day and night gain are the same. + if (asiDayAutoGain == 1 || asiNightAutoGain == 1 || gainTransitionTime == 0 || asiDayGain == asiNightGain || darkframe == 1) + { + adjustGain = false; + printf("Will NOT adjust gain at transitions\n"); + } + else + { + adjustGain = true; + printf("Will adjust gain at transitions\n"); + } + + if (tty) + printf("Press Ctrl+C to stop\n\n"); + else + printf("Stop the allsky service to end this process.\n\n"); while (bMain) { - bool needCapture = true; std::string lastDayOrNight; - int captureTimeout = -1; // Find out if it is currently DAY or NIGHT calculateDayOrNight(latitude, longitude, angle); + if (! darkframe) + currentAdjustGain = resetGainTransitionVariables(asiDayGain, asiNightGain); + lastDayOrNight = dayOrNight; - printf("\n"); - if (dayOrNight == "DAY") + if (darkframe) + { + // We're doing dark frames so turn off autoexposure and autogain, and use + // nightime gain, delay, max exposure, bin, and brightness to mimic a nightime shot. + currentAutoExposure = ASI_FALSE; + setControl(CamNum, ASI_EXPOSURE, currentExposure, currentAutoExposure); + asiNightAutoExposure = 0; + currentAutoGain = ASI_FALSE; + // Don't need to set ASI_AUTO_MAX_GAIN since we're not using auto gain + setControl(CamNum, ASI_GAIN, asiNightGain, ASI_FALSE); + currentGain = asiNightGain; + currentDelay = nightDelay; + currentExposure = asiNightMaxExposure * US_IN_MS; + currentBin = nightBin; + currentBrightness = asiNightBrightness; + + displayDebugText("Taking dark frames...\n", 0); + + if (notificationImages) { + system("scripts/copy_notification_image.sh DarkFrames &"); + } + } + + else if (dayOrNight == "DAY") { // Setup the daytime capture parameters - if (endOfNight == true) + if (endOfNight == true) // Execute end of night script { + sprintf(textBuffer, "Processing end of night data\n"); + displayDebugText(textBuffer, 0); system("scripts/endOfNight.sh &"); endOfNight = false; + displayedNoDaytimeMsg = 0; } + if (daytimeCapture != 1) { - needCapture = false; - sprintf(textBuffer, "It's daytime... we're not saving images\n"); - displayDebugText(textBuffer, 0); - usleep(daytimeDelay * 1000); + // Only display messages once a day. + if (displayedNoDaytimeMsg == 0) { + if (notificationImages) { + system("scripts/copy_notification_image.sh CameraOffDuringDay &"); + } + sprintf(textBuffer, "It's daytime... we're not saving images.\n%s\n", + tty ? "Press Ctrl+C to stop" : "Stop the allsky service to end this process."); + displayDebugText(textBuffer, 0); + displayedNoDaytimeMsg = 1; + + // sleep until almost nighttime, then wake up and sleep a short time + int secsTillNight = calculateTimeToNightTime(latitude, longitude, angle); + sleep(secsTillNight - 10); + } + else + { + // Shouldn't need to sleep more than a few times before nighttime. + sleep(5); + } + + // No need to do any of the code below so go back to the main loop. + continue; } + else { - sprintf(textBuffer, "Starting daytime capture\nSaving auto exposed images every %d ms\n\n", daytimeDelay); + sprintf(textBuffer, "==========\n=== Starting daytime capture ===\n==========\n"); + displayDebugText(textBuffer, 0); + sprintf(textBuffer, "Saving images with delay of %'d ms (%d sec)\n\n", dayDelay, dayDelay / MS_IN_SEC); displayDebugText(textBuffer, 0); - exp_ms = 32; - useDelay = daytimeDelay; - captureTimeout = exp_ms <= 100 ? 200 : exp_ms * 2; - ASISetControlValue(CamNum, ASI_EXPOSURE, exp_ms, ASI_TRUE); - ASISetControlValue(CamNum, ASI_GAIN, 0, ASI_FALSE); +#ifdef USE_HISTOGRAM + // Don't use camera auto exposure since we mimic it ourselves. + if (asiDayAutoExposure == 1) + { + sprintf(textBuffer, "Turning off daytime auto-exposure to use histogram exposure.\n"); + displayDebugText(textBuffer, 2); + currentAutoExposure = ASI_FALSE; + } +#else + currentAutoExposure = asiDayAutoExposure ? ASI_TRUE : ASI_FALSE; +#endif + currentBrightness = asiDayBrightness; + currentDelay = dayDelay; + currentBin = dayBin; + + // If we went from Night to Day, then currentExposure will be the last night + // exposure so leave it if we're using auto-exposure so there's a seamless change from + // Night to Day, i.e., if the exposure was fine a minute ago it will likely be fine now. + // On the other hand, if this program just started or we're using manual exposures, + // use what the user specified. + if (numExposures == 0 || asiDayAutoExposure == ASI_FALSE) + { + currentExposure = asiDayExposure; + } + else + { + sprintf(textBuffer, "Using last night exposure of %'ld µs (%'.2lf ms)\n", currentExposure, (float)currentExposure / US_IN_MS); + displayDebugText(textBuffer, 2); + } +#ifndef USE_HISTOGRAM + setControl(CamNum, ASI_EXPOSURE, currentExposure, currentAutoExposure); +#endif + setControl(CamNum, ASI_AUTO_MAX_EXP, cameraMaxAutoExposureUS / US_IN_MS, ASI_FALSE); // need ms + currentGain = asiDayGain; // must come before determineGainChange() below + if (currentAdjustGain) + { + // we did some nightime images so adjust gain + numGainChanges = 0; + gainChange = determineGainChange(asiDayGain, asiNightGain); + } + else + { + gainChange = 0; + } + currentAutoGain = asiDayAutoGain ? ASI_TRUE : ASI_FALSE; + setControl(CamNum, ASI_GAIN, currentGain + gainChange, currentAutoGain); + // We don't have a separate asiDayMaxGain, so set to night one + setControl(CamNum, ASI_AUTO_MAX_GAIN, asiNightMaxGain, ASI_FALSE); } } - else if (dayOrNight == "NIGHT") + + else // NIGHT { + sprintf(textBuffer, "==========\n=== Starting nighttime capture ===\n==========\n"); + displayDebugText(textBuffer, 0); + // Setup the night time capture parameters - if (asiAutoExposure == 1) + if (asiNightAutoExposure == 1) + { + currentAutoExposure = ASI_TRUE; + setControl(CamNum, ASI_AUTO_MAX_EXP, asiNightMaxExposure, ASI_FALSE); + printf("Saving auto exposed night images with delay of %'d ms (%d sec)\n\n", nightDelay, nightDelay / MS_IN_SEC); + } + else + { + currentAutoExposure = ASI_FALSE; + printf("Saving %ds manual exposure night images with delay of %'d ms (%d sec)\n\n", (int)round(currentExposure / US_IN_SEC), nightDelay, nightDelay / MS_IN_SEC); + } + + currentBrightness = asiNightBrightness; + currentDelay = nightDelay; + currentBin = nightBin; + if (numExposures == 0 || asiDayAutoExposure == ASI_FALSE) { - printf("Saving auto exposed images every %d ms\n\n", delay); + currentExposure = asiNightExposure; + } +#ifndef USE_HISTOGRAM + setControl(CamNum, ASI_EXPOSURE, currentExposure, currentAutoExposure); +#endif + currentGain = asiNightGain; // must come before determineGainChange() below + if (currentAdjustGain) + { + // we did some daytime images so adjust gain + numGainChanges = 0; + gainChange = determineGainChange(asiDayGain, asiNightGain); } else { - printf("Saving %ds exposure images every %d ms\n\n", (int)round(currentExposure / 1000000), delay); + gainChange = 0; } - // Set exposure value for night time capture - useDelay = delay; - ASISetControlValue(CamNum, ASI_EXPOSURE, currentExposure, asiAutoExposure == 1 ? ASI_TRUE : ASI_FALSE); - ASISetControlValue(CamNum, ASI_GAIN, asiGain, asiAutoGain == 1 ? ASI_TRUE : ASI_FALSE); + currentAutoGain = asiNightAutoGain ? ASI_TRUE : ASI_FALSE; + setControl(CamNum, ASI_GAIN, currentGain + gainChange, currentAutoGain); + setControl(CamNum, ASI_AUTO_MAX_GAIN, asiNightMaxGain, ASI_FALSE); } - printf("Press Ctrl+C to stop\n\n"); - if (needCapture) + // Adjusting variables for chosen binning + height = originalHeight / currentBin; + width = originalWidth / currentBin; + iTextX = originalITextX / currentBin; + iTextY = originalITextY / currentBin; + fontsize = originalFontsize / currentBin; + linewidth = originalLinewidth / currentBin; + bufferSize = width * height * bytesPerPixel((ASI_IMG_TYPE) Image_type); + if (numExposures > 0 && dayBin != nightBin) { - ASIStartVideoCapture(CamNum); - while (bMain && lastDayOrNight == dayOrNight) + // No need to print after first time if the binning didn't change. + sprintf(debugText, "Buffer size: %ld\n", bufferSize); + displayDebugText(debugText, 2); + } + + if (Image_type == ASI_IMG_RAW16) + { + pRgb.create(cvSize(width, height), CV_16UC1); + } + else if (Image_type == ASI_IMG_RGB24) + { + pRgb.create(cvSize(width, height), CV_8UC3); + } + else // RAW8 and Y8 + { + pRgb.create(cvSize(width, height), CV_8UC1); + } + + ASISetROIFormat(CamNum, width, height, currentBin, (ASI_IMG_TYPE)Image_type); + setControl(CamNum, ASI_BRIGHTNESS, currentBrightness, ASI_FALSE); // ASI_BRIGHTNESS == ASI_OFFSET + + // Here and below, indent sub-messages with " > " so it's clear they go with the un-indented line. + // This simply makes it easier to see things in the log file. + + // As of April 2021 there's a bug that causes the first 3 images to be identical, + // so take 3 short ones but don't save them. + // On the ASI178MC the shortest time is 10010 µs; it may be higher on other cameras, + // so use a higher value like 30,000 µs to be safe. + // Only do this once. + if (numExposures == 0) { +#define SHORT_EXPOSURE 30000 + displayDebugText("===Taking 3 images to clear buffer...\n", 2); + // turn off auto exposure + ASI_BOOL savedAutoExposure = currentAutoExposure; + currentAutoExposure = ASI_FALSE; + for (i=1; i <= 3; i++) { - if (ASIGetVideoData(CamNum, pRgb.data, pRgb.step[0] * pRgb.rows, captureTimeout) == ASI_SUCCESS) + // don't count these as "real" exposures, so don't increment numExposures. + asiRetCode = takeOneExposure(CamNum, SHORT_EXPOSURE, pRgb.data, width, height, (ASI_IMG_TYPE) Image_type); + if (asiRetCode != ASI_SUCCESS) { - // Read current camera parameters - ASIGetControlValue(CamNum, ASI_EXPOSURE, &autoExp, &bAuto); - ASIGetControlValue(CamNum, ASI_GAIN, &autoGain, &bAuto); - ASIGetControlValue(CamNum, ASI_TEMPERATURE, <emp, &bAuto); + numErrors++; + sleep(2); // sometimes sleeping keeps errors from reappearing + } + } + if (numErrors >= maxErrors) + { + bMain = false; + exitCode = 2; + break; + } + + // Restore correct exposure times and auto-exposure mode. + currentAutoExposure = savedAutoExposure; + setControl(CamNum, ASI_EXPOSURE, currentExposure, currentAutoExposure); + sprintf(debugText, "...DONE. Reset exposure to %'ld µs\n", currentExposure); + displayDebugText(debugText, 2); + // END of bug code + } - // Write temperature to file - writeTemperatureToFile((float)ltemp / 10.0); + int mean = 0; - // Get Current Time for overlay - sprintf(bufTime, "%s", getTime()); + while (bMain && lastDayOrNight == dayOrNight) + { + // date/time is added to many log entries to make it easier to associate them + // with an image (which has the date/time in the filename). + timeval t; + t = getTimeval(); + char exposureStart[128]; + char f[10] = "%F %T"; + sprintf(exposureStart, "%s", formatTime(t, f)); + sprintf(textBuffer, "STARTING EXPOSURE at: %s\n", exposureStart); + displayDebugText(textBuffer, 0); + + // Get start time for overlay. Make sure it has the same time as exposureStart. + if (showTime == 1) + sprintf(bufTime, "%s", formatTime(t, timeFormat)); + + asiRetCode = takeOneExposure(CamNum, currentExposure, pRgb.data, width, height, (ASI_IMG_TYPE) Image_type); + if (asiRetCode == ASI_SUCCESS) + { + numErrors = 0; + numExposures++; - if (darkframe != 1) +#ifdef USE_HISTOGRAM + int usedHistogram = 0; // did we use the histogram method? + // We don't use this at night since the ZWO bug is only when it's light outside. + if (dayOrNight == "DAY" && asiDayAutoExposure && ! darkframe && currentExposure <= cameraMaxAutoExposureUS) + { + int minAcceptableHistogram; + int maxAcceptableHistogram; + int reallyLowMean; + int lowMean; + int roundToMe; + + usedHistogram = 1; // we are using the histogram code on this exposure + int histogram[256]; + computeHistogram(pRgb.data, width, height, (ASI_IMG_TYPE) Image_type, histogram); + mean = calculateHistogramMean(histogram); + // "last_OK_exposure" is the exposure time of the last OK + // image (i.e., mean < 255). + // The intent is to keep track of the last OK exposure in case the final + // exposure we calculate is no good, we can go back to the last OK one. + long last_OK_exposure = currentExposure; + + int attempts = 0; + long newExposure = 0; + + int minExposure = 100; + long tempMinExposure = minExposure; + long tempMaxExposure = asiNightMaxExposure * US_IN_MS; + + // Got these by trial and error. They are more-or-less half the max of 255. + minAcceptableHistogram = 120; + maxAcceptableHistogram = 136; + reallyLowMean = 5; + lowMean = 15; + + roundToMe = 5; // round exposures to this many microseconds + + if (asiDayBrightness != DEFAULT_BRIGHTNESS) { - // If darkframe mode is off, add overlay text to the image - int iYOffset = 0; - //cvText(pRgb, ImgText, iTextX, iTextY+(iYOffset/bin), fontsize, linewidth, linetype[linenumber], fontname[fontnumber], fontcolor, Image_type); - //iYOffset+=30; - if (Showtime == 1) + // Adjust brightness based on asiDayBrightness. + // The default value has no adjustment. + // The only way we can do this easily is via adjusting the exposure. + // We could apply a stretch to the image, but that's more difficult. + // Sure would be nice to see how ZWO handles this variable. + // We asked but got a useless reply. + // Values below the default make the image darker; above make it brighter. + + float exposureAdjustment, numMultiples; + + // Adjustments of DEFAULT_BRIGHTNESS up or down make the image this much darker/lighter. + // Don't want the max brightness to give pure white. + //xxx May have to play with this number, but it seems to work ok. + float adjustmentAmountPerMultiple = 0.12; // 100 * this number is the percent to change + + // The amount doesn't change after being set, so only display once. + static int showedMessage = 0; + if (showedMessage == 0) { - cvText(pRgb, bufTime, iTextX, iTextY + (iYOffset / bin), fontsize * 0.1, linewidth, - linetype[linenumber], fontname[fontnumber], fontcolor, Image_type, outlinefont); - iYOffset += iTextLineHeight; + // Determine the adjustment amount - only done once. + // See how many multiples we're different. + // If asiDayBrightnes < DEFAULT_BRIGHTNESS the numMultiples will be negative, + // which is ok - it just means the multiplier will be less than 1. + numMultiples = (asiDayBrightness - DEFAULT_BRIGHTNESS) / DEFAULT_BRIGHTNESS; + exposureAdjustment = 1 + (numMultiples * adjustmentAmountPerMultiple); + sprintf(textBuffer, " > >>> Adjusting exposure %.1f%% for daybrightness\n", (exposureAdjustment - 1) * 100); + displayDebugText(textBuffer, 2); + showedMessage = 1; } - if (showDetails == 1) + // Now adjust the variables + minExposure *= exposureAdjustment; + reallyLowMean *= exposureAdjustment; + lowMean *= exposureAdjustment; + minAcceptableHistogram *= exposureAdjustment; + maxAcceptableHistogram *= exposureAdjustment; + } + + while ((mean < minAcceptableHistogram || mean > maxAcceptableHistogram) && ++attempts <= maxHistogramAttempts) + { + sprintf(textBuffer, " > Attempt %i, current exposure %'ld µs, mean %d, temp min exposure %ld µs, tempMaxExposure %'ld µs", attempts, currentExposure, mean, tempMinExposure, tempMaxExposure); + displayDebugText(textBuffer, 2); + + std::string why; // Why did we adjust the exposure? For debugging + int num; + if (mean >= 254) { + newExposure = currentExposure * 0.4; + tempMaxExposure = currentExposure - roundToMe; + why = "mean >= max"; + num = 254; + } + else + { + // The code below takes into account how far off we are from an acceptable mean. + // There's probably a simplier way to do this, like adjust by some multiple of + // how far of we are. That exercise is left to the reader... + last_OK_exposure = currentExposure; + if (mean < reallyLowMean) { + // The cameras don't appear linear at this low of a level, + // so really crank it up to get into the linear area. + newExposure = currentExposure * 20; + tempMinExposure = currentExposure + roundToMe; + why = "mean < reallyLowMean"; + num = reallyLowMean; + } + else if (mean < lowMean) { + newExposure = currentExposure * 5; + tempMinExposure = currentExposure + roundToMe; + why = "mean < lowMean"; + num = lowMean; + } + else if (mean < (minAcceptableHistogram * 0.6)) + { + newExposure = currentExposure * 2.5; + tempMinExposure = currentExposure + roundToMe; + why = "mean < (minAcceptableHistogram * 0.6)"; + num = minAcceptableHistogram * 0.6; + } + else if (mean < minAcceptableHistogram) + { + newExposure = currentExposure * 1.1; + tempMinExposure = currentExposure + roundToMe; + why = "mean < minAcceptableHistogram"; + num = minAcceptableHistogram; + } + else if (mean > (maxAcceptableHistogram * 1.6)) + { + newExposure = currentExposure * 0.7; + tempMaxExposure = currentExposure - roundToMe; + why = "mean > (maxAcceptableHistogram * 1.6)"; + num = (maxAcceptableHistogram * 1.6); + } + else if (mean > maxAcceptableHistogram) + { + newExposure = currentExposure * 0.9; + tempMaxExposure = currentExposure - roundToMe; + why = "mean > maxAcceptableHistogram"; + num = maxAcceptableHistogram; + } + } + + newExposure = roundTo(newExposure, roundToMe); + newExposure = std::max(tempMinExposure, newExposure); + newExposure = std::min(newExposure, tempMaxExposure); + newExposure = std::max(tempMinExposure, newExposure); + newExposure = std::min(newExposure, cameraMaxAutoExposureUS); + + sprintf(textBuffer, ", new exposure %'ld µs\n", newExposure); + displayDebugText(textBuffer, 2); + + if (newExposure == currentExposure) + { + // We can't find a better exposure so stick with this one + // or the last OK one. If the last exposure had a mean >= 254, + // use the most recent exposure that was OK. + if (mean >= 254 && 0) { // xxxxxxxxxxxxxxxxxxxx This needs work so disabled + currentExposure = last_OK_exposure; + sprintf(textBuffer, " > !!! Resetting to last OK exposure of '%ld µs\n", currentExposure); + displayDebugText(textBuffer, 2); + takeOneExposure(CamNum, currentExposure, pRgb.data, width, height, (ASI_IMG_TYPE) Image_type); + computeHistogram(pRgb.data, width, height, (ASI_IMG_TYPE) Image_type, histogram); + mean = calculateHistogramMean(histogram); + } + break; + } + + currentExposure = newExposure; + + sprintf(textBuffer, " > !!! Retrying @ %'ld µs because '%s (%d)'\n", currentExposure, why.c_str(), num); + displayDebugText(textBuffer, 2); + takeOneExposure(CamNum, currentExposure, pRgb.data, width, height, (ASI_IMG_TYPE) Image_type); + computeHistogram(pRgb.data, width, height, (ASI_IMG_TYPE) Image_type, histogram); + mean = calculateHistogramMean(histogram); + } + if (attempts > maxHistogramAttempts) + { + sprintf(textBuffer, " > max attempts reached - using exposure of %'ld µs with mean %d\n", currentExposure, mean); + displayDebugText(textBuffer, 2); + } + else if (attempts > 1) + { + sprintf(textBuffer, " > Using exposure of %'ld µs with mean %d\n", currentExposure, mean); + displayDebugText(textBuffer, 2); + } + else if (attempts == 1) + { + sprintf(textBuffer, " > Current exposure of %'ld µs with mean %d was ok - no additional attempts needed.\n", currentExposure, mean); + displayDebugText(textBuffer, 2); + } + actualExposureMicroseconds = currentExposure; + } else { + currentExposure = actualExposureMicroseconds; + } +#endif + // Write temperature to file + writeTemperatureToFile((float)actualTemp / 10.0); + + // If darkframe mode is off, add overlay text to the image + if (! darkframe) + { + int iYOffset = 0; + + if (showTime == 1) + { + // The time and ImgText are in the larger font; everything else is in smaller font. + cvText(pRgb, bufTime, iTextX, iTextY + (iYOffset / currentBin), fontsize * 0.1, linewidth, + linetype[linenumber], fontname[fontnumber], fontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + + if (ImgText[0] != '\0') + { + cvText(pRgb, ImgText, iTextX, iTextY + (iYOffset / currentBin), fontsize * 0.1, linewidth, + linetype[linenumber], fontname[fontnumber], fontcolor, Image_type, outlinefont); + iYOffset+=iTextLineHeight; + } + + + if (showTemp == 1) + { + char C[20] = { 0 }, F[20] = { 0 }; + if (strcmp(tempType, "C") == 0 || strcmp(tempType, "B") == 0) + { + sprintf(C, " %.0fC", (float)actualTemp / 10); + } + if (strcmp(tempType, "F") == 0 || strcmp(tempType, "B") == 0) + { + sprintf(F, " %.0fF", (((float)actualTemp / 10 * 1.8) + 32)); + } + sprintf(bufTemp, "Sensor: %s %s", C, F); + cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, + linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + + if (showExposure == 1) + { + // Indicate when the time to take the exposure is less than the reported exposure time + if (actualExposureMicroseconds == currentExposure) + bufTemp2[0] = '\0'; + else + sprintf(bufTemp2, " actual %'.2lf ms)", (double)actualExposureMicroseconds / US_IN_MS); + if (actualExposureMicroseconds >= (1 * US_IN_SEC)) // display in seconds if >= 1 second, else in ms + sprintf(bufTemp, "Exposure: %'.2f s%s", (float)currentExposure / US_IN_SEC, bufTemp2); + else + sprintf(bufTemp, "Exposure: %'.2f ms%s", (float)currentExposure / US_IN_MS, bufTemp2); + // Indicate if in auto exposure mode. + if (currentAutoExposure == ASI_TRUE) strcat(bufTemp, " (auto)"); + cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, + linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + + if (showGain == 1) + { + sprintf(bufTemp, "Gain: %ld", actualGain); + + // Indicate if in auto gain mode. + if (currentAutoGain == ASI_TRUE) strcat(bufTemp, " (auto)"); + // Indicate if in gain transition mode. + if (gainChange != 0) { - sprintf(bufTemp, "Sensor %.1fC", (float)ltemp / 10); - cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / bin), fontsize * 0.08, linewidth, - linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); - iYOffset += iTextLineHeight; - sprintf(bufTemp, "Exposure %.3f s", (float)autoExp / 1000000); - cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / bin), fontsize * 0.08, linewidth, - linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); - iYOffset += iTextLineHeight; - sprintf(bufTemp, "Gain %d", (int)autoGain); - cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / bin), fontsize * 0.08, linewidth, - linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); - iYOffset += iTextLineHeight; + char x[20]; + sprintf(x, " (adj: %+d)", gainChange); + strcat(bufTemp, x); } - /** - * Display extra text if required. The extra text is read from the provided file. If the - * age of the file exceeds the specified limit then the text in the file is not displayed - * this is to prevent situations where the code updating the text file stops working. - **/ - if (ImgExtraText[0] != '\0') { - bool bUseExtraFile = true; - if (access(ImgExtraText, F_OK ) == -1 ) { - bUseExtraFile = false; - displayDebugText("Extra Text File Does Not Exist So Ignoring It\n", 1); - } else { - if (access(ImgExtraText, R_OK ) == -1 ) { - displayDebugText("Cannot Read From Extra Text File So Ignoring It\n", 1); - bUseExtraFile = false; - } - } - if (bUseExtraFile) { - FILE *fp = fopen(ImgExtraText, "r"); - - if (fp != NULL) { - bool bAddExtra = false; - if (extraFileAge > 0) { - struct stat buffer; - if (stat(ImgExtraText, &buffer) == 0) { - struct tm modifiedTime = *localtime(&buffer.st_mtime); - - time_t now = time(NULL); - double ageInSeconds = difftime(now, mktime(&modifiedTime)); - sprintf(textBuffer, "Extra Text File (%s) Modified %f seconds ago ", ImgExtraText, ageInSeconds); - displayDebugText(textBuffer, 1); - if (ageInSeconds < extraFileAge) { - displayDebugText("So Using It\n", 1); - bAddExtra = true; - } else { - displayDebugText("So Ignoring\n", 1); - } + cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, + linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + if (currentAdjustGain) + { + // Determine if we need to change the gain on the next image. + // This must come AFTER the "showGain" above. + gainChange = determineGainChange(asiDayGain, asiNightGain); + setControl(CamNum, ASI_GAIN, currentGain + gainChange, currentAutoGain); + } + + if (showBrightness == 1) + { + sprintf(bufTemp, "Brightness: %d", currentBrightness); + cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, + linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + +#ifdef USE_HISTOGRAM + if (showHistogram && usedHistogram) + { + sprintf(bufTemp, "Histogram mean: %d", mean); + cvText(pRgb, bufTemp, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, + linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + } + if (showHistogramBox && usedHistogram) + { + // Draw a rectangle where the histogram box is. + + int lt = CV_AA, thickness = 2; + cv::Point from1, to1, from2, to2; + int X1 = (width * histogramBoxPercentFromLeft) - (histogramBoxSizeX / 2); + int X2 = X1 + histogramBoxSizeX; + int Y1 = (height * histogramBoxPercentFromTop) - (histogramBoxSizeY / 2); + int Y2 = Y1 + histogramBoxSizeY; + // Put a black and white line one next to each other so they + // can be seen in day and night images. + // The black line is on the outside; the white on the inside. + // cv::line takes care of bytes per pixel. + + // top lines + from1 = cvPoint(X1, Y1); + to1 = cvPoint(X2, Y1); + from2 = cvPoint(X1, Y1+thickness); + to2 = cvPoint(X2, Y1+thickness); + cv::line(pRgb, from1, to1, cvScalar(0,0,0), thickness, lt); + cv::line(pRgb, from2, to2, cvScalar(255,255,255), thickness, lt); + + // right lines + from1 = cvPoint(X2, Y1); + to1 = cvPoint(X2, Y2); + from2 = cvPoint(X2-thickness, Y1+thickness); + to2 = cvPoint(X2-thickness, Y2-thickness); + cv::line(pRgb, from1, to1, cvScalar(0,0,0), thickness, lt); + cv::line(pRgb, from2, to2, cvScalar(255,255,255), thickness, lt); + + // bottom lines + from1 = cvPoint(X1, Y2); + to1 = cvPoint(X2, Y2); + from2 = cvPoint(X1, Y2-thickness); + to2 = cvPoint(X2, Y2-thickness); + cv::line(pRgb, from1, to1, cvScalar(0,0,0), thickness, lt); + cv::line(pRgb, from2, to2, cvScalar(255,255,255), thickness, lt); + + // left lines + from1 = cvPoint(X1, Y1); + to1 = cvPoint(X1, Y2); + from2 = cvPoint(X1+thickness, Y1+thickness); + to2 = cvPoint(X1+thickness, Y2-thickness); + cv::line(pRgb, from1, to1, cvScalar(0,0,0), thickness, lt); + cv::line(pRgb, from2, to2, cvScalar(255,255,255), thickness, lt); + } +#endif + /** + * Display extra text if required. The extra text is read from the provided file. If the + * age of the file exceeds the specified limit then the text in the file is not displayed + * this is to prevent situations where the code updating the text file stops working. + **/ + if (ImgExtraText[0] != '\0') { + bool bUseExtraFile = true; + // Display these messages every time, since it's possible the user will correct the + // issue while we're running. + if (access(ImgExtraText, F_OK ) == -1 ) { + bUseExtraFile = false; + displayDebugText(" > *** WARNING: Extra Text File Does Not Exist So Ignoring It\n", 1); + } else if (access(ImgExtraText, R_OK ) == -1 ) { + displayDebugText(" > *** ERROR: Cannot Read From Extra Text File So Ignoring It\n", 1); + bUseExtraFile = false; + } + + if (bUseExtraFile) { + FILE *fp = fopen(ImgExtraText, "r"); + + if (fp != NULL) { + bool bAddExtra = false; + if (extraFileAge > 0) { + struct stat buffer; + if (stat(ImgExtraText, &buffer) == 0) { + struct tm modifiedTime = *localtime(&buffer.st_mtime); + + time_t now = time(NULL); + double ageInSeconds = difftime(now, mktime(&modifiedTime)); + sprintf(textBuffer, " > Extra Text File (%s) Modified %.1f seconds ago", ImgExtraText, ageInSeconds); + displayDebugText(textBuffer, 1); + if (ageInSeconds < extraFileAge) { + displayDebugText(", so Using It\n", 1); + bAddExtra = true; } else { - displayDebugText("Stat Of Extra Text File Failed !\n", 1); + displayDebugText(", so Ignoring\n", 1); } } else { - displayDebugText("Extra Text File Age Disabled So Displaying Anyway\n", 1); - bAddExtra = true; + displayDebugText(" > *** ERROR: Stat Of Extra Text File Failed !\n", 0); } - if (bAddExtra) { - char *line = NULL; - size_t len = 0; - while (getline(&line, &len, fp) != -1) { - if (line[strlen(line)-1] == 10) { - line[strlen(line)-1] = '\0'; - }; - cvText(pRgb, line, iTextX, iTextY + (iYOffset / bin), fontsize * 0.08, linewidth, linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); - iYOffset += iTextLineHeight; + } else { + // xxx Should really only display this once, maybe at program start. + displayDebugText(" > Extra Text File Age Disabled So Displaying Anyway\n", 1); + bAddExtra = true; + } + if (bAddExtra) { + char *line = NULL; + size_t len = 0; + int slen = 0; + while (getline(&line, &len, fp) != -1) { + slen = strlen(line); + if (slen > 1 && (line[slen-2] == 10 || line[slen-2] == 13)) { // LF, CR + line[slen-2] = '\0'; + } else if (line[slen-1] == 10 || line[slen-1] == 13) { + line[slen-1] = '\0'; } + slen = strlen(line); + sprintf(textBuffer, " > Extra Text line 13: '%s' (new length=%d)\n", line, slen); + displayDebugText(textBuffer, 3); + + cvText(pRgb, line, iTextX, iTextY + (iYOffset / currentBin), fontsize * SMALLFONTSIZE_MULTIPLIER, linewidth, linetype[linenumber], fontname[fontnumber], smallFontcolor, Image_type, outlinefont); + iYOffset += iTextLineHeight; + if (slen > 0) free(line); } - fclose(fp); - } else { - displayDebugText("Failed To Open Extra Text File\n", 1); } + fclose(fp); + } else { + displayDebugText(" > *** WARNING: Failed To Open Extra Text File\n", 0); } - } else { - displayDebugText("No Extra Text File Specified\n", 1); } } - sprintf(textBuffer, "Exposure value: %.0f µs\n", (float)autoExp); + } + +#ifndef USE_HISTOGRAM + if (currentAutoExposure == ASI_TRUE) + { + // Retrieve the current Exposure for smooth transition to night time + // as long as auto-exposure is enabled during night time + currentExposure = actualExposureMicroseconds; + } +#endif + + // Save the image + if (bSavingImg == false) + { + sprintf(textBuffer, " > Saving image '%s' that started at %s", fileName, exposureStart); displayDebugText(textBuffer, 0); - if (asiAutoExposure == 1) - { - // Retrieve the current Exposure for smooth transition to night time - // as long as auto-exposure is enabled during night time - currentExposure = autoExp; - } - // Save the image - sprintf(textBuffer, "%s \n", bufTime); + pthread_mutex_lock(&mtx_SaveImg); + // Display the time it took to save an image, for debugging. + int64 st = cvGetTickCount(); + pthread_cond_signal(&cond_SatrtSave); + int64 et = cvGetTickCount(); + pthread_mutex_unlock(&mtx_SaveImg); + + sprintf(textBuffer, " (%.0f µs)\n", timeDiff(st, et)); displayDebugText(textBuffer, 0); - if (!bSavingImg) - { - pthread_mutex_lock(&mtx_SaveImg); - pthread_cond_signal(&cond_SatrtSave); - pthread_mutex_unlock(&mtx_SaveImg); - } + } + else + { + // Hopefully the user can use the time it took to save a file to disk + // to help determine why they are getting this warning. + // Perhaps their disk is very slow or their delay is too short. + sprintf(textBuffer, " > WARNING: currently saving an image; can't save new one at %s.\n", exposureStart); + displayDebugText(textBuffer, 0); + } - if (asiAutoGain == 1 && dayOrNight == "NIGHT") + if (asiNightAutoGain == 1 && dayOrNight == "NIGHT" && ! darkframe) + { + ASIGetControlValue(CamNum, ASI_GAIN, &actualGain, &bAuto); + sprintf(textBuffer, " > Auto Gain value: %ld\n", actualGain); + displayDebugText(textBuffer, 1); + writeToLog((int)actualGain); + } + + if (currentAutoExposure == ASI_TRUE) + { +#ifndef USE_HISTOGRAM + writeToLog((int)actualExposureMicroseconds); + + if (dayOrNight == "DAY") { - ASIGetControlValue(CamNum, ASI_GAIN, &autoGain, &bAuto); - sprintf(textBuffer, "Auto Gain value: %d\n", (int)autoGain); - displayDebugText(textBuffer, 0); - writeToLog(autoGain); + currentExposure = actualExposureMicroseconds; } +#endif - if (asiAutoExposure == 1) + // Delay applied before next exposure + if (dayOrNight == "NIGHT" && asiNightAutoExposure == 1 && actualExposureMicroseconds < (asiNightMaxExposure * US_IN_MS) && ! darkframe) { - printf("Auto Exposure value: %d ms\n", (int)round(autoExp / 1000)); - writeToLog(autoExp); - if (dayOrNight == "NIGHT") - { - ASIGetControlValue(CamNum, ASI_EXPOSURE, &autoExp, &bAuto); - } - else - { - currentExposure = autoExp; - } - - // Delay applied before next exposure - if (autoExp < asiMaxExposure * 1000 && dayOrNight == "NIGHT") - { - // if using auto-exposure and the actual exposure is less than the max, - // we still wait until we reach maxexposure. This is important for a - // constant frame rate during timelapse generation - sprintf(textBuffer,"Sleeping: %d ms\n", asiMaxExposure - (int)(autoExp / 1000) + useDelay); - displayDebugText(textBuffer, 0); - usleep((asiMaxExposure * 1000 - autoExp) + useDelay * 1000); - } - else - { - usleep(useDelay * 1000); - } + // If using auto-exposure and the actual exposure is less than the max, + // we still wait until we reach maxexposure, then wait for the delay period. + // This is important for a constant frame rate during timelapse generation. + // This doesn't apply during the day since we don't have a max time then. + int s = (asiNightMaxExposure * US_IN_MS) - actualExposureMicroseconds; // to get to max + s += currentDelay * US_IN_MS; // Add standard delay amount + sprintf(textBuffer," > Sleeping: %'d ms\n", s / US_IN_MS); + displayDebugText(textBuffer, 0); + usleep(s); // usleep() is in microseconds } else { - usleep(useDelay * 1000); + // Sleep even if taking dark frames so the sensor can cool between shots like it would + // do on a normal night. With no delay the sensor may get hotter than it would at night. + sprintf(textBuffer," > Sleeping from %s exposure: %'d ms (%.0f sec)\n", darkframe ? "dark frame" : "auto", currentDelay, (float)currentDelay/US_IN_MS); + displayDebugText(textBuffer, 0); + usleep(currentDelay * US_IN_MS); } - calculateDayOrNight(latitude, longitude, angle); + } + else + { + std::string s; + if (darkframe) + s = "dark frame"; + else + s = "manual"; +#ifdef USE_HISTOGRAM + if (usedHistogram == 1) + s = "histogram"; +#endif + sprintf(textBuffer," > Sleeping from %s exposure: %'d ms\n", s.c_str(), currentDelay); + displayDebugText(textBuffer, 0); + usleep(currentDelay * US_IN_MS); + } + calculateDayOrNight(latitude, longitude, angle); + + } else { + // Once takeOneExposure() fails with a timeout, it seems to always fail, + // even with extremely large timeout values, so apparently ASI_ERROR_TIMEOUT doesn't + // necessarily mean it's timing out. I think it means the camera went away, + // so exit which will cause us to be restarted. + numErrors++; sleep(2); + if (numErrors >= maxErrors) + { + bMain = false; // get out of inner and outer loop + exitCode = 2; } } - if (lastDayOrNight == "NIGHT") - { - endOfNight = true; - } - ASIStopVideoCapture(CamNum); } - } - ASICloseCamera(CamNum); - - if (bDisplay) - { - bDisplay = 0; - pthread_join(thread_display, &retval); + if (lastDayOrNight == "NIGHT") + { + endOfNight = true; + } } - if (bSaveRun) - { - bSaveRun = false; - pthread_mutex_lock(&mtx_SaveImg); - pthread_cond_signal(&cond_SatrtSave); - pthread_mutex_unlock(&mtx_SaveImg); - pthread_join(hthdSave, 0); - } - printf("main function over\n"); - return 1; + closeUp(exitCode); } diff --git a/capture_RPiHQ.cpp b/capture_RPiHQ.cpp index b012d7503..c6c972be7 100644 --- a/capture_RPiHQ.cpp +++ b/capture_RPiHQ.cpp @@ -1,7 +1,7 @@ #include #include #include -//#include +#include //#include #include #include @@ -30,6 +30,10 @@ using namespace std; #define KCYN "\x1B[36m" #define KWHT "\x1B[37m" +#define US_IN_MS 1000 // microseconds in a millisecond +#define MS_IN_SEC 1000 // milliseconds in a second +#define US_IN_SEC (US_IN_MS * MS_IN_SEC) // microseconds in a second + //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- @@ -38,11 +42,52 @@ char const *fileName = "image.jpg"; bool bMain = true; //ol bDisplay = false; std::string dayOrNight; +int numExposures = 0; // how many valid pictures have we taken so far? + +// Some command-line and other option definitions needed outside of main(): +int tty = 0; // 1 if we're on a tty (i.e., called from the shell prompt). +#define NOT_SET -1 // signifies something isn't set yet +#define DEFAULT_NOTIFICATIONIMAGES 1 +int notificationImages = DEFAULT_NOTIFICATIONIMAGES; -bool bSavingImg = false; +//bool bSavingImg = false; //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- + +char debugText[500]; // buffer to hold debug messages displayed by displayDebugText() +int debugLevel = 0; +/** + * Helper function to display debug info +**/ +void displayDebugText(const char * text, int requiredLevel) { + if (debugLevel >= requiredLevel) { + printf("%s", text); + } +} + +// Return the numeric time. +timeval getTimeval() +{ + timeval curTime; + gettimeofday(&curTime, NULL); + return(curTime); +} + +// Format a numeric time as a string. +char *formatTime(timeval t, char const *tf) +{ + static char TimeString[128]; + strftime(TimeString, 80, tf, localtime(&t.tv_sec)); + return(TimeString); +} + +// Return the current time as a string. Uses both functions above. +char *getTime(char const *tf) +{ + return(formatTime(getTimeval(), tf)); +} + std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) { size_t start_pos = 0; while((start_pos = str.find(from, start_pos)) != std::string::npos) { @@ -83,9 +128,54 @@ void *Display(void *params) return (void *)0; } */ + +// Exit the program gracefully. +void closeUp(int e) +{ + static int closingUp = 0; // indicates if we're in the process of exiting. + // For whatever reason, we're sometimes called twice, but we should only execute once. + if (closingUp) return; + + closingUp = 1; + + // If we're not on a tty assume we were started by the service. + // Unfortunately we don't know if the service is stopping us, or restarting us. + // If it was restarting there'd be no reason to copy a notification image since it + // will soon be overwritten. Since we don't know, always copy it. + if (notificationImages) { + system("scripts/copy_notification_image.sh NotRunning &"); + // Sleep to give it a chance to print any messages so they (hopefully) get printed + // before the one below. This is only so it looks nicer in the log file. + sleep(3); + } + + printf(" ***** Stopping AllSky *****\n"); + exit(e); +} + void IntHandle(int i) { bMain = false; + closeUp(0); +} + +// A user error was found. Wait for the user to fix it. +void waitToFix(char const *msg) +{ + printf("**********\n"); + printf(msg); + printf("\n"); + printf("*** After fixing, "); + if (tty) + printf("restart allsky.sh.\n"); + else + printf("restart the allsky service.\n"); + if (notificationImages) + system("scripts/copy_notification_image.sh Error &"); + sleep(5); // give time for image to be copied + printf("*** Sleeping until you fix the problem.\n"); + printf("**********\n"); + sleep(100000); // basically, sleep forever until the user fixes this. } // Calculate if it is day or night @@ -93,17 +183,54 @@ void calculateDayOrNight(const char *latitude, const char *longitude, const char { char sunwaitCommand[128]; - // Log data - sprintf(sunwaitCommand, "sunwait poll exit set angle %s %s %s", angle, latitude, longitude); + // Log data. Don't need "exit" or "set". + sprintf(sunwaitCommand, "sunwait poll angle %s %s %s", angle, latitude, longitude); // Inform user - printf("Determine if it is day or night using variables: desired sun declination angle: %s degrees, latitude: %s, longitude: %s\n", angle, latitude, longitude); + sprintf(debugText, "Determine if it is day or night using variables: desired sun declination angle: %s degrees, latitude: %s, longitude: %s\n", angle, latitude, longitude); + displayDebugText(debugText, 1); // Determine if it is day or night dayOrNight = exec(sunwaitCommand); // RMu, I have no clue what this does... dayOrNight.erase(std::remove(dayOrNight.begin(), dayOrNight.end(), '\n'), dayOrNight.end()); + + if (dayOrNight != "DAY" && dayOrNight != "NIGHT") + { + sprintf(debugText, "*** ERROR: dayOrNight isn't DAY or NIGHT, it's '%s'\n", dayOrNight.c_str()); + waitToFix(debugText); + closeUp(2); + } +} + +// Calculate how long until nighttime. +int calculateTimeToNightTime(const char *latitude, const char *longitude, const char *angle) +{ + std::string t; + char sunwaitCommand[128]; // returns "hh:mm, hh:mm" (sunrise, sunset) + sprintf(sunwaitCommand, "sunwait list angle %s %s %s | awk '{print $2}'", angle, latitude, longitude); + t = exec(sunwaitCommand); + t.erase(std::remove(t.begin(), t.end(), '\n'), t.end()); + + int h=0, m=0, secs; + sscanf(t.c_str(), "%d:%d", &h, &m); + secs = (h*60*60) + (m*60); + + char *now = getTime("%H:%M"); + int hNow=0, mNow=0, secsNow; + sscanf(now, "%d:%d", &hNow, &mNow); + secsNow = (hNow*60*60) + (mNow*60); + + // Handle the (probably rare) case where nighttime is tomorrow + if (secsNow > secs) + { + return(secs + (60*60*24) - secsNow); + } + else + { + return(secs - secsNow); + } } // write value to log file @@ -120,7 +247,8 @@ void writeToLog(int val) // Build capture command to capture the image from the HQ camera void RPiHQcapture(int asiAutoFocus, int asiAutoExposure, int asiExposure, int asiAutoGain, int asiAutoAWB, double asiGain, int bin, double asiWBR, double asiWBB, int asiRotation, int asiFlip, int asiGamma, int asiBrightness, int quality, const char* fileName, int time, int showDetails, const char* ImgText, int fontsize, int fontcolor, int background, int darkframe) { - //printf ("capturing image in file %s\n", fileName); + sprintf(debugText, "capturing image in file %s\n", fileName); + displayDebugText(debugText, 3); // Ensure no rraspistill process is still running string kill = "ps -ef|grep raspistill| grep -v color|awk '{print $2}'|xargs kill -9 1> /dev/null 2>&1"; @@ -131,7 +259,8 @@ void RPiHQcapture(int asiAutoFocus, int asiAutoExposure, int asiExposure, int as // Convert command to character variable strcpy(kcmd, kill.c_str()); - //printf("Command: %s\n", cmd); + sprintf(debugText, "Command: %s\n", kcmd); + displayDebugText(debugText, 3); // Execute raspistill command system(kcmd); @@ -231,7 +360,7 @@ time ( NULL ); // Anolog Gain string gain; - // Check if auto gain is sleected + // Check if auto gain is selected if (asiAutoGain) { // Set analog gain to 1 @@ -453,18 +582,32 @@ time ( NULL ); // Convert command to character variable strcpy(cmd, command.c_str()); - printf("Capture command: %s\n", cmd); + sprintf(debugText, "Capture command: %s\n", cmd); + displayDebugText(debugText, 1); // Execute raspistill command - system(cmd); + if (system(cmd) == 0) numExposures++; } +// Simple function to make flags easier to read for humans. +char const *yes = "1 (yes)"; +char const *no = "0 (no)"; +char const *yesNo(int flag) +{ + if (flag) + return(yes); + else + return(no); +} + + //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- int main(int argc, char *argv[]) { signal(SIGINT, IntHandle); + signal(SIGTERM, IntHandle); // The service sends SIGTERM to end this program. /* int fontname[] = { CV_FONT_HERSHEY_SIMPLEX, CV_FONT_HERSHEY_PLAIN, CV_FONT_HERSHEY_DUPLEX, CV_FONT_HERSHEY_COMPLEX, CV_FONT_HERSHEY_TRIPLEX, CV_FONT_HERSHEY_COMPLEX_SMALL, @@ -492,24 +635,37 @@ int main(int argc, char *argv[]) */ int width = 0; int height = 0; - int bin = 2; - int asiExposure = 60000000; - int asiAutoExposure = 0; + int dayBin = 1; + int nightBin = 2; + int currentBin = NOT_SET; + int asiDayExposure = 32; // milliseconds + int asiNightExposure = 60000000; + int currentExposure = NOT_SET; + int asiNightAutoExposure = 0; + int asiDayAutoExposure= 1; + int currentAutoExposure = 0; int asiAutoFocus = 0; - double asiGain = 4; - int asiAutoGain = 0; + double asiNightGain = 4; + double asiDayGain = 1; + double currentGain = NOT_SET; + int asiNightAutoGain = 0; + int asiDayAutoGain = 0; + int currentAutoGain = NOT_SET; int asiAutoAWB = 0; - int delay = 10; // Delay in milliseconds. Default is 10ms - int daytimeDelay = 15000; // Delay in milliseconds. Default is 15 seconds + int nightDelay = 10; // Delay in milliseconds. Default is 10ms + int dayDelay = 15000; // Delay in milliseconds. Default is 15 seconds + int currentDelay = NOT_SET; double asiWBR = 2.5; double asiWBB = 2; int asiGamma = 50; - int asiBrightness = 50; + int asiDayBrightness = 50; + int asiNightBrightness= 50; + int currentBrightness = NOT_SET; int asiFlip = 0; int asiRotation = 0; char const *latitude = "52.57N"; //GPS Coordinates of Limmen, Netherlands where this code was altered char const *longitude = "4.70E"; - char const *angle = "0"; // angle of the sun with the horizon (0=sunset, -6=civil twilight, -12=nautical twilight, -18=astronomical twilight) + char const *angle = "0"; // angle of the sun with the horizon (0=sunset, -6=civil twilight, -12=nautical twilight, -18=astronomical twilight) //int preview = 0; int time = 0; int showDetails = 0; @@ -525,13 +681,14 @@ int main(int argc, char *argv[]) //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- + setlinebuf(stdout); // Line buffer output so entries appear in the log immediately. printf("\n"); printf("%s ******************************************\n", KGRN); - printf("%s *** Allsky Camera Software v0.6 | 2019 ***\n", KGRN); + printf("%s *** Allsky Camera Software v0.8 | 2021 ***\n", KGRN); printf("%s ******************************************\n\n", KGRN); printf("\%sCapture images of the sky with a Raspberry Pi and an ZWO ASI or RPi HQ camera\n", KGRN); printf("\n"); - printf("%sAdd -h or -help for available options \n", KYEL); + printf("%sAdd -h or -help for available options\n", KYEL); printf("\n"); printf("\%sAuthor: ", KNRM); printf("Thomas Jacquin - \n\n"); @@ -543,298 +700,306 @@ int main(int argc, char *argv[]) printf("-Michael J. Kidd - \n"); printf("-Rob Musquetier\n\n"); + // The newer "allsky.sh" puts quotes around arguments so we can have spaces in them. + // If you are running the old allsky.sh, set this to false: + bool argumentsQuoted = true; + if (argc > 0) { - // printf("Found %d parameters...\n", argc - 1); + sprintf(debugText, "Found %d parameters...\n", argc - 1); + displayDebugText(debugText, 3); + + // -h[elp] doesn't take an argument, but the "for" loop assumes every option does, + // so check separately, assuming the option is the first one. + // If it's not the first option, we'll find it in the "for" loop. + if (strcmp(argv[0], "-h") == 0 || strcmp(argv[0], "-help") == 0) + { + help = 1; + i = 1; + } + else + { + i = 0; + } for (i = 0; i < argc - 1; i++) { - // printf("Processing argument: %s\n\n", argv[i]); + sprintf(debugText, "Processing argument: %s\n\n", argv[i]); + displayDebugText(debugText, 3); + // Check again in case "-h" isn't the first option. if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-help") == 0) { - help = atoi(argv[i + 1]); - i++; + help = 1; } else if (strcmp(argv[i], "-width") == 0) { - width = atoi(argv[i + 1]); - i++; + width = atoi(argv[++i]); } else if (strcmp(argv[i], "-height") == 0) { - height = atoi(argv[i + 1]); - i++; + height = atoi(argv[++i]); } /* else if (strcmp(argv[i], "-type") == 0) { - Image_type = atoi(argv[i + 1]); - i++; + Image_type = atoi(argv[++i]); } */ else if (strcmp(argv[i], "-quality") == 0) { - quality = atoi(argv[i + 1]); - i++; + quality = atoi(argv[++i]); } - else if (strcmp(argv[i], "-exposure") == 0) + // check for old names as well - the "||" part is the old name + else if (strcmp(argv[i], "-nightexposure") == 0 || strcmp(argv[i], "-exposure") == 0) { - asiExposure = atoi(argv[i + 1]) * 1000; - i++; + asiNightExposure = atoi(argv[++i]) * US_IN_MS; } - else if (strcmp(argv[i], "-autoexposure") == 0) + else if (strcmp(argv[i], "-nightautoexposure") == 0 || strcmp(argv[i], "-autoexposure") == 0) { - asiAutoExposure = atoi(argv[i + 1]); - i++; + asiNightAutoExposure = atoi(argv[++i]); } else if (strcmp(argv[i], "-autofocus") == 0) { - asiAutoFocus = atoi(argv[i + 1]); - i++; + asiAutoFocus = atoi(argv[++i]); } - else if (strcmp(argv[i], "-gain") == 0) + // xxxx Day gain isn't settable by the user. Should it be? + else if (strcmp(argv[i], "-nightgain") == 0 || strcmp(argv[i], "-gain") == 0) { - asiGain = atof(argv[i + 1]); - i++; + asiNightGain = atof(argv[++i]); } - else if (strcmp(argv[i], "-autogain") == 0) + else if (strcmp(argv[i], "-nightautogain") == 0 || strcmp(argv[i], "-autogain") == 0) { - asiAutoGain = atoi(argv[i + 1]); - i++; + asiNightAutoGain = atoi(argv[++i]); } else if (strcmp(argv[i], "-gamma") == 0) { - asiGamma = atoi(argv[i + 1]); - i++; + asiGamma = atoi(argv[++i]); } - else if (strcmp(argv[i], "-brightness") == 0) + else if (strcmp(argv[i], "-brightness") == 0)// old "-brightness" applied to day and night { - asiBrightness = atoi(argv[i + 1]); - i++; + asiDayBrightness = atoi(argv[++i]); + asiNightBrightness = asiDayBrightness; } - else if (strcmp(argv[i], "-bin") == 0) + else if (strcmp(argv[i], "-daybrightness") == 0) { - bin = atoi(argv[i + 1]); - i++; + asiDayBrightness = atoi(argv[++i]); } - else if (strcmp(argv[i], "-delay") == 0) + else if (strcmp(argv[i], "-nightbrightness") == 0) { - delay = atoi(argv[i + 1]); - i++; + asiNightBrightness = atoi(argv[++i]); } - else if (strcmp(argv[i], "-daytimeDelay") == 0) + else if (strcmp(argv[i], "-daybin") == 0) + { + dayBin = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-nightbin") == 0 || strcmp(argv[i], "-bin") == 0) { - daytimeDelay = atoi(argv[i + 1]); - i++; + nightBin = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-nightdelay") == 0 || strcmp(argv[i], "-delay") == 0) + { + nightDelay = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-daydelay") == 0 || strcmp(argv[i], "-daytimeDelay") == 0) + { + dayDelay = atoi(argv[++i]); } - else if (strcmp(argv[i], "-awb") == 0) { - asiAutoAWB = atoi(argv[i + 1]); - i++; + asiAutoAWB = atoi(argv[++i]); } - else if (strcmp(argv[i], "-wbr") == 0) { - asiWBR = atof(argv[i + 1]); - i++; + asiWBR = atof(argv[++i]); } else if (strcmp(argv[i], "-wbb") == 0) { - asiWBB = atof(argv[i + 1]); - i++; + asiWBB = atof(argv[++i]); } // Check for text parameter else if (strcmp(argv[i], "-text") == 0) { - // Get first param - param = argv[i + 1]; - - // Space character - const char *space = " "; + if (argumentsQuoted) + { + ImgText = argv[++i]; + } + else + { + // Get first param + param = argv[i + 1]; - // Temporary text buffer - char buffer[1024]; // <- danger, only storage for 1024 characters. + // Space character + const char *space = " "; - // First word flag - int j = 0; + // Temporary text buffer + char buffer[1024]; // <- danger, only storage for 1024 characters. - // Loop while next parameter doesn't start with a - character - while (strncmp(param, "-", 1) != 0) - { - // Copy Text into buffer - strncpy(buffer, ImgText, sizeof(buffer)); + // First word flag + int j = 0; - // Add a space after each word (skip for first word) - if (j) - strncat(buffer, space, sizeof(buffer)); + // Loop while next parameter doesn't start with a - character + while (strncmp(param, "-", 1) != 0) + { + // Copy Text into buffer + strncpy(buffer, ImgText, sizeof(buffer)); - // Add parameter - strncat(buffer, param, sizeof(buffer)); + // Add a space after each word (skip for first word) + if (j) + strncat(buffer, space, sizeof(buffer)); - // Copy buffer into ImgText variable - ImgText = buffer; + // Add parameter + strncat(buffer, param, sizeof(buffer)); - // Increase parameter counter - i++; + // Copy buffer into ImgText variable + ImgText = buffer; - // Flag first word is entered - j = 1; + // Flag first word is entered + j = 1; - // Get next parameter - param = argv[i + 1]; + // Get next parameter + param = argv[++i]; + } } } /* else if (strcmp(argv[i], "-textx") == 0) { - iTextX = atoi(argv[i + 1]); - i++; + iTextX = atoi(argv[++i]); } else if (strcmp(argv[i], "-texty") == 0) { - iTextY = atoi(argv[i + 1]); - i++; + iTextY = atoi(argv[++i]); } else if (strcmp(argv[i], "-fontname") == 0) { - fontnumber = atoi(argv[i + 1]); - i++; + fontnumber = atoi(argv[++i]); } */ else if (strcmp(argv[i], "-background") == 0) { - background = atoi(argv[i + 1]); - i++; + background = atoi(argv[++i]); } else if (strcmp(argv[i], "-fontcolor") == 0) { - fontcolor = atoi(argv[i + 1]); - i++; + fontcolor = atoi(argv[++i]); } /* else if (strcmp(argv[i], "-smallfontcolor") == 0) { - smallFontcolor[0] = atoi(argv[i + 1]); - i++; - smallFontcolor[1] = atoi(argv[i + 1]); - i++; - smallFontcolor[2] = atoi(argv[i + 1]); - i++; + if (argumentsQuoted) + { + sscanf(argv[++i], "%d %d %d", &smallFontcolor[0], &smallFontcolor[1], &smallFontcolor[2]); + } + else + { + smallFontcolor[0] = atoi(argv[++i]); + smallFontcolor[1] = atoi(argv[++i]); + smallFontcolor[2] = atoi(argv[++i]); + } } else if (strcmp(argv[i], "-fonttype") == 0) { - linenumber = atoi(argv[i + 1]); - i++; + linenumber = atoi(argv[++i]); } */ else if (strcmp(argv[i], "-fontsize") == 0) { - fontsize = atof(argv[i + 1]); - i++; + fontsize = atof(argv[++i]); } /* else if (strcmp(argv[i], "-fontline") == 0) { - linewidth = atoi(argv[i + 1]); - i++; + linewidth = atoi(argv[++i]); } else if (strcmp(argv[i], "-outlinefont") == 0) { - outlinefont = atoi(argv[i + 1]); + outlinefont = atoi(argv[++i]); if (outlinefont != 0) outlinefont = 1; - i++; } */ else if (strcmp(argv[i], "-rotation") == 0) { - asiRotation = atoi(argv[i + 1]); - i++; + asiRotation = atoi(argv[++i]); } else if (strcmp(argv[i], "-flip") == 0) { - asiFlip = atoi(argv[i + 1]); - i++; + asiFlip = atoi(argv[++i]); } else if (strcmp(argv[i], "-filename") == 0) { - fileName = (argv[i + 1]); - i++; + fileName = (argv[++i]); } else if (strcmp(argv[i], "-latitude") == 0) { - latitude = argv[i + 1]; - i++; + latitude = argv[++i]; } else if (strcmp(argv[i], "-longitude") == 0) { - longitude = argv[i + 1]; - i++; + longitude = argv[++i]; } else if (strcmp(argv[i], "-angle") == 0) { - angle = argv[i + 1]; - i++; + angle = argv[++i]; } /* else if (strcmp(argv[i], "-preview") == 0) { - preview = atoi(argv[i + 1]); - i++; + preview = atoi(argv[++i]); } */ - else if (strcmp(argv[i], "-time") == 0) + else if (strcmp(argv[i], "-showTime") == 0 || strcmp(argv[i], "-time") == 0) { - time = atoi(argv[i + 1]); - i++; + time = atoi(argv[++i]); } else if (strcmp(argv[i], "-darkframe") == 0) { - darkframe = atoi(argv[i + 1]); - i++; + darkframe = atoi(argv[++i]); } else if (strcmp(argv[i], "-showDetails") == 0) { - showDetails = atoi(argv[i + 1]); - i++; + showDetails = atoi(argv[++i]); } else if (strcmp(argv[i], "-daytime") == 0) { - daytimeCapture = atoi(argv[i + 1]); - i++; + daytimeCapture = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-notificationimages") == 0) + { + notificationImages = atoi(argv[++i]); + } + else if (strcmp(argv[i], "-tty") == 0) + { + tty = atoi(argv[++i]); } } } - // Save the status of Auto Gain for night exposure - int oldAutoExposure = asiAutoExposure; - int oldGain = asiGain; - if (help == 1) { - printf("%sAvailable Arguments: \n", KYEL); - printf(" -width - Default = Camera Max Width \n"); - printf(" -height - Default = Camera Max Height \n"); - printf(" -exposure - Default = 5000000 - Time in µs (equals to 5 sec) \n"); - printf(" -autoexposure - Default = 0 - Set to 1 to enable auto Exposure \n"); - printf(" -autofocus - Default = 0 - Set to 1 to enable auto Focus \n"); - printf(" -gain - Default = 1 (1 - 16) \n"); - printf(" -autogain - Default = 0 - Set to 1 to enable auto Gain \n"); + printf("%sAvailable Arguments:\n", KYEL); + printf(" -width - Default = Camera Max Width\n"); + printf(" -height - Default = Camera Max Height\n"); + printf(" -nightexposure - Default = 5000000 - Time in µs (equals to 5 sec)\n"); + printf(" -nightautoexposure - Default = 0 - Set to 1 to enable auto Exposure\n"); + printf(" -autofocus - Default = 0 - Set to 1 to enable auto Focus\n"); + printf(" -nightgain - Default = 1 (1 - 16)\n"); + printf(" -nightautogain - Default = 0 - Set to 1 to enable auto Gain at night\n"); printf(" -gamma - Default = 50 (-100 till 100)\n"); - printf(" -brightness - Default = 50 (0 till 100) \n"); + printf(" -brightness - Default = 50 (0 till 100)\n"); printf(" -awb - Default = 0 - Auto White Balance (0 = off)\n"); printf(" -wbr - Default = 2 - White Balance Red (0 = off)\n"); printf(" -wbb - Default = 2 - White Balance Blue (0 = off)\n"); - printf(" -bin - Default = 1 - binning OFF (1x1), 2 = 2x2, 3 = 3x3 binning\n"); - printf(" -delay - Default = 10 - Delay between images in milliseconds - 1000 = 1 sec.\n"); - printf(" -daytimeDelay - Default = 5000 - Delay between images in milliseconds - 5000 = 5 sec.\n"); - printf(" -type = Image Type - Default = 0 - 0 = RAW8, 1 = RGB24, 2 = RAW16 \n"); + printf(" -daybin - Default = 1 - binning OFF (1x1), 2 = 2x2, 3 = 3x3 binning\n"); + printf(" -nightbin - Default = 1 - same as -daybin but for nighttime\n"); + printf(" -nightdelay - Default = 10 - Delay between images in milliseconds - %d = 1 sec.\n", MS_IN_SEC); + printf(" -daydelay - Default = 5000 - Delay between images in milliseconds - 5000 = 5 sec.\n"); + printf(" -type = Image Type - Default = 0 - 0 = RAW8, 1 = RGB24, 2 = RAW16\n"); printf(" -quality - Default = 70%%, 0%% (poor) 100%% (perfect)\n"); printf(" -filename - Default = image.jpg\n"); printf(" -rotation - Default = 0 degrees - Options 0, 90, 180 or 270\n"); @@ -842,23 +1007,15 @@ int main(int argc, char *argv[]) printf("\n"); printf(" -text - Default = - Character/Text Overlay. Use Quotes. Ex. -c " "\"Text Overlay\"\n"); -/* - printf( - " -textx - Default = 15 - Text Placement Horizontal from LEFT in Pixels\n"); - printf(" -texty = Text Y - Default = 25 - Text Placement Vertical from TOP in Pixels\n"); - printf(" -fontname = Font Name - Default = 0 - Font Types (0-7), Ex. 0 = simplex, 4 = triplex, " - "7 = script\n"); -*/ +// printf(" -textx - Default = 15 - Text Placement Horizontal from LEFT in Pixels\n"); +// printf(" -texty = Text Y - Default = 25 - Text Placement Vertical from TOP in Pixels\n"); +// printf(" -fontname = Font Name - Default = 0 - Font Types (0-7), Ex. 0 = simplex, 4 = triplex, 7 = script\n"); printf(" -fontcolor = Font Color - Default = 255 - Text gray scale color (0 - 255)\n"); printf(" -background= Font Color - Default = 0 - Backgroud gray scale color (0 - 255)\n"); -/* - printf(" -smallfontcolor = Small Font Color - Default = 0 0 255 - Text red (BGR)\n"); - printf(" -fonttype = Font Type - Default = 0 - Font Line Type,(0-2), 0 = AA, 1 = 8, 2 = 4\n"); -*/ +// printf(" -smallfontcolor = Small Font Color - Default = 0 0 255 - Text red (BGR)\n"); +// printf(" -fonttype = Font Type - Default = 0 - Font Line Type,(0-2), 0 = AA, 1 = 8, 2 = 4\n"); printf(" -fontsize - Default = 32 - Text Font Size (range 6 - 160, 32 default)\n"); -/* - printf(" -fontline - Default = 1 - Text Font Line Thickness\n"); -*/ +// printf(" -fontline - Default = 1 - Text Font Line Thickness\n"); printf("\n"); printf("\n"); printf(" -latitude - Default = 60.7N (Whitehorse) - Latitude of the camera.\n"); @@ -866,13 +1023,15 @@ int main(int argc, char *argv[]) printf(" -angle - Default = -6 - Angle of the sun below the horizon. -6=civil " "twilight, -12=nautical twilight, -18=astronomical twilight\n"); printf("\n"); - // printf(" -preview - set to 1 to preview the captured images. Only works with a Desktop Environment \n"); + // printf(" -preview - set to 1 to preview the captured images. Only works with a Desktop Environment\n"); printf(" -time - Adds the time to the image.\n"); - printf(" -darkframe - Set to 1 to grab dark frame and cover your camera \n"); - printf(" -showDetails - Set to 1 to display the metadata on the image \n"); + printf(" -darkframe - Set to 1 to grab dark frame and cover your camera\n"); + printf(" -showDetails - Set to 1 to display the metadata on the image\n"); + printf(" -notificationimages - Set to 1 to enable notification images, for example, 'Camera is off during day'.\n"); + printf(" -debuglevel - Default = 0. Set to 1,2 or 3 for more debugging information.\n"); printf("%sUsage:\n", KRED); - printf(" ./capture -width 640 -height 480 -exposure 5000000 -gamma 50 -bin 1 -filename Lake-Laberge.JPG\n\n"); + printf(" ./capture_RPiHQ -width 640 -height 480 -nightexposure 5000000 -gamma 50 -nightbin 1 -filename Lake-Laberge.JPG\n\n"); } printf("%s", KNRM); @@ -882,64 +1041,59 @@ int main(int argc, char *argv[]) double pixelSize = 1.55; printf("- Resolution: %dx%d\n", iMaxWidth, iMaxHeight); - printf("- Pixel Size: %1.2fμm\n", pixelSize); + printf("- Pixel Size: %1.2fμm\n", pixelSize); printf("- Supported Bin: 1x, 2x and 3x\n"); - // Adjusting variables for chosen binning - width = iMaxWidth / bin; - height = iMaxHeight / bin; - - //iTextX = iTextX / bin; - //iTextY = iTextY / bin; - //fontsize = fontsize / bin; - //linewidth = linewidth / bin; + if (darkframe) + { + // To avoid overwriting the optional notification inage with the dark image, + // during dark frames we use a different file name. + fileName = "dark.jpg"; + } //------------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------------- printf("%s", KGRN); - printf("\nCapture Settings: \n"); - printf(" Resolution: %dx%d \n", width, height); - printf(" Quality: %d \n", quality); - printf(" Exposure: %1.0fms\n", round(asiExposure / 1000)); - printf(" Auto Exposure: %d\n", asiAutoExposure); - printf(" Auto Focus: %d\n", asiAutoFocus); - printf(" Gain: %1.2f\n", asiGain); - printf(" Auto Gain: %d\n", asiAutoGain); - printf(" Brightness: %d\n", asiBrightness); + printf("\nCapture Settings:\n"); + printf(" Resolution (before any binning): %dx%d\n", width, height); + printf(" Quality: %d\n", quality); + printf(" Exposure (night): %1.0fms\n", round(asiNightExposure / US_IN_MS)); + printf(" Auto Exposure (night): %s\n", yesNo(asiNightAutoExposure)); + printf(" Auto Focus: %s\n", yesNo(asiAutoFocus)); + printf(" Gain (night): %1.2f\n", asiNightGain); + printf(" Auto Gain (night): %s\n", yesNo(asiNightAutoGain)); + printf(" Brightness (day): %d\n", asiDayBrightness); + printf(" Brightness (night): %d\n", asiNightBrightness); printf(" Gamma: %d\n", asiGamma); - printf(" Auto White Balance: %d\n", asiAutoAWB); + printf(" Auto White Balance: %s\n", yesNo(asiAutoAWB)); printf(" WB Red: %1.2f\n", asiWBR); printf(" WB Blue: %1.2f\n", asiWBB); - printf(" Binning: %d\n", bin); - printf(" Delay: %dms\n", delay); - printf(" Daytime Delay: %dms\n", daytimeDelay); + printf(" Binning (day): %d\n", dayBin); + printf(" Binning (night): %d\n", nightBin); + printf(" Delay (day): %dms\n", dayDelay); + printf(" Delay (night): %dms\n", nightDelay); printf(" Text Overlay: %s\n", ImgText); -/* - printf(" Text Position: %dpx left, %dpx top\n", iTextX, iTextY); - printf(" Font Name: %d\n", fontname[fontnumber]); -*/ - printf(" Font Color: %d\n", fontcolor); - printf(" Font Background Color: %d\n", background); -/* - printf(" Small Font Color: %d , %d, %d\n", smallFontcolor[0], smallFontcolor[1], smallFontcolor[2]); - printf(" Font Line Type: %d\n", linetype[linenumber]); -*/ - printf(" Font Size: %1.1f\n", fontsize); -/* - printf(" Font Line: %d\n", linewidth); - printf(" Outline Font : %d\n", outlinefont); -*/ +// printf(" Text Position: %dpx left, %dpx top\n", iTextX, iTextY); +// printf(" Font Name: %d\n", fontname[fontnumber]); + printf(" Font Color: %d\n", fontcolor); + printf(" Font Background Color: %d\n", background); +// printf(" Small Font Color: %d , %d, %d\n", smallFontcolor[0], smallFontcolor[1], smallFontcolor[2]); +// printf(" Font Line Type: %d\n", linetype[linenumber]); + printf(" Font Size: %1.1f\n", fontsize); +// printf(" Font Line: %d\n", linewidth); +// printf(" Outline Font : %s\n", yesNo(outlinefont)); printf(" Rotation: %d\n", asiRotation); printf(" Flip Image: %d\n", asiFlip); printf(" Filename: %s\n", fileName); printf(" Latitude: %s\n", latitude); printf(" Longitude: %s\n", longitude); printf(" Sun Elevation: %s\n", angle); - // printf(" Preview: %d\n", preview); - printf(" Time: %d\n", time); - printf(" Show Details: %d\n", showDetails); - printf(" Darkframe: %d\n", darkframe); + // printf(" Preview: %s\n", yesNo(preview)); + printf(" Time: %s\n", yesNo(time)); + printf(" Show Details: %s\n", yesNo(showDetails)); + printf(" Darkframe: %s\n", yesNo(darkframe)); + printf(" Notification Images: %s\n", yesNo(notificationImages)); // Show selected camera type printf(" Camera: Raspberry Pi HQ camera\n"); @@ -947,18 +1101,13 @@ int main(int argc, char *argv[]) printf("%s", KNRM); // Initialization - int currentExposure = asiExposure; - int exp_ms = 0; - int useDelay = 0; - bool needCapture = true; std::string lastDayOrNight; + int displayedNoDaytimeMsg = 0; // Have we displayed "not taking picture during day" message, if applicable? while (bMain) { printf("\n"); - needCapture = true; - // Find out if it is currently DAY or NIGHT calculateDayOrNight(latitude, longitude, angle); @@ -968,30 +1117,39 @@ int main(int argc, char *argv[]) lastDayOrNight = dayOrNight; // Next lines are present for testing purposes -// printf("Daytimecapture: %d\n", daytimeCapture); - - if (dayOrNight=="DAY") - printf("Check for day or night: DAY\n"); - else if (dayOrNight=="NIGHT") - printf("Check for day or night: NIGHT\n"); - else - printf("Nor day or night...\n"); +sprintf(debugText, "Daytimecapture: %d\n", daytimeCapture); +displayDebugText(debugText, 3); printf("\n"); - if (dayOrNight == "DAY") + if (darkframe) { - // Switch auto gain on - asiAutoExposure = 1; - asiGain = 1; + // We're doing dark frames so turn off autoexposure and autogain, and use + // nightime gain, delay, exposure, and brightness to mimic a nightime shot. + currentAutoExposure = 0; + currentAutoGain = 0; + currentGain = asiNightGain; + currentDelay = nightDelay; + currentExposure = asiNightExposure; + currentBrightness = asiNightBrightness; + currentBin = nightBin; + + displayDebugText("Taking dark frames...\n", 0); + if (notificationImages) { + system("scripts/copy_notification_image.sh DarkFrames &"); + } + } - // Execute end of night script - if (endOfNight == true) + else if (dayOrNight == "DAY") + { + if (endOfNight == true) // Execute end of night script { system("scripts/endOfNight.sh &"); // Reset end of night indicator endOfNight = false; + + displayedNoDaytimeMsg = 0; } // Next line is present for testing purposes @@ -1000,154 +1158,176 @@ int main(int argc, char *argv[]) // Check if images should not be captured during day-time if (daytimeCapture != 1) { - // Indicate no images need capturing - needCapture = false; - - // Inform user - printf("It's daytime... we're not saving images\n"); + // Only display messages once a day. + if (displayedNoDaytimeMsg == 0) { + if (notificationImages) { + system("scripts/copy_notification_image.sh CameraOffDuringDay &"); + } + sprintf(debugText, "It's daytime... we're not saving images.\n%s\n", + tty ? "Press Ctrl+C to stop" : "Stop the allsky service to end this process."); + displayDebugText(debugText, 0); + displayedNoDaytimeMsg = 1; + + // sleep until almost nighttime, then wake up and sleep a short time + int secsTillNight = calculateTimeToNightTime(latitude, longitude, angle); + sleep(secsTillNight - 10); + } + else + { + // Shouldn't need to sleep more than a few times before nighttime. + sleep(5); + } - // Sleep for a while - usleep(daytimeDelay * 1000); + // No need to do any of the code below so go back to the main loop. + continue; } // Images should be captured during day-time else { // Inform user - printf("Starting daytime capture\n"); - - // Set exposure to 32 ms - exp_ms = 32; + char const *x; + if (numExposures > 0) // so it's easier to see in log file + x = "\n==========\n"; + else + x = ""; + sprintf(debugText, "%s=== Starting daytime capture ===\n%s", x, x); + displayDebugText(debugText, 0); + + // set daytime settings + currentAutoExposure = asiDayAutoExposure; + currentAutoGain = asiDayAutoGain; + currentGain = asiDayGain; + currentDelay = dayDelay; + currentExposure = asiDayExposure; + currentBrightness = asiDayBrightness; + currentBin = dayBin; // Inform user - printf("Saving %d ms exposed images with %d seconds delays in between...\n\n", exp_ms, daytimeDelay / 1000); - - // Set delay time - useDelay = daytimeDelay; - - // Set exposure time - currentExposure = exp_ms * 1000; + sprintf(debugText, "Saving %d ms exposed images with %d seconds delays in between...\n\n", currentExposure * US_IN_MS, currentDelay / MS_IN_SEC); + displayDebugText(debugText, 0); } } - // Check for night time - else if (dayOrNight == "NIGHT") + else // NIGHT { - // Retrieve auto gain setting - asiAutoExposure = oldAutoExposure; - asiGain = oldGain; + char const *x; + if (numExposures > 0) // so it's easier to see in log file + x = "\n==========\n"; + else + x = ""; + sprintf(debugText, "%s=== Starting nighttime capture ===\n%s", x, x); + displayDebugText(debugText, 0); + + // Set nighttime settings + currentAutoExposure = asiNightAutoExposure; + currentAutoGain = asiNightAutoGain; + currentGain = asiNightGain; + currentDelay = nightDelay; + currentExposure = asiNightExposure; + currentBrightness = asiNightBrightness; + currentBin = nightBin; // Inform user - printf("Saving %d seconds exposure images with %d ms delays in between...\n\n", (int)round(currentExposure / 1000000), delay); - - // Set exposure value for night time capture - useDelay = delay; - - currentExposure = asiExposure; + sprintf(debugText, "Saving %d seconds exposure images with %d ms delays in between...\n\n", (int)round(currentExposure / US_IN_SEC), nightDelay); + displayDebugText(debugText, 0); } + // Adjusting variables for chosen binning + width = iMaxWidth / currentBin; + height = iMaxHeight / currentBin; +// iTextX = iTextX / currentBin; +// iTextY = iTextY / currentBin; +// fontsize = fontsize / currentBin; +// linewidth = linewidth / currentBin; + // Inform user - printf("Press Ctrl+Z to stop capturing images...\n\n"); + if (tty) + printf("Press Ctrl+Z to stop\n\n"); // xxx ECC: Ctrl-Z stops a process, it doesn't kill it + else + printf("Stop the allsky service to end this process.\n\n"); - // check if images should be captured - if (needCapture) + // Wait for switch day time -> night time or night time -> day time + while (bMain && lastDayOrNight == dayOrNight) { - // Wait for switch day time -> night time or night time -> day time - while (bMain && lastDayOrNight == dayOrNight) - { - // Inform user - printf("Capturing & saving image...\n"); - - // Capture and save image - RPiHQcapture(asiAutoFocus, asiAutoExposure, currentExposure, asiAutoGain, asiAutoAWB, asiGain, bin, asiWBR, asiWBB, asiRotation, asiFlip, asiGamma, asiBrightness, quality, fileName, time, showDetails, ImgText, fontsize, fontcolor, background, darkframe); - - // Check if no processing is going on - if (!bSavingImg) - { - // Flag processing is on-going - bSavingImg = true; + // Inform user + sprintf(debugText, "Capturing & saving image...\n"); + displayDebugText(debugText, 0); - // Check for night time - if (dayOrNight == "NIGHT") - { - // Preserve image during night time - system("scripts/saveImageNight.sh &"); - } - else - { - // Upload and resize image when configured - system("scripts/saveImageDay.sh &"); - } + // Capture and save image + RPiHQcapture(asiAutoFocus, currentAutoExposure, currentExposure, currentAutoGain, asiAutoAWB, currentGain, currentBin, asiWBR, asiWBB, asiRotation, asiFlip, asiGamma, currentBrightness, quality, fileName, time, showDetails, ImgText, fontsize, fontcolor, background, darkframe); - // Flag processing is over - bSavingImg = false; - } + // Check for night time + if (dayOrNight == "NIGHT") + { + // Preserve image during night time + system("scripts/saveImageNight.sh &"); + } + else + { + // Upload and resize image when configured + system("scripts/saveImageDay.sh &"); + } - // Inform user - printf("Capturing & saving image done, now wait %d seconds...\n", useDelay / 1000); + // Inform user + sprintf(debugText, "Capturing & saving %s done, now wait %d seconds...\n", darkframe ? "dark frame" : "image", currentDelay / MS_IN_SEC); + displayDebugText(debugText, 0); - // Sleep for a moment - usleep(useDelay * 1000); + // Sleep for a moment + usleep(currentDelay * US_IN_SEC); - // Check for day or night based on location and angle - calculateDayOrNight(latitude, longitude, angle); + // Check for day or night based on location and angle + calculateDayOrNight(latitude, longitude, angle); // Next line is present for testing purposes // dayOrNight.assign("NIGHT"); - // Check if it is day time - if (dayOrNight=="DAY") + // ECC: why bother with the check below for DAY/NIGHT? + // Check if it is day time + if (dayOrNight=="DAY") + { + // Check started capturing during day time + if (lastDayOrNight=="DAY") { - // Check started capturing during day time - if (lastDayOrNight=="DAY") - { - printf("Check for day or night: DAY (waiting for changing DAY into NIGHT)...\n"); - } - - // Started capturing during night time - else - { - printf("Check for day or night: DAY (waiting for changing NIGHT into DAY)...\n"); - } + sprintf(debugText, "Check for day or night: DAY (waiting for changing DAY into NIGHT)...\n"); + displayDebugText(debugText, 2); } - // Check if it is night time - else if (dayOrNight=="NIGHT") + // Started capturing during night time + else { - // Check started capturing during day time - if (lastDayOrNight=="DAY") - { - printf("Check for day or night: NIGHT (waiting for changing DAY into NIGHT)...\n"); - } + sprintf(debugText, "Check for day or night: DAY (waiting for changing NIGHT into DAY)...\n"); + displayDebugText(debugText, 2); + } + } - // Started capturing during night time - else - { - printf("Check for day or night: NIGHT (waiting for changing NIGHT into DAY)...\n"); - } + else // NIGHT + { + // Check started capturing during day time + if (lastDayOrNight=="DAY") + { + sprintf(debugText, "Check for day or night: NIGHT (waiting for changing DAY into NIGHT)...\n"); + displayDebugText(debugText, 2); } - // Unclear if it is day or night + // Started capturing during night time else { - printf("Nor day or night...\n"); + sprintf(debugText, "Check for day or night: NIGHT (waiting for changing NIGHT into DAY)...\n"); + displayDebugText(debugText, 2); } - - printf("\n"); } - // Check for night situation - if (lastDayOrNight == "NIGHT") - { - // Flag end of night processing is needed - endOfNight = true; - } + printf("\n"); } - } - // Stop script - printf("main function over\n"); + // Check for night situation + if (lastDayOrNight == "NIGHT") + { + // Flag end of night processing is needed + endOfNight = true; + } + } - // Return all is well - return 1; + closeUp(0); } diff --git a/config.sh.repo b/config.sh.repo index 9c3fb53ac..3dc054e9e 100644 --- a/config.sh.repo +++ b/config.sh.repo @@ -19,6 +19,9 @@ TIMELAPSE=true TIMELAPSEWIDTH=0 TIMELAPSEHEIGHT=0 +# Set the bitrate of the timelapse video. Higher numbers will produce higher quality but larger files. +TIMELAPSE_BITRATE="2000k" + # Timelapse frame rate (number of frames per second) FPS=25 @@ -47,7 +50,7 @@ NIGHTS_TO_KEEP=14 DARK_FRAME_SUBTRACTION=false # Set to 0 to disable Daytime Capture -DAYTIME="1" +DAYTIME=1 # Set 24Hr capture to true to save both night and day images to disk. By default, only night images are saved. CAPTURE_24HR=false @@ -69,6 +72,44 @@ AUTO_STRETCH=false AUTO_STRETCH_AMOUNT=10 AUTO_STRETCH_MID_POINT=10% +# Resize uploaded images. Change the size to fit your sensor. +RESIZE_UPLOADS=false +RESIZE_UPLOADS_SIZE=962x720 + +# Size of thumbnails +THUMBNAIL_SIZE_X=100 +THUMBNAIL_SIZE_Y=75 + +# Scan for, and remove corrupt images before generating keograms and startrails. +# This can take several (tens of) minutes to run and isn't necessary unless your system produces +# corrupt images which then generate funny colors in the summary images... +REMOVE_BAD_IMAGES=false +# Images whose mean brightness is less than THRESHOLD_LOW or over THRESHOLD_HIGH +# percent (max: 100) will be removed. Set to 0 to disable the brightness checks. +REMOVE_BAD_IMAGES_THRESHOLD_LOW=1; export REMOVE_BAD_IMAGES_THRESHOLD_LOW +REMOVE_BAD_IMAGES_THRESHOLD_HIGH=90; export REMOVE_BAD_IMAGES_THRESHOLD_HIGH + +# The uhubctl command can reset the USB bus if the camera isn't found and you know it's there. +# Allsky.sh uses this to often fix "missing" cameras. Specify the path to the command and the USB bus number +# (on a Pi 4, bus 1 is USB 2.0 and bus 2 is the USB 3.0 ports). +# If you don't have 'uhubctl' installed set UHUBCTL_PATH to "". +UHUBCTL_PATH="" +UHUBCTL_PORT=2 + +# IMG_DIR is the location of the image that the websites use. +# It must have nothing after the trailing double quote (i.e., no comments). +# "allsky" is /var/www/html/allsky/. "current" is /home/pi/allsky. +# If you use "current" and have the website, prepend "/current/" to the "imageName" in /var/www/html/allsky/config.js, +# for example: imageName: "/current/liveview-image.jpg", +# Useing "current" avoids copying the image to the website. +IMG_DIR="current" + +# Set an optional prefix on the website image file name, before "$FILENAME.$EXTENSION". +# This must match what the local web site thinks the name is (see /var/www/html/allsky/config.js). +# IMG_PREFIX must have nothing after the trailing double quote (i.e., no comments). +# Recommend NOT to set to "" because the websites may try to read the file while it's being modified. +IMG_PREFIX="liveview-" + # Path to the camera settings (exposure, gain, delay, overlay, etc) CAMERA_SETTINGS_DIR="$ALLSKY_HOME" diff --git a/notification_images/CameraOffDuringDay.jpg b/notification_images/CameraOffDuringDay.jpg new file mode 100644 index 000000000..5f6c9acc4 Binary files /dev/null and b/notification_images/CameraOffDuringDay.jpg differ diff --git a/notification_images/CameraOffDuringDay.png b/notification_images/CameraOffDuringDay.png new file mode 100644 index 000000000..c5aafffbd Binary files /dev/null and b/notification_images/CameraOffDuringDay.png differ diff --git a/notification_images/DarkFrames.jpg b/notification_images/DarkFrames.jpg new file mode 100644 index 000000000..d30bab435 Binary files /dev/null and b/notification_images/DarkFrames.jpg differ diff --git a/notification_images/DarkFrames.png b/notification_images/DarkFrames.png new file mode 100644 index 000000000..7b679e9d0 Binary files /dev/null and b/notification_images/DarkFrames.png differ diff --git a/notification_images/Error.jpg b/notification_images/Error.jpg new file mode 100644 index 000000000..3736065f6 Binary files /dev/null and b/notification_images/Error.jpg differ diff --git a/notification_images/Error.png b/notification_images/Error.png new file mode 100644 index 000000000..47aedfa53 Binary files /dev/null and b/notification_images/Error.png differ diff --git a/notification_images/NotRunning.jpg b/notification_images/NotRunning.jpg new file mode 100644 index 000000000..35c11d207 Binary files /dev/null and b/notification_images/NotRunning.jpg differ diff --git a/notification_images/NotRunning.png b/notification_images/NotRunning.png new file mode 100644 index 000000000..f8e98b50a Binary files /dev/null and b/notification_images/NotRunning.png differ diff --git a/notification_images/StartingUp.jpg b/notification_images/StartingUp.jpg new file mode 100644 index 000000000..8e6b0529c Binary files /dev/null and b/notification_images/StartingUp.jpg differ diff --git a/notification_images/StartingUp.png b/notification_images/StartingUp.png new file mode 100644 index 000000000..745d49e46 Binary files /dev/null and b/notification_images/StartingUp.png differ diff --git a/scripts/copy_notification_image.sh b/scripts/copy_notification_image.sh new file mode 100644 index 000000000..e7b87ba3a --- /dev/null +++ b/scripts/copy_notification_image.sh @@ -0,0 +1,85 @@ +#!/bin/bash + +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file + +NOTIFICATIONFILE="$1" # filename, minus the extension, since the extension may vary +if [ "$1" = "" ] ; then + echo "*** $ME: ERROR: no file specified" >&2 + exit 1 +fi + +source $ALLSKY_HOME/config.sh +source $ALLSKY_HOME/scripts/filename.sh +source $ALLSKY_HOME/scripts/ftp-settings.sh + +cd $ALLSKY_HOME + +NOTIFICATIONFILE="notification_images/$NOTIFICATIONFILE.$EXTENSION" +if [ ! -e "$NOTIFICATIONFILE" ] ; then + echo "*** $ME: ERROR: File '$NOTIFICATIONFILE' does not exist or is empty!" >&2 + exit 1 +fi + +IMAGE_TO_USE="$FULL_FILENAME" +cp "$NOTIFICATIONFILE" "$IMAGE_TO_USE" # don't overwrite notification image + +# Resize the image if required +if [ $IMG_RESIZE = "true" ]; then + convert "$IMAGE_TO_USE" -resize "$IMG_WIDTH"x"$IMG_HEIGHT" "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: IMG_RESIZE failed with RET=$RET" + exit 1 + fi +fi + +# IMG_DIR and IMG_PREFIX are in config.sh +# If the user specified an IMG_PREFIX, copy the file to that name so the websites can display it. +if [ "${IMG_PREFIX}" != "" ]; then + cp "$IMAGE_TO_USE" "${IMG_PREFIX}${FILENAME}.${EXTENSION}" +fi + +# If 24 hour saving is desired, save the image in today's thumbnail directory +# so the user can see when things changed. +# Don't save in main image directory because we don't want the notification image in timelapses. +if [ "$CAPTURE_24HR" = "true" ] ; then + CURRENT=$(date +'%Y%m%d') + mkdir -p images/$CURRENT + THUMBNAILS_DIR=images/$CURRENT/thumbnails + mkdir -p $THUMBNAILS_DIR + + SAVED_FILE="$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION" + # Create a thumbnail of the image for faster load in web GUI + convert "$IMAGE_TO_USE" -resize "${THUMBNAIL_SIZE_X}x${THUMBNAIL_SIZE_Y}" "$THUMBNAILS_DIR/$SAVED_FILE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: WARNING: THUMBNAIL resize failed with RET=$RET; continuing." + fi +fi + +# If upload is true, optionally create a smaller version of the image and upload it +if [ "$UPLOAD_IMG" = "true" ] ; then + if [ "$RESIZE_UPLOADS" = "true" ]; then + echo -e "$ME: Resizing $NOTIFICATIONFILE for uploading" + # Create a smaller version for upload + convert "$IMAGE_TO_USE" -resize "$RESIZE_UPLOADS_SIZE" -gravity East -chop 2x0 "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: RESIZE_UPLOADS failed with RET=$RET" + exit 1 + fi + fi + + TS=$(ls -l --time-style='+%H:%M:%S' $IMAGE_TO_USE | awk '{print $6}') + echo -e "$ME: Uploading $(basename $NOTIFICATIONFILE) with timestamp: $TS\n" + if [ $PROTOCOL = "S3" ] ; then + $AWS_CLI_DIR/aws s3 cp "$IMAGE_TO_USE" s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & + elif [ $PROTOCOL = "local" ] ; then + cp "$IMAGE_TO_USE" "$IMGDIR" & + else + TEMP_NAME="ni-$RANDOM" + # "ni" = notification image. Use unique temporary name. + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$IMGDIR" -e "set net:max-retries 2; set net:timeout 20; put "$IMAGE_TO_USE" -o $TEMP_NAME; rm -f "$IMAGE_TO_USE"; mv $TEMP_NAME "$IMAGE_TO_USE"; bye" & + + fi +fi diff --git a/scripts/darkCapture.sh b/scripts/darkCapture.sh index 083491a95..ffc263b5d 100755 --- a/scripts/darkCapture.sh +++ b/scripts/darkCapture.sh @@ -1,21 +1,32 @@ #!/bin/bash +# This file is "source"d into another. cd $ALLSKY_HOME # If we are in darkframe mode, we only save to the dark file DARK_MODE=$(jq -r '.darkframe' "$CAMERA_SETTINGS") +USE_NOTIFICATION_IMAGES=$(jq -r '.notificationimages' "$CAMERA_SETTINGS") -TEMP_FILE="temperature.txt" -if [ -e "$TEMP_FILE" ]; then - TEMP=$(printf "%.0f" "`cat temperature.txt`") -else - # If the temperature file doesn't exist, set a default value - TEMP=20 -fi +# If the temperature file doesn't exist, set a default value +TEMP=20 +if [ "$DARK_MODE" = "1" ] ; then + + TEMP_FILE="temperature.txt" + if [ -s "$TEMP_FILE" ]; then # -s so we don't use an empty file + TEMP=$(printf "%.0f" "`cat ${TEMP_FILE}`") + fi -if [ $DARK_MODE = "1" ] ; then mkdir -p darks - cp $FULL_FILENAME "darks/$TEMP.$EXTENSION" - cp $FULL_FILENAME "liveview-$FILENAME.$EXTENSION" - exit 0 + # To avoid having the websites display a dark frame, when in dark mode + # the image file is different. + DARK_FRAME="dark.$EXTENSION" + cp "$DARK_FRAME" "darks/$TEMP.$EXTENSION" + + # If the user has notification images on, the current images says we're taking dark frames, so don't overwrite. + if [ "$USE_NOTIFICATION_IMAGES" = "0" ] ; then + # Go ahead and let the web sites see the dark frame. Not very interesting though... + cp "$DARK_FRAME" "${IMG_PREFIX}${FILENAME}.${EXTENSION}" + fi + + exit 0 # exit so the calling script exits. fi diff --git a/scripts/darkSubtract.sh b/scripts/darkSubtract.sh index 4132cbe8f..851610f4b 100755 --- a/scripts/darkSubtract.sh +++ b/scripts/darkSubtract.sh @@ -2,15 +2,18 @@ cd $ALLSKY_HOME +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file + # Subtract dark frame if there is one defined in config.sh +# This has to come after executing darkCapture.sh which sets ${TEMP}. -if [ "$DARK_FRAME_SUBTRACTION" == "true" ]; then +if [ "$DARK_FRAME_SUBTRACTION" = "true" ]; then # Find the closest dark frame temperature wise CLOSEST_TEMP=0 DIFF=100 for file in darks/* do - if [[ -f $file ]]; then + if [[ -f $file ]]; then # example file name for 21 degree dark: "darks/21.jpg". DARK_TEMP=$(echo $file | awk -F[/.] '{print $2}') DELTA=$(expr $TEMP - $CLOSEST_TEMP) ABS_DELTA=${DELTA#-} @@ -22,7 +25,18 @@ if [ "$DARK_FRAME_SUBTRACTION" == "true" ]; then fi done - if [ -e "darks/$CLOSEST_TEMP.$EXTENSION" ] ; then - convert "$FULL_FILENAME" "darks/$CLOSEST_TEMP.$EXTENSION" -compose minus_src -composite -type TrueColor "$FILENAME-processed.$EXTENSION" + PROCESSED_FILE="$FILENAME-processed.$EXTENSION" + DARK="darks/$CLOSEST_TEMP.$EXTENSION" + if [ -f "$DARK" ]; then + convert "$FULL_FILENAME" "$DARK" -compose minus_src -composite -type TrueColor "$PROCESSED_FILE" + RET=$? + if [ $RET -ne 0 ]; then + echo "*** $ME: ERROR: 'convert' of '$DARK' failed with RET=$RET" + exit 1 + fi + else + echo "*** $ME: ERROR: No dark frame found for $FULL_FILENAME at TEMP $TEMP." >> log.txt + echo "Either take dark frames or turn DARK_FRAME_SUBTRACTION off in config.sh" >> log.txt + cp "$FULL_FILENAME" "$PROCESSED_FILE" fi fi diff --git a/scripts/endOfNight.sh b/scripts/endOfNight.sh index 91f243d80..838354171 100755 --- a/scripts/endOfNight.sh +++ b/scripts/endOfNight.sh @@ -16,46 +16,73 @@ source $ALLSKY_HOME/scripts/filename.sh source $ALLSKY_HOME/scripts/ftp-settings.sh cd $ALLSKY_HOME/scripts +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file # Post end of night data. This includes next twilight time if [[ $POST_END_OF_NIGHT_DATA == "true" ]]; then - echo -e "Posting next twilight time to let server know when to resume liveview\n" + echo -e "$ME: Posting next twilight time to let server know when to resume liveview\n" ./postData.sh echo -e "\n" fi -# Uncomment this to scan for, and remove corrupt images before generating +LAST_NIGHT_DIR="$ALLSKY_HOME/images/$LAST_NIGHT" + +# Scan for, and remove corrupt images before generating # keograms and startrails. This can take several (tens of) minutes to run # and isn't necessary unless your system produces corrupt images which then # generate funny colors in the summary images... -# ./removeBadImages.sh $ALLSKY_HOME/images/$LAST_NIGHT/ +if [[ "$REMOVE_BAD_IMAGES" == "true" ]]; then + echo -e "$ME: Removing bad images\n" + ./removeBadImages.sh $LAST_NIGHT_DIR +fi + +TMP_DIR="$ALLSKY_HOME/tmp" +mkdir -p "$TMP_DIR" # Generate keogram from collected images if [[ $KEOGRAM == "true" ]]; then - echo -e "Generating Keogram\n" - mkdir -p $ALLSKY_HOME/images/$LAST_NIGHT/keogram/ - ../keogram $ALLSKY_HOME/images/$LAST_NIGHT/ $EXTENSION $ALLSKY_HOME/images/$LAST_NIGHT/keogram/keogram-$LAST_NIGHT.$EXTENSION - if [[ $UPLOAD_KEOGRAM == "true" ]] ; then - OUTPUT="$ALLSKY_HOME/images/$LAST_NIGHT/keogram/keogram-$LAST_NIGHT.$EXTENSION" + echo -e "$ME: Generating Keogram\n" + mkdir -p $LAST_NIGHT_DIR/keogram/ + OUTPUT="$LAST_NIGHT_DIR/keogram/keogram-$LAST_NIGHT.$EXTENSION" + # The keogram command outputs one line for each of the many hundreds of files, + # and this adds needless clutter to the log file, so send output to a tmp file so we can output the + # number of images. + + TMP="$TMP_DIR/keogramTMP.txt" + ../keogram $LAST_NIGHT_DIR/ $EXTENSION $OUTPUT > ${TMP} + RETCODE=$? + if [[ $UPLOAD_KEOGRAM == "true" && $RETCODE = 0 ]] ; then if [[ $PROTOCOL == "S3" ]] ; then $AWS_CLI_DIR/aws s3 cp $OUTPUT s3://$S3_BUCKET$KEOGRAM_DIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then cp $OUTPUT $KEOGRAM_DIR & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$KEOGRAM_DIR" \ - -e "set net:max-retries 1; put $OUTPUT; bye" & + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$KEOGRAM_DIR" \ + -e "set net:max-retries 1; put "$OUTPUT"; bye" & fi fi - echo -e "\n" + echo -e "$ME: Processed $(wc -l ${TMP}) keogram files\n" + # Leave ${TMP} in case the user needs to debug something. + + # Optionally copy to the local website in addition to the upload above. + if [ "$WEB_KEOGRAM_DIR" != "" ]; then + cp $OUTPUT "$WEB_KEOGRAM_DIR" + fi fi -# Generate startrails from collected images. Threshold set to 0.1 by default in config.sh to avoid stacking over-exposed images +# Generate startrails from collected images. +# Threshold set to 0.1 by default in config.sh to avoid stacking over-exposed images. if [[ $STARTRAILS == "true" ]]; then - echo -e "Generating Startrails\n" - mkdir -p $ALLSKY_HOME/images/$LAST_NIGHT/startrails/ - ../startrails $ALLSKY_HOME/images/$LAST_NIGHT/ $EXTENSION $BRIGHTNESS_THRESHOLD $ALLSKY_HOME/images/$LAST_NIGHT/startrails/startrails-$LAST_NIGHT.$EXTENSION - if [[ $UPLOAD_STARTRAILS == "true" ]] ; then - OUTPUT="$ALLSKY_HOME/images/$LAST_NIGHT/startrails/startrails-$LAST_NIGHT.$EXTENSION" + echo -e "$ME: Generating Startrails\n" + mkdir -p $LAST_NIGHT_DIR/startrails/ + OUTPUT="$LAST_NIGHT_DIR/startrails/startrails-$LAST_NIGHT.$EXTENSION" + # The startrails command outputs one line for each of the many hundreds of files, + # and this adds needless clutter to the log file, so send output to a tmp file so we can output the + # number of images. + TMP="$TMP_DIR/startrailsTMP.txt" + ../startrails $LAST_NIGHT_DIR/ $EXTENSION $BRIGHTNESS_THRESHOLD $OUTPUT > ${TMP} + RETCODE=$? + if [[ $UPLOAD_STARTRAILS == "true" && $RETCODE == 0 ]] ; then if [[ $PROTOCOL == "S3" ]] ; then $AWS_CLI_DIR/aws s3 cp $OUTPUT s3://$S3_BUCKET$STARTRAILS_DIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then @@ -65,15 +92,26 @@ if [[ $STARTRAILS == "true" ]]; then -e "set net:max-retries 1; put $OUTPUT; bye" & fi fi + echo -e "$ME: Processed $(wc -l ${TMP}) startrails files. Summary:\n" + grep "^Minimum" "${TMP}" + # Leave ${TMP} in case the user needs to debug something. - echo -e "\n" + # Optionally copy to the local website in addition to the upload above. + if [ "$WEB_STARTRAILS_DIR" != "" ]; then + cp $OUTPUT "$WEB_STARTRAILS_DIR" + fi fi # Generate timelapse from collected images if [[ $TIMELAPSE == "true" ]]; then - echo -e "Generating Timelapse\n" + echo -e "$ME: Generating Timelapse\n" ./timelapse.sh $LAST_NIGHT echo -e "\n" + + # Optionally copy to the local website in addition to the upload above. + if [ "$WEB_MP4DIR" != "" ]; then + cp $LAST_NIGHT_DIR/allsky-$LAST_NIGHT.mp4 "$WEB_MP4DIR" + fi fi # Run custom script at the end of a night. This is run BEFORE the automatic deletion just in case you need to do something with the files before they are removed @@ -82,7 +120,7 @@ fi # Automatically delete old images and videos if [[ $AUTO_DELETE == "true" ]]; then del=$(date --date="$NIGHTS_TO_KEEP days ago" +%Y%m%d) - for i in `find $ALLSKY_HOME/images/ -type d -name "2*"`; do + for i in `find $ALLSKY_HOME/images/ -type d -name "2*"`; do # "2*" for years >= 2000 (($del > $(basename $i))) && rm -rf $i done fi diff --git a/scripts/endOfNight_additionalSteps.sh b/scripts/endOfNight_additionalSteps.sh index 6a610156f..5ff64d674 100755 --- a/scripts/endOfNight_additionalSteps.sh +++ b/scripts/endOfNight_additionalSteps.sh @@ -3,4 +3,6 @@ # # Place any additional code you require to be run at the end of the night in this script. This script is run prior # to the deletion of any old image files. -# \ No newline at end of file +# +# Include "${ME}" in any output so it's easier to find in the log file, for debugging. +ME="$(basename "$BASH_ARGV0")" diff --git a/scripts/generateForDay.sh b/scripts/generateForDay.sh index c73854351..6e4653375 100755 --- a/scripts/generateForDay.sh +++ b/scripts/generateForDay.sh @@ -1,33 +1,41 @@ #!/bin/bash +# This script allows users to manually generate keograms,startrails, and timelapses, +# outside of the ones generated automatically. + source $ALLSKY_HOME/config.sh source $ALLSKY_HOME/scripts/filename.sh cd $ALLSKY_HOME/scripts +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file + RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color if [ $# -lt 1 ] then - echo -en "${RED}You need to pass a day argument\n" - echo -en " ex: generateForDay.sh 20180119${NC}\n" + echo -en "${ME}: ${RED}You need to pass a day argument\n" + echo -en " ex: ${ME} 20180119${NC}\n" exit 3 fi +DATE=$1 +DIR="$ALLSKY_HOME/images/$DATE" + # Generate timelapse from collected images -echo -e "Generating Keogram\n" -mkdir -p $ALLSKY_HOME/images/$1/keogram/ -../keogram $ALLSKY_HOME/images/$1/ $EXTENSION $ALLSKY_HOME/images/$1/keogram/keogram-$1.jpg +echo -e "${ME}: Generating Keogram\n" +mkdir -p ${DIR}/keogram/ +../keogram ${DIR}/ $EXTENSION ${DIR}/keogram/keogram-$DATE.jpg echo -e "\n" # Generate startrails from collected images. Treshold set to 0.1 by default in config.sh to avoid stacking over-exposed images -echo -e "Generating Startrails\n" -mkdir -p $ALLSKY_HOME/images/$1/startrails/ -../startrails $ALLSKY_HOME/images/$1/ $EXTENSION $BRIGHTNESS_THRESHOLD $ALLSKY_HOME/images/$1/startrails/startrails-$1.jpg +echo -e "${ME}: Generating Startrails\n" +mkdir -p ${DIR}/startrails +../startrails ${DIR}/ $EXTENSION $BRIGHTNESS_THRESHOLD ${DIR}/startrails/startrails-$DATE.jpg echo -e "\n" # Generate timelapse from collected images -echo -e "Generating Timelapse\n" -./timelapse.sh $1 +echo -e "${ME}: Generating Timelapse\n" +./timelapse.sh $DATE echo -e "\n" diff --git a/scripts/postData.sh b/scripts/postData.sh index a6a1ce99e..53548de13 100755 --- a/scripts/postData.sh +++ b/scripts/postData.sh @@ -4,9 +4,10 @@ source $ALLSKY_HOME/scripts/ftp-settings.sh # TODO Needs fixing when civil twilight happens after midnight cd $ALLSKY_HOME/scripts +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file -latitude=60.7N -longitude=135.02W +latitude=$(jq -r '.latitude' "$CAMERA_SETTINGS") +longitude=$(jq -r '.longitude' "$CAMERA_SETTINGS") timezone=-0700 streamDaytime=false @@ -14,7 +15,7 @@ if [[ $DAYTIME == "1" ]] ; then streamDaytime=true; fi -echo "Posting Next Twilight Time" +#echo "$ME: Posting Next Twilight Time" today=`date +%Y-%m-%d` time="$(sunwait list set civil $latitude $longitude)" timeNoZone=${time:0:5} @@ -22,11 +23,11 @@ echo { > data.json echo \"sunset\": \"$today"T"$timeNoZone":00.000$timezone"\", >> data.json echo \"streamDaytime\": \"$streamDaytime\" >> data.json echo } >> data.json -echo "Uploading data.json" +echo "$ME: Uploading data.json" if [[ $PROTOCOL == "S3" ]] ; then $AWS_CLI_DIR/aws s3 cp data.json s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then cp data.json $IMGDIR & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$IMGDIR" -e "set net:max-retries 1; set net:timeout 20; put data.json; bye" & + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$IMGDIR" -e "set net:max-retries 1; set net:timeout 20; put data.json; bye" & fi diff --git a/scripts/removeBadImages.sh b/scripts/removeBadImages.sh index 0da42cc98..76806a4ff 100755 --- a/scripts/removeBadImages.sh +++ b/scripts/removeBadImages.sh @@ -1,15 +1,21 @@ #!/bin/bash +REMOVE_BAD_IMAGES_THRESHOLD_LOW=${REMOVE_BAD_IMAGES_THRESHOLD_LOW:-0} # in case not in config.sh file +REMOVE_BAD_IMAGES_THRESHOLD_HIGH=${REMOVE_BAD_IMAGES_THRESHOLD_HIGH:-0} # in case not in config.sh file + +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file + if [ $# -ne 1 -o 'x$1' == 'x-h' ] ; then echo "Remove images with corrupt data which might mess up startrails and keograms" - echo "usage: $0 " + echo "usage: $ME " exit 1 fi if [ \! -d "$1" ] ; then - echo "$1 is not a directory" + echo "$ME: $1 is not a directory" exit 1 fi +DIR=$1 # Super simple: find the full size image-*jpg and image-*png files (not the # thumbnails) and ask imagemagick to compute a histogram (which is discarded) @@ -28,11 +34,47 @@ fi # If GNU Parallel is installed (it's not by default), then blast through and # clean all the images as fast as possible without regard for CPU utilization. +# Use IMAGE_FILES and ERROR_WORDS to avoid duplicating them. +# Remove 0-length files ("insufficient image data") and files too dim or bright. +# $DIR may end in a "/" so there will be "//" in the filenames, but there's no harm in that. +cd $DIR +IMAGE_FILES="$( find . -type f \( -iname image-\*.jpg -o -iname image-\*.png \) \! -ipath \*thumbnail\* )" +ERROR_WORDS="Huffman|Bogus|Corrupt|Invalid|Trunc|Missing|insufficient image data|no decode delegate" + +TMP=badError.txt + if which parallel > /dev/null ; then - find "$1" -type f \( -iname image-\*.jpg -o -iname image-\*.png \) \! -ipath \*thumbnail\* | \ - parallel -- "convert {} histogram:/dev/null 2>&1 | egrep -q 'Huffman|Bogus|Corrupt|Invalid|Trunc|Missing' && rm -vf {}" + echo $IMAGE_FILES | \ + parallel -- "convert {} histogram:/dev/null 2>&1 | egrep -q "$ERROR_WORDS" && rm -vf {}" + # xxxxxxxxxx need to add THRESHOLD checking here and remove bad thumbnails... + # xxxxxxxxxx Can we replace "rm -vf" above with "echo" and redirect output to the tmp file, + # xxxxxxxxxx then do a "for f in $(cat $TMP); do" and remove the files that way? else - for f in $( find "$1" -type f \( -iname image-\*.jpg -o -iname image-\*.png \) \! -ipath \*thumbnail\* ) ; do - nice convert "$f" histogram:/dev/null 2>&1 | egrep -q 'Huffman|Bogus|Corrupt|Invalid|Trunc|Missing' && rm -vf $f + typeset -i num_bad=0 + for f in ${IMAGE_FILES} ; do + MEAN=$(nice convert "$f" -colorspace Gray -format "%[fx:image.mean]" info: 2> $TMP) + BAD="" + egrep -q "$ERROR_WORDS" $TMP + RET=$? + if [ $RET -eq 0 ] ; then + rm -f "$f" "thumbnails/$f" + BAD="'$f' (corrupt file: $(cat $TMP))" + let num_bad=num_bad+1 + else + # Multiply MEAN by 100 to convert to integer (0-100 %) since bash doesn't work with floats. + MEAN=$(echo "$MEAN" | awk '{ printf("%d", $1 * 100); }') + if [ $MEAN -lt $REMOVE_BAD_IMAGES_THRESHOLD_LOW -o $MEAN -gt $REMOVE_BAD_IMAGES_THRESHOLD_HIGH ]; then + rm -f "$f" "thumbnails/$f" + BAD="'$f' (bad threshold: MEAN=$MEAN)" + let num_bad=num_bad+1 + fi + fi + [ "$BAD" != "" ] && echo "$ME: Removed $BAD" done + if [ $num_bad -eq 0 ]; then + echo "$ME: No bad files found." + else + echo "$ME: $num_bad bad file(s) found and removed." + fi fi +rm -f $TMP diff --git a/scripts/saveImageDay.sh b/scripts/saveImageDay.sh index 369f14fec..051e54b8a 100755 --- a/scripts/saveImageDay.sh +++ b/scripts/saveImageDay.sh @@ -1,64 +1,101 @@ #!/bin/bash source $ALLSKY_HOME/config.sh source $ALLSKY_HOME/scripts/filename.sh -source $ALLSKY_HOME/scripts/darkCapture.sh +source $ALLSKY_HOME/scripts/darkCapture.sh # does not return if in darkframe mode source $ALLSKY_HOME/scripts/ftp-settings.sh cd $ALLSKY_HOME +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file -# If we are in darkframe mode, we only save to the dark file -DARK_MODE=$(jq -r '.darkframe' "$CAMERA_SETTINGS") +IMAGE_TO_USE="$FULL_FILENAME" +# quotes around $IMAGE_TO_USE below, in case it has a space or special characters. -if [ $DARK_MODE = "1" ] ; then - exit 0 +# Make sure the image isn't corrupted +identify "$IMAGE_TO_USE" >/dev/null 2>&1 +RET=$? +if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: Image '${IMAGE_TO_USE} is corrupt; ignoring." + exit 1 fi -IMAGE_TO_USE="$FULL_FILENAME" - # Resize the image if required if [[ $IMG_RESIZE == "true" ]]; then - convert "$IMAGE_TO_USE" -resize "$IMG_WIDTH"x"$IMG_HEIGHT" $IMAGE_TO_USE + convert "$IMAGE_TO_USE" -resize "$IMG_WIDTH"x"$IMG_HEIGHT" "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: IMG_RESIZE failed with RET=$RET" + exit 1 + fi fi # Crop the image around the center if required if [[ $CROP_IMAGE == "true" ]]; then - convert "$IMAGE_TO_USE" -gravity Center -crop "$CROP_WIDTH"x"$CROP_HEIGHT"+"$CROP_OFFSET_X"+"$CROP_OFFSET_Y" +repage "$IMAGE_TO_USE"; + convert "$IMAGE_TO_USE" -gravity Center -crop "$CROP_WIDTH"x"$CROP_HEIGHT"+"$CROP_OFFSET_X"+"$CROP_OFFSET_Y" +repage "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: CROP_IMAGE failed with RET=$RET" + exit 1 + fi fi -cp $IMAGE_TO_USE "liveview-$FILENAME.$EXTENSION" - +# IMG_DIR and IMG_PREFIX are in config.sh +# If the user specified an IMG_PREFIX, copy the file to that name so the websites can display it. +if [ "${IMG_PREFIX}" != "" ]; then + cp "$IMAGE_TO_USE" "${IMG_PREFIX}${FILENAME}.${EXTENSION}" +fi # If 24 hour saving is desired, save the current image in today's directory -if [ "$CAPTURE_24HR" = true ] ; then +if [ "$CAPTURE_24HR" = "true" ] ; then CURRENT=$(date +'%Y%m%d') mkdir -p images/$CURRENT - mkdir -p images/$CURRENT/thumbnails + THUMBNAILS_DIR=images/$CURRENT/thumbnails + mkdir -p $THUMBNAILS_DIR # Save image in images/current directory - cp $IMAGE_TO_USE "images/$CURRENT/$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION" + SAVED_FILE="$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION" + cp "$IMAGE_TO_USE" "images/$CURRENT/$SAVED_FILE" - # Create a thumbnail of the image for faster load in web GUI - if identify $IMAGE_TO_USE >/dev/null 2>&1; then - convert "$IMAGE_TO_USE" -resize 100x75 "images/$CURRENT/thumbnails/$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION"; + # Create a thumbnail of the image for faster load in web GUI. + # If we resized above, this will be a resize of a resize, + # but for thumbnails it should be ok. + convert "$IMAGE_TO_USE" -resize "${THUMBNAIL_SIZE_X}x${THUMBNAIL_SIZE_Y}" "$THUMBNAILS_DIR/$SAVED_FILE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: THUMBNAIL resize failed with RET=$RET; continuing." fi fi -# If upload is true, create a smaller version of the image and upload it +# If upload is true, optionally create a smaller version of the image; either way, upload it if [ "$UPLOAD_IMG" = true ] ; then - echo -e "Resizing" - echo -e "Resizing $FULL_FILENAME \n" >> log.txt + if [[ "$RESIZE_UPLOADS" == "true" ]]; then + echo -e "$ME: Resizing '$IMAGE_TO_USE' for uploading" + echo -e "$ME: Resizing '$IMAGE_TO_USE' for uploading\n" >> log.txt - # Create a thumbnail for live view - convert "$IMAGE_TO_USE" -resize 962x720 -gravity East -chop 2x0 "$FILENAME-resize.$EXTENSION"; + # Create a smaller version for upload + convert "$IMAGE_TO_USE" -resize "$RESIZE_UPLOADS_SIZE" -gravity East -chop 2x0 "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: RESIZE_UPLOADS failed with RET=$RET" + exit 1 + fi + fi - echo -e "Uploading\n" - echo -e "Uploading $FILENAME-resize.$EXTENSION \n" >> log.txt - if [[ $PROTOCOL == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp $FILENAME-resize.$EXTENSION s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & + TS=$(ls -l --time-style='+%H:%M:%S' "$IMAGE_TO_USE" | awk '{print $6}') + echo -e "$ME: Uploading '$IMAGE_TO_USE' with timestamp: $TS\n" + echo -e "$ME: Uploading '$IMAGE_TO_USE' with timestamp: $TS" >> log.txt + if [[ $PROTOCOL == "S3" ]] ; then + $AWS_CLI_DIR/aws s3 cp "$IMAGE_TO_USE" s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $FILENAME-resize.$EXTENSION $IMGDIR & + cp "$IMAGE_TO_USE" "$IMGDIR" & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$IMGDIR" -e "set net:max-retries 1; set net:timeout 20; put $FILENAME-resize.$EXTENSION; bye" & + # Put to a temp name, then move the temp name to the final name. + # This is because slow uplinks can cause multiple lftp requests to be running at once, + # and only one lftp can upload the file at once, otherwise get this error: + # put: Access failed: 550 The process cannot access the file because it is being used by another process. (image.jpg) + # Slow uploads also cause a problem with web pages that try to read the file as it's being uploaded. + # "si" = "save image" - use a unique temporary name + TEMP_NAME="si-$RANDOM" + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$IMGDIR" -e "set net:max-retries 2; set net:timeout 20; put "$IMAGE_TO_USE" -o $TEMP_NAME; rm -f "$IMAGE_TO_USE"; mv $TEMP_NAME "$IMAGE_TO_USE"; bye" & fi fi diff --git a/scripts/saveImageNight.sh b/scripts/saveImageNight.sh index 399fffc15..c79e2a7d6 100755 --- a/scripts/saveImageNight.sh +++ b/scripts/saveImageNight.sh @@ -1,73 +1,130 @@ #!/bin/bash source $ALLSKY_HOME/config.sh source $ALLSKY_HOME/scripts/filename.sh -source $ALLSKY_HOME/scripts/darkCapture.sh +source $ALLSKY_HOME/scripts/darkCapture.sh # does not return if in darkframe mode source $ALLSKY_HOME/scripts/darkSubtract.sh source $ALLSKY_HOME/scripts/ftp-settings.sh cd $ALLSKY_HOME +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file -# If we are in darkframe mode, we only save to the dark file -DARK_MODE=$(jq -r '.darkframe' "$CAMERA_SETTINGS") - -if [ $DARK_MODE = "1" ] ; then - exit 0 -fi - -# Make a directory to store current night images -# the 12 hours ago option ensures that we're always using today's date even at high latitudes where civil twilight can start after midnight +# Make a directory to store current night images. +# The 12 hours ago option ensures that we're always using today's date even at high latitudes where civil twilight can start after midnight. CURRENT=$(date -d '12 hours ago' +'%Y%m%d') mkdir -p images/$CURRENT -mkdir -p images/$CURRENT/thumbnails +THUMBNAILS_DIR=images/$CURRENT/thumbnails +mkdir -p $THUMBNAILS_DIR # Create image to use (original or processed) for liveview in GUI IMAGE_TO_USE="$FULL_FILENAME" +# quotes around $IMAGE_TO_USE below, in case it has a space or special characters. + +# Make sure the image isn't corrupted +identify "$IMAGE_TO_USE" >/dev/null 2>&1 +RET=$? +if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: Image '${IMAGE_TO_USE} is corrupt; ignoring." + exit 1 +fi + if [ "$DARK_FRAME_SUBTRACTION" = "true" ] ; then - IMAGE_TO_USE="$FILENAME-processed.$EXTENSION" + # PROCESSED_FILE should have been created by darkSubtract.sh. If not, it output an error message. + PROCESSED_FILE="$FILENAME-processed.$EXTENSION" + # Check in case the user has subtraction set to "true" but has no dark frames. + if [[ ! -f "$PROCESSED_FILE" ]]; then + echo "*** $ME: WARNING: Processed image '$PROCESSED_FILE' does not exist; continuing!" >> log.txt + else + # Want the name of the final file to alway be the same + mv -f "$PROCESSED_FILE" "$IMAGE_TO_USE" + fi fi # Resize the image if required if [[ $IMG_RESIZE == "true" ]]; then - convert "$IMAGE_TO_USE" -resize "$IMG_WIDTH"x"$IMG_HEIGHT" $IMAGE_TO_USE + convert "$IMAGE_TO_USE" -resize "$IMG_WIDTH"x"$IMG_HEIGHT" "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: IMG_RESIZE failed with RET=$RET" + exit 1 + fi fi # Crop the image around the center if required if [[ $CROP_IMAGE == "true" ]]; then - convert "$IMAGE_TO_USE" -gravity Center -crop "$CROP_WIDTH"x"$CROP_HEIGHT"+"$CROP_OFFSET_X"+"$CROP_OFFSET_Y" +repage "$IMAGE_TO_USE"; + convert "$IMAGE_TO_USE" -gravity Center -crop "$CROP_WIDTH"x"$CROP_HEIGHT"+"$CROP_OFFSET_X"+"$CROP_OFFSET_Y" +repage "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: CROP_IMAGE failed with RET=$RET" + exit 1 + fi fi # Stretch the image if [[ $AUTO_STRETCH == "true" ]]; then - convert $IMAGE_TO_USE -sigmoidal-contrast "$AUTO_STRETCH_AMOUNT","$AUTO_STRETCH_MID_POINT" $IMAGE_TO_USE + convert "$IMAGE_TO_USE" -sigmoidal-contrast "$AUTO_STRETCH_AMOUNT","$AUTO_STRETCH_MID_POINT" "$IMAGE_TO_USE" + RET=$? + if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: AUTO_STRETCH failed with RET=$RET" + exit 1 + fi fi -cp $IMAGE_TO_USE "liveview-$FILENAME.$EXTENSION" +# IMG_DIR and IMG_PREFIX are in config.sh +# If the user specified an IMG_PREFIX, copy the file to that name so the websites can display it. +if [ "${IMG_PREFIX}" != "" ]; then + cp "$IMAGE_TO_USE" "${IMG_PREFIX}${FILENAME}.${EXTENSION}" +fi # Save image in images/current directory -cp $IMAGE_TO_USE "images/$CURRENT/$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION" +SAVED_FILE="$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION" +cp "$IMAGE_TO_USE" "images/$CURRENT/$SAVED_FILE" -# Create a thumbnail of the image for faster load in web GUI -if identify $IMAGE_TO_USE >/dev/null 2>&1; then - convert "$IMAGE_TO_USE" -resize 100x75 "images/$CURRENT/thumbnails/$FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION"; +# Create a thumbnail of the image for faster load in web GUI. +# If we resized above, this will be a resize of a resize, but for thumbnails it should be ok. +convert "$IMAGE_TO_USE" -resize "${THUMBNAIL_SIZE_X}x${THUMBNAIL_SIZE_Y}" "$THUMBNAILS_DIR/$SAVED_FILE" +RET=$? +if [ $RET -ne 0 ] ; then + echo "*** $ME: ERROR: THUMBNAIL resize failed with RET=$RET; continuing." fi -echo -e "Saving $FILENAME-$(date +'%Y%m%d%H%M%S').$EXTENSION\n" >> log.txt - -# If upload is true, create a smaller version of the image and upload it -if [ "$UPLOAD_IMG" = true ] ; then - echo -e "Resizing \n" - echo -e "Resizing $FULL_FILENAME \n" >> log.txt +# If upload is true, optionally create a smaller version of the image; either way, upload it +if [[ "$UPLOAD_IMG" == "true" ]] ; then + if [[ "$RESIZE_UPLOADS" == "true" ]]; then + echo -e "$ME: Resizing '$IMAGE_TO_USE' for uploading\n" + echo -e "$ME: Resizing '$IMAGE_TO_USE' for uploading\n" >> log.txt - # Create a thumbnail for live view - convert "$IMAGE_TO_USE" -resize 962x720 -gravity East -chop 2x0 "$FILENAME-resize.$EXTENSION"; + # Create smaller version for upload + convert "$IMAGE_TO_USE" -resize "$RESIZE_UPLOADS_SIZE" -gravity East -chop 2x0 "$IMAGE_TO_USE" + RET=$? + [ $RET -ne 0 ] && echo "*** $ME: ERROR: RESIZE_UPLOADS failed with RET=$RET" + fi - echo -e "Uploading \n" - echo -e "Uploading $FILENAME-resize.$EXTENSION \n" >> log.txt + TS=$(ls -l --time-style='+%H:%M:%S' $IMAGE_TO_USE | awk '{print $6}') + echo -e "$ME: Uploading '$IMAGE_TO_USE' with timestamp: $TS\n" + echo -e "$ME: Uploading '$IMAGE_TO_USE' with timestamp: $TS" >> log.txt if [[ $PROTOCOL == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp $FILENAME-resize.$EXTENSION s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & + $AWS_CLI_DIR/aws s3 cp "$IMAGE_TO_USE" s3://$S3_BUCKET$IMGDIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $FILENAME-resize.$EXTENSION $IMGDIR & + cp "$IMAGE_TO_USE" "$IMGDIR" & + elif [[ $PROTOCOL == "test" ]]; then # added for testing + (echo set ssl:check-hostname false + echo open -u "$USER,$PASSWORD" "ftp://$HOST" + echo cd "$IMGDIR" + echo set net:max-retries 1 + echo set net:timeout 20 + echo put "$IMAGE_TO_USE" + echo bye + ) > night.lftp + lftp -d -f night.lftp > log_night.txt 2>&1 & + else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$IMGDIR" -e "set net:max-retries 1; set net:timeout 20; put $FILENAME-resize.$EXTENSION; bye" & + # "put" to a temp name, then move the temp name to the final name. + # This is because slow uplinks can cause multiple lftp requests to be running at once, + # and only one lftp can upload the file at once, otherwise get this error: + # put: Access failed: 550 The process cannot access the file because it is being used by another process. (image.jpg) + # Slow uploads also cause a problem with web pages that try to read the file as it's being uploaded. + # "si" = "save image" - use a unique temporary name + TEMP_NAME="si-$RANDOM" + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$IMGDIR" -e "set net:max-retries 2; set net:timeout 20; put "$IMAGE_TO_USE" -o $TEMP_NAME; rm -f "$IMAGE_TO_USE"; mv $TEMP_NAME "$IMAGE_TO_USE"; bye" & fi fi diff --git a/scripts/timelapse.sh b/scripts/timelapse.sh index f21db7783..526849ff9 100755 --- a/scripts/timelapse.sh +++ b/scripts/timelapse.sh @@ -3,7 +3,9 @@ source $ALLSKY_HOME/config.sh source $ALLSKY_HOME/scripts/filename.sh source $ALLSKY_HOME/scripts/ftp-settings.sh -cd $ALLSKY_HOME/ +cd $ALLSKY_HOME + +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file RED='\033[0;31m' GREEN='\033[0;32m' @@ -11,50 +13,110 @@ NC='\033[0m' # No Color if [ $# -lt 1 ] then - echo -en "${RED}You need to pass a day argument\n" + echo -en "${ME}: ${RED}You need to pass a day argument\n" echo -en " ex: timelapse.sh 20180119${NC}\n" exit 3 fi -echo -en "* ${GREEN}Creating symlinks to generate timelapse${NC}\n" -mkdir $ALLSKY_HOME/images/$1/sequence/ +# Allow timelapses of pictures not in ALLSKY_HOME. +# If $2 is passed, it's the top folder, otherwise use the one in ALLSKY_HOME. +DAY="$1" +if [ "$2" = "" ] ; then + DIR="$ALLSKY_HOME/images/$DAY" # Need full pathname for links +else + DIR="$2" +fi +echo -en "${ME}: ${GREEN}Creating symlinks to generate timelapse${NC}\n" +[ -d $DIR/sequence ] && rm -fr $DIR/sequence # remove in case it was left over from last run +mkdir -p $DIR/sequence/ # find images, make symlinks sequentially and start avconv to build mp4; upload mp4 and move directory -find "$ALLSKY_HOME/images/$1" -name "*.$EXTENSION" -size 0 -delete -ls -rt $ALLSKY_HOME/images/$1/*.$EXTENSION | -gawk 'BEGIN{ a=1 }{ printf "ln -sv %s $ALLSKY_HOME/images/'$1'/sequence/%04d.'$EXTENSION'\n", $0, a++ }' | +find "$DIR" -name "*.$EXTENSION" -size 0 -delete + +TMP_DIR="$ALLSKY_HOME/tmp" +mkdir -p "$TMP_DIR" +TMP="$TMP_DIR/timelapseTMP.txt" # capture the "ln" commands in case the user needs to debug +> $TMP +ls -rt $DIR/*.$EXTENSION | +gawk 'BEGIN{ a=1 } + { + printf "ln -s %s '$DIR'/sequence/%04d.'$EXTENSION'\n", $0, a; + printf "ln -s %s '$DIR'/sequence/%04d.'$EXTENSION'\n", $0, a >> "'$TMP'"; + # if (a % 100 == 0) printf "echo '$ME': %d links created so far\n", a; + a++; + }' | bash +# Make sure there's at least one link. +NUM_FILES=$(wc -l < ${TMP}) +if [ $NUM_FILES -eq 0 ]; then + echo -en "*** ${ME}: ${RED}ERROR: No links found!${NC}\n" + rmdir "${DIR}/sequence" + exit 1 +else + echo -e "$ME: Created $NUM_FILES links total\n" +fi + SCALE="" TIMELAPSEWIDTH=${TIMELAPSEWIDTH:-0} -if [ "${TIMELAPSEWIDTH}" != 0 ] - then +if [ "${TIMELAPSEWIDTH}" != 0 ]; then SCALE="-filter:v scale=${TIMELAPSEWIDTH:0}:${TIMELAPSEHEIGHT:0}" - echo "Using video scale ${TIMELAPSEWIDTH} * ${TIMELAPSEHEIGHT}" + echo "$ME: Using video scale ${TIMELAPSEWIDTH} * ${TIMELAPSEHEIGHT}" fi +# "-loglevel warning" gets rid of the dozens of lines of garbage output +# but doesn't get rid of "deprecated pixel format" message when -pix_ftm is "yuv420p". ffmpeg -y -f image2 \ + -loglevel warning \ -r $FPS \ - -i images/$1/sequence/%04d.$EXTENSION \ + -i $DIR/sequence/%04d.$EXTENSION \ -vcodec libx264 \ -b:v 2000k \ -pix_fmt yuv420p \ -movflags +faststart \ $SCALE \ - images/$1/allsky-$1.mp4 + $DIR/allsky-$DAY.mp4 +RET=$? +if [ $RET -ne 0 ]; then + echo -e "\n${RED}*** $ME: ERROR: ffmpeg failed with RET=$RET" + echo "Links in '$DIR/sequence' left for debugging." + echo -e "Remove them when the problem is fixed.${NC}\n" + exit 1 +fi if [ "$UPLOAD_VIDEO" = true ] ; then if [[ "$PROTOCOL" == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp images/$1/allsky-$1.mp4 s3://$S3_BUCKET$MP4DIR --acl $S3_ACL & + $AWS_CLI_DIR/aws s3 cp $DIR/allsky-$DAY.mp4 s3://$S3_BUCKET$MP4DIR --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $FILENAME-resize.$EXTENSION /var/www/html/$MP4DIR & + cp $DIR/allsky-$DAY.mp4 $MP4DIR & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$MP4DIR" -e "set net:max-retries 1; put images/$1/allsky-$1.mp4; bye" & + # This sometimes fails with "mv: Access failed: 550 The process cannot access the file because it is being used by another process. (cp)". + # xxxx Could it be because the web server is trying to access the file at the same time? + # If so, should we wait a few seconds and try lftp again? + # This probably isn't an issue with .jpg files since they are much smaller and the window for + # simultaneous access is much smaller. + + # "put" to a temp name, then move the temp name to the final name. + # This is because slow uplinks can cause multiple lftp requests to be running at once, + # and only one lftp can upload the file at once, otherwise get this error: + # put: Access failed: 550 The process cannot access the file because it is being used by another process. (image.jpg) + # Slow uploads also cause a problem with web pages that try to read the file as it's being uploaded. + # "tl" = "time lapse" - use a unique temporary name + TEMP_NAME="tl-$RANDOM" + ( + FILE="allsky-$DAY.mp4" + FULL_FILE="/$DIR/$FILE" + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$MP4DIR" -e "set net:max-retries 1; put "$FULL_FILE" -o $TEMP_NAME; rm -f "$FILE"; mv $TEMP_NAME "$FILE"; bye" + RET=$? + if [ $RET -ne 0 ]; then + echo "${RED}*** $ME: ERROR: lftp failed with RET=$RET on '$FULL_FILE'${NC}" + fi + ) & fi fi -echo -en "* ${GREEN}Deleting sequence${NC}\n" -rm -rf $ALLSKY_HOME/images/$1/sequence +echo -en "${ME}: ${GREEN}Deleting sequence${NC}\n" +rm -rf $DIR/sequence -echo -en "* ${GREEN}Timelapse was created${NC}\n" +echo -en "${ME}: ${GREEN}Timelapse was created${NC}\n" diff --git a/scripts/uploadForDay.sh b/scripts/uploadForDay.sh index e4c0b7e0b..c6ffc07b2 100755 --- a/scripts/uploadForDay.sh +++ b/scripts/uploadForDay.sh @@ -5,49 +5,53 @@ source $ALLSKY_HOME/scripts/ftp-settings.sh cd $ALLSKY_HOME/scripts +ME="$(basename "$BASH_ARGV0")" # Include script name in output so it's easier to find in the log file + RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m' # No Color if [ $# -lt 1 ] then - echo -en "${RED}You need to pass a day argument\n" - echo -en " ex: uploadForDay.sh 20180119${NC}\n" + echo -en "${ME}: ${RED}You need to pass a day argument\n" + echo -en " ex: $ME 20180119${NC}\n" exit 3 fi +DIR="$ALLSKY_HOME/images/$1" + # Upload keogram -echo -e "Uploading Keogram\n" -KEOGRAM="$ALLSKY_HOME/images/$1/keogram/keogram-$1.$EXTENSION" +echo -e "${ME}: Uploading Keogram\n" +KEOGRAM="$DIR/keogram/keogram-$1.$EXTENSION" if [[ $PROTOCOL == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp $KEOGRAM s3://$S3_BUCKET$KEOGRAM_DIR --acl $S3_ACL & + $AWS_CLI_DIR/aws s3 cp "$KEOGRAM" "s3://$S3_BUCKET$KEOGRAM_DIR" --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $KEOGRAM $KEOGRAM_DIR & + cp "$KEOGRAM" "$KEOGRAM_DIR" & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$KEOGRAM_DIR" -e "set net:max-retries 1; put $KEOGRAM; bye" -u "$USER","$PASSWORD" & + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$KEOGRAM_DIR" -e "set net:max-retries 1; put "$KEOGRAM"; bye" & fi echo -e "\n" # Upload Startrails -echo -e "Uploading Startrails\n" -STARTRAILS="$ALLSKY_HOME/images/$1/startrails/startrails-$1.$EXTENSION" +echo -e "${ME}: Uploading Startrails\n" +STARTRAILS="$DIR/startrails/startrails-$1.$EXTENSION" if [[ $PROTOCOL == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp $STARTRAILS s3://$S3_BUCKET$STARTRAILS_DIR --acl $S3_ACL & + $AWS_CLI_DIR/aws s3 cp "$STARTRAILS" "s3://$S3_BUCKET$STARTRAILS_DIR" --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $STARTRAILS $STARTRAILS_DIR & + cp "$STARTRAILS" "$STARTRAILS_DIR" & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$STARTRAILS_DIR" -e "set net:max-retries 1; put $STARTRAILS; bye" & + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$STARTRAILS_DIR" -e "set net:max-retries 1; put "$STARTRAILS"; bye" & fi echo -e "\n" # Upload timelapse -echo -e "Uploading Timelapse\n" -TIMELAPSE="$ALLSKY_HOME/images/$1/allsky-$1.mp4" +echo -e "${ME}: Uploading Timelapse\n" +TIMELAPSE="$DIR/allsky-$1.mp4" if [[ "$PROTOCOL" == "S3" ]] ; then - $AWS_CLI_DIR/aws s3 cp $TIMELAPSE s3://$S3_BUCKET$MP4DIR --acl $S3_ACL & + $AWS_CLI_DIR/aws s3 cp "$TIMELAPSE" "s3://$S3_BUCKET$MP4DIR" --acl $S3_ACL & elif [[ $PROTOCOL == "local" ]] ; then - cp $TIMELAPSE $MP4DIR & + cp "$TIMELAPSE" "$MP4DIR" & else - lftp "$PROTOCOL"://"$USER":"$PASSWORD"@"$HOST":"$MP4DIR" -e "set net:max-retries 1; put $TIMELAPSE; bye" & + lftp "$PROTOCOL://$USER:$PASSWORD@$HOST:$MP4DIR" -e "set net:max-retries 1; put "$TIMELAPSE"; bye" & fi -echo -e "\n" \ No newline at end of file +echo -e "\n" diff --git a/settings_RPiHQ.json.repo b/settings_RPiHQ.json.repo index 416f6df2d..45ffb257a 100644 --- a/settings_RPiHQ.json.repo +++ b/settings_RPiHQ.json.repo @@ -1,28 +1,33 @@ { - "width":"0", - "height":"0", - "autoexposure":"0", - "exposure":"60000", - "autogain":"0", - "gain":"4", + "nightexposure":"60000", + "nightautoexposure":"0", + "nightgain":"4", + "nightautogain":"0", "gamma":"50", "brightness":"50", "awb":"0", "wbr":"2.8", "wbb":"2", - "bin":"1", - "delay":"10", - "daytimeDelay": "30000", + "nightbin":"1", + "nightdelay":"10", + "daydelay": "30000", "quality":"95", + "width":"0", + "height":"0", "filename":"image.jpg", "rotation":"0", "flip":"2", + "text":"", + "showTime":"1", + "showDetails":"1", + "fontsize":"32", "fontcolor":"255", "background":"0", - "fontsize":"32", - "time":"1", - "showDetails":"1", + "notificationimages":"1", "latitude":"52.57N", "longitude":"4.7E", - "angle":"-9" + "angle":"-9", + "autofocus":"0", + "darkframe":"0", + "alwaysshowadvanced":"0" } diff --git a/settings_ZWO.json.repo b/settings_ZWO.json.repo index 15583b284..5780bf6f5 100644 --- a/settings_ZWO.json.repo +++ b/settings_ZWO.json.repo @@ -1,27 +1,43 @@ { + "dayautoexposure":"1", + "dayexposure":".5", + "daybrightness":"50", + "daydelay":"5000", + "daybin":"1", + "nightautoexposure":"1", + "nightmaxexposure":"20000", + "nightexposure":"10000", + "nightbrightness":"50", + "nightdelay":"10", + "nightautogain":"0", + "nightmaxgain":"200", + "nightgain":"50", + "gaintransitiontime":"15", + "nightbin":"1", "width":"0", "height":"0", - "exposure":"10000", - "maxexposure":"20000", - "autoexposure":"1", - "gain":"50", - "maxgain":"200", - "autogain":"0", - "coolerEnabled":"0", - "targetTemp":"0", + "type":"99", "gamma":"50", - "brightness":"50", + "autowhitebalance":"0", "wbr":"53", "wbb":"90", - "bin":"1", - "delay":"10", - "daytimeDelay": "5000", - "type":"1", "quality":"95", + "autousb":"0", "usb":"40", "filename":"image.jpg", "flip":"0", - "text":"text", + "showTime":"1", + "timeformat":"%Y%m%d %H:%M:%S" + "showTemp":"1", + "temptype":"C", + "showExposure":"1", + "showGain":"1", + "showBrightness":"0", + "showHistogram":"0", + "text":"", + "extratext":"", + "extratextage":"600", + "textlineheight":"60", "textx":"15", "texty":"30", "fontname":"0", @@ -31,10 +47,16 @@ "fontsize":"7", "fontline":"1", "outlinefont":"0", + "notificationimages":"1", + "coolerEnabled":"0", + "targetTemp":"0", "latitude":"60.7N", "longitude":"135.05W", "angle":"-6", - "time":"1", + "histogrambox":"500 500 50 50", + "showhistogrambox":"0", "darkframe":"0", - "showDetails":"1" + "locale":"en_US.UTF-8", + "debuglevel":"1", + "alwaysshowadvanced":"1" }