def config_whitelist():
    """
    Serves information to fill in the interface for changing the dfnstation.cfg file.

    Returns:
        outDict (dict): Format::

            {param : value}
    """
    white_list = constants.configBoxWhitelist
    path = constants.dfnconfigPath
    conf_dict = dfn_functions.load_config(path)
    result_dict = {}

    for whitelist_category in white_list:
        for conf_category in conf_dict:
            if whitelist_category == conf_category:
                for whitelist_field in white_list[whitelist_category]:
                    for conf_field in conf_dict[conf_category]:
                        if whitelist_field == conf_field:
                            if not conf_category in result_dict:
                                result_dict[conf_category] = {}

                            result_dict[conf_category][conf_field] = conf_dict[
                                conf_category][conf_field]

    return result_dict
예제 #2
0
def update_config_file(inProperty):
    """
    Updates the dfnstation.cfg file with a new value for a parameter.

    Args:
        inProperty (json): JSON object representing a config. Format::

            {param : value}

    Returns:
        consoleFeedback (str): Resulting console feedback.
    """
    consoleFeedback = constants.configWriteFailed
    path = constants.dfnconfigPath
    updated_conf_dict = dfn_functions.load_config(path)

    for key in inProperty:
        parsed = key.split("] ")
        property_category = parsed[0].replace("[", "")
        property_field = parsed[1]

        updated_conf_dict[property_category][property_field] = inProperty[key]
        consoleFeedback = constants.configWritePassed.format(key, inProperty[key])

    dfn_functions.save_config_file("dfnstation.cfg", updated_conf_dict)

    return consoleFeedback
def config_file():
    """
    Serves the config file in full.
    """
    path = constants.dfnconfigPath
    config_file = dfn_functions.load_config(path)

    if not config_file:
        raise IOError('Cannot load config file with path: {0}'.format(path))

    return config_file
예제 #4
0
def populateConfigBox():
    white_list = constants.configBoxWhitelist
    path = constants.dfnconfigPath

    conf_dict = dfn_functions.load_config(path)
    result_dict = {}

    for whitelist_category in white_list:
        for conf_category in conf_dict:
            if whitelist_category == conf_category:
                for whitelist_field in white_list[whitelist_category]:
                    for conf_field in conf_dict[conf_category]:
                        if whitelist_field == conf_field:
                            result_dict["[" + conf_category + "] " +
                                        conf_field] = conf_dict[conf_category][
                                            conf_field]
    return result_dict
예제 #5
0
def updateConfigFile(inProperty):
    print('inprop, ', inProperty)
    consoleFeedback = constants.configWriteFailed
    path = constants.dfnconfigPath
    updated_conf_dict = dfn_functions.load_config(path)

    parsed = inProperty['key'].split("] ")
    print('parsesd, ', parsed)
    property_category = parsed[0].replace("[", "")
    property_field = parsed[1]

    updated_conf_dict[property_category][property_field] = str(
        inProperty['value'])
    consoleFeedback = constants.configWritePassed.format(
        inProperty['key'], inProperty['value'])

    dfn_functions.save_config_file(r"/opt/dfn-software/dfnstation.cfg",
                                   updated_conf_dict)

    return consoleFeedback
def update_config_file(category, field, value):
    """
    Updates the dfnstation.cfg file with a new value for a parameter.

    Args:
        category (str): A config properties category
        field (str): A config properties field
        value (str): A config properties value

    Returns:
        consoleFeedback (str): Resulting console feedback.
    """
    path = constants.dfnconfigPath
    updated_conf_dict = dfn_functions.load_config(path)

    oldValue = updated_conf_dict[category][field]
    updated_conf_dict[category][field] = value

    if dfn_functions.save_config_file(path, updated_conf_dict):
        return 'Overwritten {0}:{1}:{2} as {3}'.format(category, field,
                                                       oldValue, value)
    else:
        raise IOError('Unable to write {0}:{1}:{2} to config file'.format(
            category, field, value))
예제 #7
0
def main_function(test_time=0):
    """Takes one night worth of images with processing at sunrise. 
    
       test_time -- the length for a test, 0 is no testing. (default 0)    
    """

    # Initialise variables.
    job_list = []

    # Get program directory.
    prog_dir = os.path.dirname(os.path.abspath(__file__))

    # Load new config file from master copy in /opt/dfn-software.
    config_file = os.path.join(prog_dir, r'dfnstation.cfg')
    config_dict = dfn.load_config(config_file)
    config_dict['internal']['config_file'] = config_file

    # Setup new data path for this night.
    data_path = dfn.make_data_path(config_dict['internal']['data_directory'])

    # TIP: Setup logger.
    # Set lowest log level for testing.
    if test_time != 0:
        log_level = logging.DEBUG
    else:
        log_level = logging.INFO

    # Set path, format and identity for logging.
    log_file = os.path.join(data_path, dfn.log_name() + 'interval.txt')
    tether_file = os.path.join(data_path, dfn.log_name() + 'tether.txt')
    formatter = logging.Formatter(
        '%(asctime)s, %(levelname)s, %(module)s, %(message)s')
    logger = logging.getLogger()

    # Remove any pre-existing handlers.
    if len(logger.handlers) != 0:
        for hdl in logger.handlers[:]:
            hdl.stream.close()
            logger.removeHandler(hdl)

    # Begin handler that records logs in /data0/latest.
    fh = logging.FileHandler(log_file)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(log_level)

    # Provide logger details and stream logging to stdout for testing.
    if test_time != 0:
        print('logger_handler_count, ' + str(len(logger.handlers)))
        print('logfile, ' + log_file)
        logger.addHandler(logging.StreamHandler())
        logger.info('testing')

    # Report interval control version.
    logger.info('interval_control_version, ' + VERSION)

    # Initialise microcontroller.
    ser = leo.connect_to_leostick()
    leo.shutter_off()
    leo.wait_for_camera_ready()
    leo.camera_off()
    leo.video_off()

    # Set bulb mode in microcontroller based on config.
    if config_dict['camera']['exp_mode'] == 'BULB':
        leo.set_bulb_mode()
        logger.info('bulb_mode')
    else:
        leo.set_non_bulb_mode()
        logger.info('non_bulb_mode')

    # Find latest image mask for the dslr and make a local copy.
    maskfile = dfn.get_mask(config_dict['internal']['data_directory'])
    shutil.copy(maskfile, data_path)
    logger.debug('mask_copied, ' + data_path)
    logger.info('mask, ' + maskfile)

    # FIXME: Is this still needed if processing is a daemon?
    # Make the transfer file early to allow background event detection.
    transfer_status_file = os.path.join(data_path, r'transfer_status.txt')
    dfn.write_string_to_file('unprocessed\n', transfer_status_file, mode='wt')
    logger.debug('transfer_status_file_written, unprocessed')

    # Get new gps location, if available and report lock.
    (config_dict['station']['lon'], config_dict['station']['lat'],
     config_dict['station']['altitude'],
     config_dict['station']['gps_lock']) = leo.update_GPS_location(
         config_dict['station']['lon'], config_dict['station']['lat'],
         config_dict['station']['altitude'])
    logger.info('GPS_lonlat, ' + str(config_dict['station']['lon']) + ', ' +
                str(config_dict['station']['lat']) + ', ' +
                str(config_dict['station']['altitude']) + ', ' +
                str(config_dict['station']['gps_lock']))

    # TIP: Calculate sunset and sunrise in localtime.
    sunrise, sunset, moonrise, moonset = sm.generate_sun_and_moon(
        config_dict['station']['lon'], config_dict['station']['lat'])
    sunset += datetime.timedelta(
        minutes=float(config_dict['internal']['sun_leeway']))
    sunrise -= datetime.timedelta(
        minutes=float(config_dict['internal']['sun_leeway']))
    sunset_after_twilight = sunset + datetime.timedelta(minutes=10)
    sunrise_before_twilight = sunrise - datetime.timedelta(minutes=10)

    # Set dummy values for testing.
    if test_time != 0:
        sunset = datetime.datetime.now() + datetime.timedelta(seconds=30)
        sunrise = sunset + datetime.timedelta(seconds=test_time)
        sunset_after_twilight = sunset
        sunrise_before_twilight = sunrise
    logger.info('sunset, ' + str(sunset.isoformat()))
    logger.info('sunset_after_twilight, ' +
                str(sunset_after_twilight.isoformat()))
    logger.info('sunrise_before_twilight, ' +
                str(sunrise_before_twilight.isoformat()))
    logger.info('sunrise, ' + str(sunrise.isoformat()))
    logger.info('now, ' + str(datetime.datetime.now()))
    logger.info('UTCnow, ' + str(datetime.datetime.utcnow()))
    logger.info('timezone, ' + str(time.timezone))

    # Handle daylight savings.
    if time.daylight != 0:
        logger.info('altzone, ' + str(time.altzone))

    # Handle missed sunset, force an immediate start.
    if sunset >= sunrise:
        logger.info('late_start-forcing_immediate')
        sunset = datetime.datetime.now() + datetime.timedelta(seconds=30)

    # Use cal.mktime not timegm as sunrise is local datetime object.
    sunrise_epoch = time.mktime(datetime.date.timetuple(sunrise))
    sunset_epoch = time.mktime(datetime.date.timetuple(sunset))

    # Convert back again for testing.
    sunrise_recalc_test = time.localtime(sunrise_epoch)
    logger.debug('recalc_sunrise, ' + str(sunrise_epoch) + ', ' +
                 str(sunrise_recalc_test))

    # TIP: Wait until sunset.
    while datetime.datetime.now() < sunset:
        logger.debug('waiting_for_sunset, ' +
                     datetime.datetime.now().isoformat())
        print('waiting_for_sunset, ' + datetime.datetime.now().isoformat())
        time.sleep(30)

        # If no lock try again.
        if config_dict['station']['gps_lock'] == 'N' and test_time == 0:
            (config_dict['station']['lon'], config_dict['station']['lat'],
             config_dict['station']['altitude'],
             config_dict['station']['gps_lock']) = leo.update_GPS_location(
                 config_dict['station']['lon'], config_dict['station']['lat'],
                 config_dict['station']['altitude'])

            # If new lock handle coordinates and recalculate timing.
            if config_dict['station']['gps_lock'] != 'N':
                logger.info('GPS_lonlat, ' +
                            str(config_dict['station']['lon']) + ', ' +
                            str(config_dict['station']['lat']) + ', ' +
                            str(config_dict['station']['altitude']) + ', ' +
                            str(config_dict['station']['gps_lock']))

                # Recalculate sunset and sunrise in localtime.
                sunrise, sunset, moonrise, moonset = sm.generate_sun_and_moon(
                    config_dict['station']['lon'],
                    config_dict['station']['lat'])
                sunset += datetime.timedelta(
                    minutes=float(config_dict['internal']['sun_leeway']))
                sunrise -= datetime.timedelta(
                    minutes=float(config_dict['internal']['sun_leeway']))
                sunset_after_twilight = sunset + datetime.timedelta(minutes=10)
                sunrise_before_twilight = sunrise - datetime.timedelta(
                    minutes=10)

                # Set dummy values for testing.
                if test_time != 0:
                    sunset = datetime.datetime.now() + datetime.timedelta(
                        seconds=30)
                    sunrise = sunset + datetime.timedelta(seconds=test_time)
                    sunset_after_twilight = sunset
                    sunrise_before_twilight = sunrise
                logger.info('sunset, ' + str(sunset.isoformat()))
                logger.info('sunset_after_twilight, ' +
                            str(sunset_after_twilight.isoformat()))
                logger.info('sunrise_before_twilight, ' +
                            str(sunrise_before_twilight.isoformat()))
                logger.info('sunrise, ' + str(sunrise.isoformat()))
                logger.info('now, ' + str(datetime.datetime.now()))
                logger.info('UTCnow, ' + str(datetime.datetime.utcnow()))
                logger.info('timezone, ' + str(time.timezone))

                # Handle daylight savings.
                if time.daylight != 0:
                    logger.info('altzone, ' + str(time.altzone))

                # Handle missed sunset, force an immediate start.
                if sunset >= sunrise:
                    logger.info('late_start-forcing_immediate')
                    sunset = datetime.datetime.now() + datetime.timedelta(
                        seconds=30)

                # Use cal.mktime not timegm as sunrise is local datetime object.
                sunrise_epoch = time.mktime(datetime.date.timetuple(sunrise))
                sunset_epoch = time.mktime(datetime.date.timetuple(sunset))

                # Convert back again for testing
                sunrise_recalc_test = time.localtime(sunrise_epoch)
                logger.debug('recalc_sunrise, ' + str(sunrise_epoch) + ', ' +
                             str(sunrise_recalc_test))
    # Report sunset time.
    logger.debug('sunset_now, ' + str(datetime.datetime.now()))

    # Get initial status, versions, temperature, etc.
    temperature = leo.get_temperature()
    logger.info('leostick_temperature, ' + str(temperature))
    time.sleep(1)
    leo_version = leo.get_version()
    logger.info('leostick_version, ' + str(leo_version))
    time.sleep(1)
    leo_sequence = leo.get_sequence()
    logger.info('leostick_sequence, ' + str(leo_sequence))
    time.sleep(1)
    leo_debug = leo.get_debug_codes()
    logger.info('leostick_debug, ' + str(leo_debug))
    time.sleep(1)
    logger.info('cloud_file, ' + config_dict['internal']['cloudy_img_file'])
    logger.info('HD_temperature, ' + str(dfn.disk_temperature()))
    logger.info('today_date, ' + dfn.today())
    logger.info('data_path, ' + data_path)
    logger.info('test_time, ' + str(test_time))
    mem_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    logger.info('memory, ' + "{:,}".format(mem_use))
    for item in dfn.get_ntp_data():
        logger.info('ntp, ' + str(item))

    # Switch on hardware.
    # FIXME: Handle point grey video camera.
    time.sleep(10)
    if os.path.exists(r'/dev/video0'):
        config_dict['camera']['video_device_exists'] = 1
        logger.info('video_device_found')
        if str(config_dict['camera']['video_enabled']) != '0':
            if test_time == 0:  #start up video cloud daemon
                logger.info('spawning_video')
                cc.spawn_video_command(sunrise_epoch)
            leo.video_on()
            logger.info('video_on_ok')
        else:
            logger.info('video_not_enabled')
    else:
        config_dict['camera']['video_device_exists'] = 0
        logger.info('video_not_exist')

    # Switch on camera and initialise settings.
    leo.camera_on()
    logger.info('camera_on_ok')
    os.chdir(data_path)
    cam.camera_download_images()
    dfn.rename_RAW_all(data_path, config_dict)
    cam.camera_set_time()
    time.sleep(1)
    logger.info('camera_time_get, ' + cam.camera_get_time())
    cam.camera_set_program()  # manual exp mode
    cam.camera_set_autoiso(1)  # autoiso off
    cam.camera_set_highisonr()
    cam.camera_set_longexpnr()
    cam.camera_set_vignette()
    cam.camera_set_fstop(config_dict['camera']['camera_fstop'])

    # Switch on condensation fan.
    leo.cond_on()

    # FIXME: This can be removed for the DFNEXT.
    if 'firmware_control' in config_dict:
        if (config_dict['firmware_control']['heater_enabled'] == '1'
                or config_dict['firmware_control']['heater_enabled'] == 1
                or config_dict['firmware_control']['heater_enabled'] == True):
            leo.heater_on(
                config_dict['firmware_control']['heater_temperature_C'])
    config_dict['camera']['shutterspeed'] = cam.get_camera_shutterspeed()

    # Save a local copy of the config file.
    if dfn.save_config_file(os.path.join(data_path, 'dfnstation.cfg'),
                            config_dict):
        logger.info('new_conf_file_written')
    else:
        logger.warning('new_conf_file_write_error')
    logger.info('location, ' + config_dict['station']['location'])

    # Set interval time from config unless testing.
    interval_time = str(int(config_dict['clouds']['time_checking_clear']))
    interval_time.rstrip(
        's')  #XXX: Is this required? Already typecast to int...

    # Set fixed interval length, tests will run for
    # config time + n * interval_time until passed test_time.
    if test_time != 0:
        interval_time = '180'

    # Set directory.
    current_dir = data_path
    os.chdir(current_dir)

    # Start with incorrect value to force immediate mode change
    cloud_status_internal = -3

    # TIP: Evening Twilight _______________________________________________________________________
    if test_time == 0:
        logger.info('twilight_evening_settings')

        cam.camera_set_quality(JPG_MODE)

        # TODO: Remove config check and ensure defaults in config.
        if 'twilight_exposuretime' in config_dict['camera']:
            cam.camera_set_shutter(
                config_dict['camera']['twilight_exposuretime'])
        else:
            cam.camera_set_shutter(
                config_dict['camera']['camera_exposuretime'])
        if 'twilight_iso' in config_dict['camera']:
            cam.camera_set_iso(config_dict['camera']['twilight_iso'])
        else:
            cam.camera_set_iso(config_dict['camera']['camera_iso'])

        logger.info('twilight_evening_starting')

        # FIXME: Evening Tether! This needs to be tested, needs to call in the background.
        with open(os.devnull, 'w') as shutup:
            try:
                # Calculate number of seconds in twilight for tethering.
                evening_twilight_seconds = int(
                    (sunset_after_twilight -
                     datetime.datetime.now()).total_seconds())
                subprocess.Popen([
                    'gphoto2', '--capture-tethered',
                    str(evening_twilight_seconds) + 's', '--force-overwrite'
                ],
                                 stderr=shutup,
                                 close_fds=True)
                logger.info('evening_twilight_tether_starting ' +
                            str(evening_twilight_seconds) + 's')
            except subprocess.CalledProcessError as e:
                logger.warning('argh-problem_starting_tether, ' + str(e))
                print('evening_twilight_tether_err, ' +
                      str(datetime.datetime.now()) + ', ' + str(e))
                res = subprocess.call(['gphoto2', '--reset'], stderr=shutup)
                logger.info('gphoto_reset, ' + str(res))

        while datetime.datetime.now() < sunset_after_twilight:
            imgfile = high_acq(current_dir, interval_time, config_dict)
            handle_new_image(imgfile, job_list, current_dir, config_dict)

    else:
        logger.debug('evening_twilight_not_called')

    # TIP: Night __________________________________________________________________________________
    logger.info('night_settings')

    cam.camera_set_quality(RAW_MODE)

    cam.camera_set_shutter(config_dict['camera']['camera_exposuretime'])
    cam.camera_set_iso(config_dict['camera']['camera_iso'])

    logger.info('night_starting')

    # FIXME: Night Tether! This needs to be tested, needs to call in the background.

    with open(tether_file, 'w') as shutup:
        try:
            # Calculate number of seconds in night for tethering.
            night_seconds = int((sunrise_before_twilight -
                                 datetime.datetime.now()).total_seconds())
            tether = subprocess.Popen(
                ['gphoto2', '--capture-tethered', '--force-overwrite'],
                stderr=shutup,
                close_fds=True)
            logger.info('night_tether_starting, ' + str(night_seconds) + 's')
        except subprocess.CalledProcessError as e:
            logger.warning('argh-problem_starting_tether, ' + str(e))
            print('night_tether_err, ' + str(datetime.datetime.now()) + ', ' +
                  str(e))
            res = subprocess.call(['gphoto2', '--reset'], stderr=shutup)
            logger.info('gphoto_reset, ' + str(res))

    while datetime.datetime.now() < sunrise_before_twilight:
        # Collect and report current cloud status.
        cloud_status = cc.read_cloud_status(
            config_dict['internal']['cloud_status_file'])
        print(datetime.datetime.now(),
              'sunrise, ' + str(sunrise) + ', ' + str(cloud_status))
        logger.debug('cloud_status, ' + str(cloud_status) + ', ' +
                     str(cloud_status_internal))

        # Force status CLEAR for testing.
        if test_time != 0:
            logger.debug('testing_force_clear')
            cloud_status = CLEAR

        # Select acquisition based on cloud status.
        if cloud_status == CLEAR:
            if cloud_status_internal != CLEAR:
                logger.info('Gone_clear, ' + str(cloud_status))
            imgfile = high_acq(current_dir, interval_time, config_dict)
        elif cloud_status == CLEARING:
            if cloud_status_internal != CLEARING:
                logger.info('Gone_clearing, ' + str(cloud_status))
            imgfile = low_acq(current_dir, cloud_status, config_dict)
        elif cloud_status == CLOUDY:
            logger.info('Gone_cloudy, ' + str(cloud_status))
            imgfile = low_acq(current_dir, cloud_status, config_dict)
        else:
            if cloud_status_internal in (CLEAR, CLEARING, CLOUDY):
                logger.info('Gone_undefined_cloud_status, ' +
                            str(cloud_status))
            else:
                logger.info('Still_undefined_cloud_status, ' +
                            str(cloud_status))
                # TODO: Incorporate uncertainty measures for undefined status.
            imgfile = high_acq(current_dir, interval_time, config_dict)
        cloud_status_internal = cloud_status

    time.sleep(5)

    print(str(tether.poll()))
    if tether.poll() is None:
        logger.debug('Tether_not_killed, ' + str(tether.pid))
    else:
        logger.debug('Tether_killed, ' + str(tether.pid))

    tether.terminate()
    tether.kill()

    print(str(tether.poll()))
    if tether.poll() is None:
        logger.debug('Tether_not_killed, ' + str(tether.pid))
    else:
        logger.debug('Tether_killed, ' + str(tether.pid))

    # TIP: Morning Twilight _______________________________________________________________________
    if test_time == 0:
        logger.info('twilight_morning_settings')
        cam.camera_set_quality(JPG_MODE)
        # TODO: Remove config check and ensure defaults in config.
        if 'twilight_exposuretime' in config_dict['camera']:
            cam.camera_set_shutter(
                config_dict['camera']['twilight_exposuretime'])
        else:
            cam.camera_set_shutter(
                config_dict['camera']['camera_exposuretime'])
        if 'twilight_iso' in config_dict['camera']:
            cam.camera_set_iso(config_dict['camera']['twilight_iso'])
        else:
            cam.camera_set_iso(config_dict['camera']['camera_iso'])

        logger.info('twilight_morning_start')

        # Morning Tether! Start gphoto tether in background until sunrise.
        with open(os.devnull, 'w') as shutup:
            try:
                # Calculate number of seconds in night for tethering.
                morning_twilight_seconds = int(
                    (sunrise - datetime.datetime.now()).total_seconds())

                subprocess.Popen([
                    'gphoto2', '--capture-tethered',
                    str(morning_twilight_seconds) + 's', '--force-overwrite'
                ],
                                 stderr=shutup,
                                 close_fds=True)
                logger.info('morning_twilight_tether_starting ' +
                            str(morning_twilight_seconds) + 's')
            except subprocess.CalledProcessError as e:
                logger.warning('argh-problem_starting_tether, ' + str(e))
                print('morning_twilight_tether_err, ' +
                      str(datetime.datetime.now()) + ', ' + str(e))
                res = subprocess.call(['gphoto2', '--reset'], stderr=shutup)
                logger.info('gphoto_reset, ' + str(res))

        while datetime.datetime.now() < sunrise:
            imgfile = high_acq(current_dir, interval_time, config_dict)
            handle_new_image(imgfile, job_list, current_dir, config_dict)
    else:
        logger.debug('morning_twilight_not_called')

    # TIP: Sunrise
    logger.info('sunrise')

    # Shutdown hardware.
    leo.shutter_off()
    if str(config_dict['camera']['video_device_exists']) == '1':
        leo.video_off()
        logger.info('video_off_ok')

    # Wait for last shutter exposure to finish.
    leo.wait_for_camera_ready()

    # Wait for camera to record last image from buffer.
    time.sleep(10)
    for job in job_list:
        job.join()
    logger.info('finished_outstanding_tasks')
    os.chdir(data_path)

    # Clear any stray images from memory card.
    cam.camera_download_images()

    leo.camera_off()
    logger.info('camera_off_ok')
    leo.cond_off()

    ser.close()

    # Clean up images.
    renamed_images = dfn.rename_RAW_all(data_path, config_dict)
    logger.info('rename_RAW_all_ok, ' + str(len(renamed_images)))
    if str(config_dict['internal']['clearing_quality']) == '2':
        dfn.make_all_thumb(data_path)
        logger.info('make_all_thumb_ok')
    else:
        logger.debug('make_all_thumb_not_called')

    # Get a rough shutter count just listdir then sort by cdate.
    raw_list = [
        os.path.join(data_path, a) for a in os.listdir(data_path)
        if (a.lower().endswith('.nef') or a.lower().endswith('.cr2'))
    ]

    # Check there are actually images.
    if len(raw_list) > 1:
        last_image = sorted(
            raw_list,
            key=lambda x: os.stat(os.path.join(data_path, x)).st_mtime)[-1]
        logger.info('shuttercount, ' + str(dfn.image_shuttercount(last_image)))

    # Report memory usage.
    mem_use = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
    logger.info('memory, ' + "{:,}".format(mem_use))
    logger.info('today_disk_usage, ' +
                "{:,}".format(dfn.disk_usage(data_path)))
    for item in dfn.get_ntp_data():
        logger.info('ntp, ' + str(item))
    time.sleep(60)

    # Close down logging and exit.
    if test_time == 0:
        if ((not 'enabled' in config_dict['event_detect'])
                or (config_dict['event_detect']['enabled'] != '0'
                    and config_dict['event_detect']['enabled'] != 'N')):
            logger.info('exiting_interval_control_calling_processing')

            # Camera systems do a reboot at 1615 localtime.
            reboot_time = dfn.get_reboot_time()
            reboot_time_epoch = time.mktime(
                datetime.date.timetuple(reboot_time))
            os.execvp(sys.executable, [
                sys.executable, r'/opt/dfn-software/processing_wrapper.py',
                '/data0/latest',
                str(reboot_time_epoch), '012346'
            ])
        else:
            logger.info('exiting_interval_control_processing_not_enabled')
    else:
        logger.info('finished_interval_control_night_end_test')

    sys.stdout.flush()
    sys.stderr.flush()
    logging.shutdown()
    return True