diff --git a/gps/src/nmeaparser.c b/gps/src/nmeaparser.c index a046273..a24b20c 100644 --- a/gps/src/nmeaparser.c +++ b/gps/src/nmeaparser.c @@ -169,7 +169,7 @@ DEFINE_FUNCTION_ELSE(GGA, 54, ISVALID, XOR_CALCULATED_CHECKSUM, IS('*'), INC_STA DEFINE_FUNCTION_STEP(GGA, 55, ISHEXDIGIT, INC_STATE; SET_RECEIVED_CHECKSUM(16 * HEXVALUE)) DEFINE_FUNCTION_STEP(GGA, 56, ISHEXDIGIT, ZERO_STATE; XOR_RECEIVED_CHECKSUM(HEXVALUE); TEST_CHECKSUM_AND_SET_STATUS) -void (*GGAfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultGGA_t*) = {GGA00, GGA01, GGA02, GGA03, GGA04, GGA05, GGA06, GGA07, \ +void (*const GGAfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultGGA_t*) = {GGA00, GGA01, GGA02, GGA03, GGA04, GGA05, GGA06, GGA07, \ GGA08, GGA09, GGA10, GGA11, GGA12, GGA13, GGA14, GGA15, \ GGA16, GGA17, GGA18, GGA19, GGA20, GGA21, GGA22, GGA23, \ GGA24, GGA25, GGA26, GGA27, GGA28, GGA29, GGA30, GGA31, \ @@ -237,7 +237,7 @@ DEFINE_FUNCTION_ELSE(RMC, 53, ISVALID, XOR_CALCULATED_CHECKSUM, IS('*'), INC_STA DEFINE_FUNCTION_STEP(RMC, 54, ISHEXDIGIT, INC_STATE; SET_RECEIVED_CHECKSUM(16 * HEXVALUE)) DEFINE_FUNCTION_STEP(RMC, 55, ISHEXDIGIT, ZERO_STATE; XOR_RECEIVED_CHECKSUM(HEXVALUE); TEST_CHECKSUM_AND_SET_STATUS) -void (*RMCfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultRMC_t*) = {RMC00, RMC01, RMC02, RMC03, RMC04, RMC05, RMC06, RMC07, \ +void (*const RMCfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultRMC_t*) = {RMC00, RMC01, RMC02, RMC03, RMC04, RMC05, RMC06, RMC07, \ RMC08, RMC09, RMC10, RMC11, RMC12, RMC13, RMC14, RMC15, \ RMC16, RMC17, RMC18, RMC19, RMC20, RMC21, RMC22, RMC23, \ RMC24, RMC25, RMC26, RMC27, RMC28, RMC29, RMC30, RMC31, \ @@ -252,7 +252,7 @@ DEFINE_FUNCTION_ELSE(DEFAULT, 01, ISVALID, XOR_CALCULATED_CHECKSUM; ADD_TO_BUFFE DEFINE_FUNCTION_STEP(DEFAULT, 02, ISHEXDIGIT, INC_STATE; SET_RECEIVED_CHECKSUM(16 * HEXVALUE)) DEFINE_FUNCTION_STEP(DEFAULT, 03, ISHEXDIGIT, ZERO_STATE; XOR_RECEIVED_CHECKSUM(HEXVALUE); TEST_CHECKSUM_AND_SET_STATUS) -void (*DEFAULTfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultDEFAULT_t*) = {DEFAULT00, DEFAULT01, DEFAULT02, DEFAULT03}; +void (*const DEFAULTfunctions[])(char, NMEA_parserState_t*, NMEA_parserResultDEFAULT_t*) = {DEFAULT00, DEFAULT01, DEFAULT02, DEFAULT03}; /* Define parsers */ diff --git a/inc/digitalFilter.h b/inc/digitalFilter.h index e8b161e..89c557a 100644 --- a/inc/digitalFilter.h +++ b/inc/digitalFilter.h @@ -18,9 +18,9 @@ typedef enum {DF_BAND_PASS_FILTER, DF_HIGH_PASS_FILTER} DF_filterType_t; void DigitalFilter_reset(); -void DigitalFilter_applyAdditionalGain(float gain); +bool DigitalFilter_applyFilter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size); -bool DigitalFilter_filter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size, uint16_t amplitudeThreshold); +bool DigitalFilter_applyFrequencyTrigger(int16_t *source, uint32_t size); /* Design filters */ @@ -28,6 +28,14 @@ void DigitalFilter_designHighPassFilter(uint32_t sampleRate, uint32_t freq); void DigitalFilter_designBandPassFilter(uint32_t sampleRate, uint32_t freq1, uint32_t freq2); +/* Set filter options */ + +void DigitalFilter_setAdditionalGain(float gain); + +void DigitalFilter_setAmplitudeThreshold(uint16_t amplitudeThreshold); + +void DigitalFilter_setFrequencyTrigger(uint32_t windowLength, uint32_t sampleRate, uint32_t frequency, float percentageThreshold); + /* Read back filter setting */ void DigitalFilter_readSettings(float *gain, float *yc0, float *yc1, DF_filterType_t *filterType); diff --git a/src/digitalFilter.c b/src/digitalFilter.c index bd4c3a8..647139c 100644 --- a/src/digitalFilter.c +++ b/src/digitalFilter.c @@ -5,28 +5,35 @@ *****************************************************************************/ #include +#include #include #include #include #include "digitalFilter.h" -/* Useful macros */ +/* Filter design constants */ #ifndef M_PI -#define M_PI 3.14159265358979323846f +#define M_PI 3.14159265358979323846f #endif #ifndef M_TWOPI -#define M_TWOPI (2.0f * M_PI) +#define M_TWOPI (2.0f * M_PI) #endif -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MAX_POLES 2 -/* Filter design constants */ +/* Goertzel filter constants */ + +#define MAXIMUM_HAMMING_WINDOW_LENGTH 1024 -#define MAX_POLES 2 +#define MINIMUM_NUMBER_OF_ITERATIONS 16 + +/* Useful macros */ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) /* Filter global variables */ @@ -50,6 +57,20 @@ static complex float zzeros[MAX_POLES]; static complex float topcoeffs[MAX_POLES + 1]; static complex float botcoeffs[MAX_POLES + 1]; +/* Amplitude threshold variable */ + +static uint16_t amplitudeThreshold; + +/* Goertzel filter variables */ + +static float hammingWindow[MAXIMUM_HAMMING_WINDOW_LENGTH]; + +static uint32_t goertzelFilterWindowLength; + +static float goertzelFilterThreshold; + +static float goertzelFilterConstant; + /* Static filter design functions */ static complex float blt(complex float pz) { @@ -132,107 +153,269 @@ static inline float applyBandPassFilter(float sample) { } -static inline void writeFilteredOutput(int16_t *dest, uint32_t index, float filterOutput, bool *exceededAmplitudeThreshold, uint32_t amplitudeThreshold) { +/* General filter routine */ - /* Apply output range limits */ +static bool filter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size) { - if (filterOutput > INT16_MAX) { + uint32_t index = 0; - filterOutput = INT16_MAX; + bool exceededThreshold = false; - } else if (filterOutput < -INT16_MAX) { + for (uint32_t i = 0; i < size; i += sampleRateDivider) { - filterOutput = -INT16_MAX; + float sample = 0.0f; - } + for (uint32_t j = 0; j < sampleRateDivider; j += 1) { - /* Check if amplitude threshold is exceeded */ + sample += source[i + j]; - if (fabsf(filterOutput) >= amplitudeThreshold) { + } - *exceededAmplitudeThreshold = true; + float filterOutput; - } + if (filterType == DF_HIGH_PASS_FILTER) { + + filterOutput = applyHighPassFilter(sample); + + } else { + + filterOutput = applyBandPassFilter(sample); + + } + + /* Apply output range limits */ + + if (filterOutput > INT16_MAX) { - /* Write the output value */ + filterOutput = INT16_MAX; - dest[index] = (int16_t)filterOutput; + } else if (filterOutput < -INT16_MAX) { + + filterOutput = -INT16_MAX; + + } + + /* Check if amplitude threshold is exceeded */ + + if (fabsf(filterOutput) >= amplitudeThreshold) { + + exceededThreshold = true; + + } + + /* Write the output value */ + + dest[index++] = (int16_t)filterOutput; + + } + + return exceededThreshold; } -/* General filter routine */ +/* Fast filter routine for when 250kHz and 384kHz and sampleRateDivider is not needed */ -static bool filter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size, uint16_t amplitudeThreshold) { +static bool fastFilterWithGoertzelFilterThreshold(int16_t *source, int16_t *dest, uint32_t size) { uint32_t index = 0; - bool exceededAmplitudeThreshold = false; + bool exceededThreshold = false; - for (uint32_t i = 0; i < size; i += sampleRateDivider) { + if (filterType == DF_HIGH_PASS_FILTER) { - float sample = 0.0f; + for (uint32_t i = 0; i < size / goertzelFilterWindowLength; i += 1) { - for (uint32_t j = 0; j < sampleRateDivider; j += 1) { + float d1 = 0.0f; - sample += source[i + j]; + float d2 = 0.0f; - } + uint32_t hammingIndex = 0; - float filterOutput; + for (uint32_t j = 0; j < goertzelFilterWindowLength / MINIMUM_NUMBER_OF_ITERATIONS; j += 1) { - if (filterType == DF_HIGH_PASS_FILTER) { + for (uint32_t k = 0; k < MINIMUM_NUMBER_OF_ITERATIONS; k += 1) { - filterOutput = applyHighPassFilter(sample); + float sample = source[index]; - } else { + float filterOutput = applyHighPassFilter(sample); - filterOutput = applyBandPassFilter(sample); + /* Apply output range limits */ + + if (filterOutput > INT16_MAX) { + + filterOutput = INT16_MAX; + + } else if (filterOutput < -INT16_MAX) { + + filterOutput = -INT16_MAX; + + } + + /* Update Goertzel filter */ + + float y = hammingWindow[hammingIndex++] * filterOutput + goertzelFilterConstant * d1 - d2; + + d2 = d1; + + d1 = y; + + /* Write the output value */ + + dest[index++] = (int16_t)filterOutput; + + } + + } + + float squaredMagnitude = d1 * d1 + d2 * d2 - goertzelFilterConstant * d1 * d2; + + if (squaredMagnitude > goertzelFilterThreshold) exceededThreshold = true; } - writeFilteredOutput(dest, index, filterOutput, &exceededAmplitudeThreshold, amplitudeThreshold); + } else { + + for (uint32_t i = 0; i < size / goertzelFilterWindowLength; i += 1) { + + float d1 = 0.0f; + + float d2 = 0.0f; + + uint32_t hammingIndex = 0; + + for (uint32_t j = 0; j < goertzelFilterWindowLength / MINIMUM_NUMBER_OF_ITERATIONS; j += 1) { + + for (uint32_t k = 0; k < MINIMUM_NUMBER_OF_ITERATIONS; k += 1) { + + float sample = source[index]; + + float filterOutput = applyBandPassFilter(sample); + + /* Apply output range limits */ + + if (filterOutput > INT16_MAX) { + + filterOutput = INT16_MAX; + + } else if (filterOutput < -INT16_MAX) { + + filterOutput = -INT16_MAX; - index += 1; + } + + /* Update Goertzel filter */ + float y = hammingWindow[hammingIndex++] * filterOutput + goertzelFilterConstant * d1 - d2; + + d2 = d1; + + d1 = y; + + /* Write the output value */ + + dest[index++] = (int16_t)filterOutput; + + } + + } + + float squaredMagnitude = d1 * d1 + d2 * d2 - goertzelFilterConstant * d1 * d2; + + if (squaredMagnitude > goertzelFilterThreshold) exceededThreshold = true; + + } + } - return exceededAmplitudeThreshold; + return exceededThreshold; } -/* Fast filter routine for when 250kHz and 384kHz and sampleRateDivider is not needed */ +static bool fastFilterWithAmplitudeThreshold(int16_t *source, int16_t *dest, uint32_t size) { -static bool fastFilter(int16_t *source, int16_t *dest, uint32_t size, uint16_t amplitudeThreshold) { + uint32_t index = 0; - bool exceededAmplitudeThreshold = false; + bool exceededThreshold = false; if (filterType == DF_HIGH_PASS_FILTER) { - for (uint32_t index = 0; index < size; index += 1) { + for (uint32_t i = 0; i < size / MINIMUM_NUMBER_OF_ITERATIONS; i += 1) { + + for (uint32_t j = 0; j < MINIMUM_NUMBER_OF_ITERATIONS; j += 1) { + + float sample = source[index]; + + float filterOutput = applyHighPassFilter(sample); + + /* Apply output range limits */ + + if (filterOutput > INT16_MAX) { + + filterOutput = INT16_MAX; + + } else if (filterOutput < -INT16_MAX) { + + filterOutput = -INT16_MAX; + + } + + /* Check if amplitude threshold is exceeded */ - float sample = source[index]; + if (fabsf(filterOutput) >= amplitudeThreshold) { - float filterOutput = applyHighPassFilter(sample); + exceededThreshold = true; - writeFilteredOutput(dest, index, filterOutput, &exceededAmplitudeThreshold, amplitudeThreshold); + } + + /* Write the output value */ + + dest[index++] = (int16_t)filterOutput; + + } } } else { - for (uint32_t index = 0; index < size; index += 1) { + for (uint32_t i = 0; i < size / MINIMUM_NUMBER_OF_ITERATIONS; i += 1) { + + for (uint32_t j = 0; j < MINIMUM_NUMBER_OF_ITERATIONS; j += 1) { + + float sample = source[index]; + + float filterOutput = applyBandPassFilter(sample); + + /* Apply output range limits */ + + if (filterOutput > INT16_MAX) { + + filterOutput = INT16_MAX; + + } else if (filterOutput < -INT16_MAX) { + + filterOutput = -INT16_MAX; - float sample = source[index]; + } - float filterOutput = applyBandPassFilter(sample); + /* Check if amplitude threshold is exceeded */ - writeFilteredOutput(dest, index, filterOutput, &exceededAmplitudeThreshold, amplitudeThreshold); + if (fabsf(filterOutput) >= amplitudeThreshold) { + + exceededThreshold = true; + + } + + /* Write the output value */ + + dest[index++] = (int16_t)filterOutput; + + } } } - return exceededAmplitudeThreshold; + return exceededThreshold; } @@ -248,11 +431,15 @@ void DigitalFilter_reset() { yv1 = 0.0f; yv2 = 0.0f; + amplitudeThreshold = 0; + + goertzelFilterThreshold = 0.0f; + } /* Update filter gain */ -void DigitalFilter_applyAdditionalGain(float g) { +void DigitalFilter_setAdditionalGain(float g) { gain *= g; @@ -260,18 +447,62 @@ void DigitalFilter_applyAdditionalGain(float g) { /* Apply digital filter */ -bool DigitalFilter_filter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size, uint16_t amplitudeThreshold) { +bool DigitalFilter_applyFilter(int16_t *source, int16_t *dest, uint32_t sampleRateDivider, uint32_t size) { if (sampleRateDivider == 1) { - return fastFilter(source, dest, size, amplitudeThreshold); + if (goertzelFilterThreshold > 0.0f) { + + return fastFilterWithGoertzelFilterThreshold(source, dest, size); + + } else { + + return fastFilterWithAmplitudeThreshold(source, dest, size); + + } } else { - return filter(source, dest, sampleRateDivider, size, amplitudeThreshold); + return filter(source, dest, sampleRateDivider, size); + + } + +} + +bool DigitalFilter_applyFrequencyTrigger(int16_t *source, uint32_t size) { + + uint32_t index = 0; + + for (uint32_t i = 0; i < size / goertzelFilterWindowLength; i += 1) { + + float d1 = 0.0f; + + float d2 = 0.0f; + + uint32_t hammingIndex = 0; + + for (uint32_t j = 0; j < goertzelFilterWindowLength / MINIMUM_NUMBER_OF_ITERATIONS; j += 1) { + + for (uint32_t k = 0; k < MINIMUM_NUMBER_OF_ITERATIONS; k += 1) { + + float y = hammingWindow[hammingIndex++] * (float)source[index++] + goertzelFilterConstant * d1 - d2; + + d2 = d1; + + d1 = y; + + } + + } + + float squaredMagnitude = d1 * d1 + d2 * d2 - goertzelFilterConstant * d1 * d2; + + if (squaredMagnitude > goertzelFilterThreshold) return true; } + return false; + } /* Design filters */ @@ -412,6 +643,36 @@ void DigitalFilter_designBandPassFilter(uint32_t sampleRate, uint32_t freq1, uin } +/* Set threshold options */ + +void DigitalFilter_setAmplitudeThreshold(uint16_t threshold) { + + amplitudeThreshold = threshold; + +} + +void DigitalFilter_setFrequencyTrigger(uint32_t windowLength, uint32_t sampleRate, uint32_t frequency, float percentageThreshold) { + + goertzelFilterThreshold = 0.0f; + + goertzelFilterWindowLength = windowLength; + + for (uint32_t i = 0; i < goertzelFilterWindowLength; i += 1) { + + hammingWindow[i] = 0.54f - 0.46f * cosf(M_TWOPI * (float)i / (float)(goertzelFilterWindowLength - 1)); + + goertzelFilterThreshold += hammingWindow[i]; + + } + + goertzelFilterThreshold *= (float)INT16_MAX / 2.0f; + + goertzelFilterThreshold = percentageThreshold >= 100.0f ? FLT_MAX : goertzelFilterThreshold * goertzelFilterThreshold * percentageThreshold / 100.0f * percentageThreshold / 100.0f; + + goertzelFilterConstant = 2.0f * cosf(M_TWOPI * (float)frequency / (float)sampleRate); + +} + /* Read back filter setting */ void DigitalFilter_readSettings(float *gainPtr, float *yc0Ptr, float *yc1Ptr, DF_filterType_t *filterTypePtr) { @@ -425,6 +686,3 @@ void DigitalFilter_readSettings(float *gainPtr, float *yc0Ptr, float *yc1Ptr, DF *filterTypePtr = filterType; } - - - diff --git a/src/main.c b/src/main.c index 8fe8186..053e9b0 100644 --- a/src/main.c +++ b/src/main.c @@ -41,6 +41,9 @@ #define WAITING_LED_FLASH_DURATION 10 #define WAITING_LED_FLASH_INTERVAL 2000 +#define MINIMUM_LED_FLASH_INTERVAL 500 + +#define SHORT_WAIT_INTERVAL 100 #define DEFAULT_WAIT_INTERVAL 1000 /* SRAM buffer constants */ @@ -109,30 +112,29 @@ /* Magnetic switch constants */ -#define MAGNETIC_SWITCH_WAIT_INTERVAL 500 #define MAGNETIC_SWITCH_WAIT_MULTIPLIER 2 - #define MAGNETIC_SWITCH_CHANGE_FLASHES 10 /* USB configuration constant */ #define USB_CONFIG_TIME_CORRECTION 26 -/* EM4 wake constant */ - -#define EM4_WAKEUP_PERIOD 43 - /* Recording preparation constants */ #define PREPARATION_PERIOD_INCREMENT 250 +#define MINIMUM_PREPARATION_PERIOD 750 #define INITIAL_PREPARATION_PERIOD 2000 -#define MINIMUM_PREPARATION_PERIOD 1000 #define MAXIMUM_PREPARATION_PERIOD 30000 /* Energy saver mode constant */ #define ENERGY_SAVER_SAMPLE_RATE_THRESHOLD 48000 +/* Frequency trigger constants */ + +#define FREQUENCY_TRIGGER_WINDOW_MINIMUM 16 +#define FREQUENCY_TRIGGER_WINDOW_MAXIMUM 1024 + /* Useful macros */ #define FLASH_LED(led, duration) { \ @@ -274,20 +276,34 @@ typedef struct { uint32_t latestRecordingTime; uint16_t lowerFilterFreq; uint16_t higherFilterFreq; - uint16_t amplitudeThreshold; + union { + uint16_t amplitudeThreshold; + uint16_t frequencyTriggerCentreFrequency; + }; uint8_t requireAcousticConfiguration : 1; AM_batteryLevelDisplayType_t batteryLevelDisplayType : 1; uint8_t minimumTriggerDuration : 6; - uint8_t enableAmplitudeThresholdDecibelScale : 1; - uint8_t amplitudeThresholdDecibels : 7; - uint8_t enableAmplitudeThresholdPercentageScale : 1; - uint8_t amplitudeThresholdPercentageMantissa : 4; - int8_t amplitudeThresholdPercentageExponent : 3; + union { + struct { + uint8_t frequencyTriggerWindowLengthShift : 4; + uint8_t frequencyTriggerThresholdPercentageMantissa : 4; + int8_t frequencyTriggerThresholdPercentageExponent : 3; + }; + struct { + uint8_t enableAmplitudeThresholdDecibelScale : 1; + uint8_t amplitudeThresholdDecibels : 7; + uint8_t enableAmplitudeThresholdPercentageScale : 1; + uint8_t amplitudeThresholdPercentageMantissa : 4; + int8_t amplitudeThresholdPercentageExponent : 3; + }; + }; uint8_t enableEnergySaverMode : 1; uint8_t disable48HzDCBlockingFilter : 1; uint8_t enableTimeSettingFromGPS : 1; uint8_t enableMagneticSwitch : 1; uint8_t enableLowGainRange : 1; + uint8_t enableFrequencyTrigger : 1; + uint8_t enableDailyFolders : 1; } configSettings_t; #pragma pack(pop) @@ -333,7 +349,9 @@ static const configSettings_t defaultConfigSettings = { .disable48HzDCBlockingFilter = 0, .enableTimeSettingFromGPS = 0, .enableMagneticSwitch = 0, - .enableLowGainRange = 0 + .enableLowGainRange = 0, + .enableFrequencyTrigger = 0, + .enableDailyFolders = 0 }; /* Persistent configuration data structure */ @@ -389,9 +407,11 @@ static void setHeaderDetails(wavHeader_t *wavHeader, uint32_t sampleRate, uint32 static void setHeaderComment(wavHeader_t *wavHeader, configSettings_t *configSettings, uint32_t currentTime, uint8_t *serialNumber, uint8_t *deploymentID, uint8_t *defaultDeploymentID, AM_extendedBatteryState_t extendedBatteryState, int32_t temperature, bool externalMicrophone, AM_recordingState_t recordingState, AM_filterType_t filterType) { + struct tm time; + time_t rawTime = currentTime + configSettings->timezoneHours * SECONDS_IN_HOUR + configSettings->timezoneMinutes * SECONDS_IN_MINUTE; - struct tm *time = gmtime(&rawTime); + gmtime_r(&rawTime, &time); /* Format artist field */ @@ -403,7 +423,7 @@ static void setHeaderComment(wavHeader_t *wavHeader, configSettings_t *configSet char *comment = wavHeader->icmt.comment; - comment += sprintf(comment, "Recorded at %02d:%02d:%02d %02d/%02d/%04d (UTC", time->tm_hour, time->tm_min, time->tm_sec, time->tm_mday, 1 + time->tm_mon, 1900 + time->tm_year); + comment += sprintf(comment, "Recorded at %02d:%02d:%02d %02d/%02d/%04d (UTC", time.tm_hour, time.tm_min, time.tm_sec, time.tm_mday, 1 + time.tm_mon, 1900 + time.tm_year); int8_t timezoneHours = configSettings->timezoneHours; @@ -471,26 +491,20 @@ static void setHeaderComment(wavHeader_t *wavHeader, configSettings_t *configSet comment += sprintf(comment, " and temperature was %s%lu.%luC.", sign, temperatureInDecidegrees / 10, temperatureInDecidegrees % 10); - bool amplitudeThresholdEnabled = configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; + bool frequencyTriggerEnabled = configSettings->enableFrequencyTrigger; - if (amplitudeThresholdEnabled) comment += sprintf(comment, " Amplitude threshold was "); + bool amplitudeThresholdEnabled = frequencyTriggerEnabled ? false : configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; - if (configSettings->enableAmplitudeThresholdDecibelScale && configSettings->enableAmplitudeThresholdPercentageScale == false) { + if (frequencyTriggerEnabled) { - comment += formatDecibels(comment, configSettings->amplitudeThresholdDecibels); + comment += sprintf(comment, " Frequency trigger (%u.%ukHz and window length of %u samples) threshold was ", configSettings->frequencyTriggerCentreFrequency / 10, configSettings->frequencyTriggerCentreFrequency % 10, (0x01 << configSettings->frequencyTriggerWindowLengthShift)); - } else if (configSettings->enableAmplitudeThresholdPercentageScale && configSettings->enableAmplitudeThresholdDecibelScale == false) { + comment += formatPercentage(comment, configSettings->frequencyTriggerThresholdPercentageMantissa, configSettings->frequencyTriggerThresholdPercentageExponent); - comment += formatPercentage(comment, configSettings->amplitudeThresholdPercentageMantissa, configSettings->amplitudeThresholdPercentageExponent); - - } else if (amplitudeThresholdEnabled) { - - comment += sprintf(comment, "%u", configSettings->amplitudeThreshold); + comment += sprintf(comment, " with %us minimum trigger duration.", configSettings->minimumTriggerDuration); } - if (amplitudeThresholdEnabled) comment += sprintf(comment, " with %us minimum trigger duration.", configSettings->minimumTriggerDuration); - uint16_t lowerFilterFreq = configSettings->lowerFilterFreq; uint16_t higherFilterFreq = configSettings->higherFilterFreq; @@ -509,6 +523,28 @@ static void setHeaderComment(wavHeader_t *wavHeader, configSettings_t *configSet } + if (amplitudeThresholdEnabled) { + + comment += sprintf(comment, " Amplitude threshold was "); + + if (configSettings->enableAmplitudeThresholdDecibelScale && configSettings->enableAmplitudeThresholdPercentageScale == false) { + + comment += formatDecibels(comment, configSettings->amplitudeThresholdDecibels); + + } else if (configSettings->enableAmplitudeThresholdPercentageScale && configSettings->enableAmplitudeThresholdDecibelScale == false) { + + comment += formatPercentage(comment, configSettings->amplitudeThresholdPercentageMantissa, configSettings->amplitudeThresholdPercentageExponent); + + } else { + + comment += sprintf(comment, "%u", configSettings->amplitudeThreshold); + + } + + comment += sprintf(comment, " with %us minimum trigger duration.", configSettings->minimumTriggerDuration); + + } + if (recordingState != RECORDING_OKAY) { comment += sprintf(comment, " Recording stopped"); @@ -543,6 +579,8 @@ static void setHeaderComment(wavHeader_t *wavHeader, configSettings_t *configSet static bool writeConfigurationToFile(configSettings_t *configSettings, uint8_t *firmwareDescription, uint8_t *firmwareVersion, uint8_t *serialNumber, uint8_t *deploymentID, uint8_t *defaultDeploymentID) { + struct tm time; + uint16_t length; static char configBuffer[512]; @@ -639,9 +677,9 @@ static bool writeConfigurationToFile(configSettings_t *configSettings, uint8_t * time_t rawTime = configSettings->earliestRecordingTime; - struct tm *time = gmtime(&rawTime); + gmtime_r(&rawTime, &time); - length += sprintf(configBuffer + length, "%04d-%02d-%02d %02d:%02d:%02d (UTC)", 1900 + time->tm_year, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); + length += sprintf(configBuffer + length, "%04d-%02d-%02d %02d:%02d:%02d (UTC)", 1900 + time.tm_year, 1 + time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); } @@ -655,9 +693,9 @@ static bool writeConfigurationToFile(configSettings_t *configSettings, uint8_t * time_t rawTime = configSettings->latestRecordingTime; - struct tm *time = gmtime(&rawTime); + gmtime_r(&rawTime, &time); - length += sprintf(configBuffer + length, "%04d-%02d-%02d %02d:%02d:%02d (UTC)", 1900 + time->tm_year, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); + length += sprintf(configBuffer + length, "%04d-%02d-%02d %02d:%02d:%02d (UTC)", 1900 + time.tm_year, 1 + time.tm_mon, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); } @@ -683,31 +721,51 @@ static bool writeConfigurationToFile(configSettings_t *configSettings, uint8_t * } - length += sprintf(configBuffer + length, "\nAmplitude threshold : "); + bool frequencyTriggerEnabled = configSettings->enableFrequencyTrigger; - bool amplitudeThresholdEnabled = configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; + bool amplitudeThresholdEnabled = frequencyTriggerEnabled ? false : configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; - if (configSettings->enableAmplitudeThresholdDecibelScale && configSettings->enableAmplitudeThresholdPercentageScale == false) { + length += sprintf(configBuffer + length, "\n\nTrigger type : "); - length += formatDecibels(configBuffer + length, configSettings->amplitudeThresholdDecibels); + if (frequencyTriggerEnabled) { - } else if (configSettings->enableAmplitudeThresholdPercentageScale && configSettings->enableAmplitudeThresholdDecibelScale == false) { + length += sprintf(configBuffer + length, "Frequency (%u.%ukHz and window length of %u samples)", configSettings->frequencyTriggerCentreFrequency / 10, configSettings->frequencyTriggerCentreFrequency % 10, (0x01 << configSettings->frequencyTriggerWindowLengthShift)); - length += formatPercentage(configBuffer + length, configSettings->amplitudeThresholdPercentageMantissa, configSettings->amplitudeThresholdPercentageExponent); + length += sprintf(configBuffer + length, "\nThreshold setting : "); + + length += formatPercentage(configBuffer + length, configSettings->frequencyTriggerThresholdPercentageMantissa, configSettings->frequencyTriggerThresholdPercentageExponent); } else if (amplitudeThresholdEnabled) { - length += sprintf(configBuffer + length, "%u", configSettings->amplitudeThreshold); + length += sprintf(configBuffer + length, "Amplitude"); + + length += sprintf(configBuffer + length, "\nThreshold setting : "); + + if (configSettings->enableAmplitudeThresholdDecibelScale && configSettings->enableAmplitudeThresholdPercentageScale == false) { + + length += formatDecibels(configBuffer + length, configSettings->amplitudeThresholdDecibels); + + } else if (configSettings->enableAmplitudeThresholdPercentageScale && configSettings->enableAmplitudeThresholdDecibelScale == false) { + + length += formatPercentage(configBuffer + length, configSettings->amplitudeThresholdPercentageMantissa, configSettings->amplitudeThresholdPercentageExponent); + + } else { + + length += sprintf(configBuffer + length, "%u", configSettings->amplitudeThreshold); + + } } else { length += sprintf(configBuffer + length, "-"); + length += sprintf(configBuffer + length, "\nThreshold setting : -"); + } length += sprintf(configBuffer + length, "\nMinimum trigger duration (s) : "); - if (amplitudeThresholdEnabled) { + if (frequencyTriggerEnabled || amplitudeThresholdEnabled) { length += sprintf(configBuffer + length, "%u", configSettings->minimumTriggerDuration); @@ -727,6 +785,8 @@ static bool writeConfigurationToFile(configSettings_t *configSettings, uint8_t * length += sprintf(configBuffer + length, "Always require acoustic chime : %s\n", configSettings->requireAcousticConfiguration ? "Yes" : "No"); + length += sprintf(configBuffer + length, "Use daily folder for WAV files : %s\n\n", configSettings->enableDailyFolders ? "Yes" : "No"); + length += sprintf(configBuffer + length, "Disable 48Hz DC blocking filter : %s\n", configSettings->disable48HzDCBlockingFilter ? "Yes" : "No"); length += sprintf(configBuffer + length, "Enable energy saver mode : %s\n", configSettings->enableEnergySaverMode ? "Yes" : "No"); @@ -767,9 +827,9 @@ static uint32_t *recordingErrorHasOccurred = (uint32_t*)(AM_BACKUP_DOMAIN_START_ static uint32_t *recordingPreparationPeriod = (uint32_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 40); -static uint32_t *magneticSwitchWaitCounter = (uint32_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 44); +static uint32_t *waitingForMagneticSwitch = (uint32_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 44); -static uint32_t *waitingForMagneticSwitch = (uint32_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 48); +static uint32_t *poweredDownWithShortWaitInterval = (uint32_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 48); static configSettings_t *configSettings = (configSettings_t*)(AM_BACKUP_DOMAIN_START_ADDRESS + 52); @@ -851,7 +911,7 @@ static int16_t secondaryBuffer[MAXIMUM_SAMPLES_IN_DMA_TRANSFER]; /* Firmware version and description */ -static uint8_t firmwareVersion[AM_FIRMWARE_VERSION_LENGTH] = {1, 7, 1}; +static uint8_t firmwareVersion[AM_FIRMWARE_VERSION_LENGTH] = {1, 8, 0}; static uint8_t firmwareDescription[AM_FIRMWARE_DESCRIPTION_LENGTH] = "AudioMoth-Firmware-Basic"; @@ -899,15 +959,17 @@ static bool isEnergySaverMode(configSettings_t *configSettings) { /* GPS time setting functions */ -static void writeGPSLogMessage(uint32_t time, uint32_t milliseconds, char *message) { +static void writeGPSLogMessage(uint32_t currentTime, uint32_t currentMilliseconds, char *message) { static char logBuffer[256]; - time_t rawTime = time; + struct tm time; + + time_t rawTime = currentTime; - struct tm *tm = gmtime(&rawTime); + gmtime_r(&rawTime, &time); - uint32_t length = sprintf(logBuffer, "%02d/%02d/%04d %02d:%02d:%02d.%03ld UTC: %s\n", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec, milliseconds, message); + uint32_t length = sprintf(logBuffer, "%02d/%02d/%04d %02d:%02d:%02d.%03ld UTC: %s\n", time.tm_mday, 1 + time.tm_mon, 1900 + time.tm_year, time.tm_hour, time.tm_min, time.tm_sec, currentMilliseconds, message); AudioMoth_writeToFile(logBuffer, length); @@ -969,6 +1031,66 @@ static GPS_fixResult_t setTimeFromGPS(bool enableLED, uint32_t timeout) { } +/* Magenetic switch wait functions */ + +static void startWaitingForMagneticSwith() { + + /* Flash LED to indicate start of waiting for magnetic switch */ + + for (uint32_t i = 0; i < MAGNETIC_SWITCH_CHANGE_FLASHES; i += 1) { + + FLASH_LED(Red, SHORT_LED_FLASH_DURATION); + + AudioMoth_delay(SHORT_LED_FLASH_DURATION); + + } + + /* Cancel any scheduled recording */ + + *timeOfNextRecording = UINT32_MAX; + + *durationOfNextRecording = UINT32_MAX; + + *timeOfNextGPSTimeSetting = UINT32_MAX; + + *waitingForMagneticSwitch = true; + +} + +static void stopWaitingForMagneticSwith(uint32_t *currentTime, uint32_t *currentMilliseconds) { + + /* Flash LED to indicate end of waiting for magnetic switch */ + + for (uint32_t i = 0; i < MAGNETIC_SWITCH_CHANGE_FLASHES; i += 1) { + + FLASH_LED(Green, SHORT_LED_FLASH_DURATION); + + AudioMoth_delay(SHORT_LED_FLASH_DURATION); + + } + + /* Schedule next recording */ + + AudioMoth_getTime(currentTime, currentMilliseconds); + + uint32_t scheduleTime = *currentTime + ROUNDED_UP_DIV(*currentMilliseconds + *recordingPreparationPeriod, MILLISECONDS_IN_SECOND); + + scheduleRecording(scheduleTime, timeOfNextRecording, durationOfNextRecording, timeOfNextGPSTimeSetting); + + *waitingForMagneticSwitch = false; + +} + +/* Function to calculate the time to the next event */ + +static void calculateTimeToNextEvent(uint32_t currentTime, uint32_t currentMilliseconds, int64_t *timeUntilPreparationStart, int64_t *timeUntilNextGPSTimeSetting) { + + *timeUntilPreparationStart = (int64_t)*timeOfNextRecording * MILLISECONDS_IN_SECOND - (int64_t)*recordingPreparationPeriod - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + + *timeUntilNextGPSTimeSetting = (int64_t)*timeOfNextGPSTimeSetting * MILLISECONDS_IN_SECOND - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + +} + /* Main function */ int main(void) { @@ -1007,10 +1129,12 @@ int main(void) { /* Initialise magnetic switch state variables */ - *magneticSwitchWaitCounter = 0; - *waitingForMagneticSwitch = false; + /* Initialise the power down interval flag */ + + *poweredDownWithShortWaitInterval = false; + /* Copy default deployment ID */ copyToBackupDomain((uint32_t*)deploymentID, (uint8_t*)defaultDeploymentID, DEPLOYMENT_ID_LENGTH); @@ -1067,6 +1191,10 @@ int main(void) { *shouldSetTimeFromGPS = false; + /* Reset the power down interval flag */ + + *poweredDownWithShortWaitInterval = false; + /* Check there are active recording periods if the switch is in CUSTOM position */ *readyToMakeRecordings = switchPosition == AM_SWITCH_DEFAULT || (switchPosition == AM_SWITCH_CUSTOM && configSettings->activeStartStopPeriods > 0); @@ -1197,24 +1325,10 @@ int main(void) { /* Try to write configuration to file */ - fileSystemEnabled = AudioMoth_enableFileSystem(); + fileSystemEnabled = AudioMoth_enableFileSystem(configSettings->sampleRateDivider == 1 ? AM_SD_CARD_HIGH_SPEED : AM_SD_CARD_NORMAL_SPEED); if (fileSystemEnabled) writtenConfigurationToFileInThisSession = writeConfigurationToFile(configSettings, firmwareDescription, firmwareVersion, (uint8_t*)AM_UNIQUE_ID_START_ADDRESS, deploymentID, defaultDeploymentID); - /* Write the GPS log file */ - - if (fileSystemEnabled && configSettings->enableTimeSettingFromGPS) { - - AudioMoth_appendFile(GPS_FILENAME); - - AudioMoth_getTime(¤tTime, ¤tMilliseconds); - - writeGPSLogMessage(currentTime, currentMilliseconds, "Switched to CUSTOM mode.\n"); - - AudioMoth_closeFile(); - - } - /* Update the time and calculate earliest schedule start time */ AudioMoth_getTime(¤tTime, ¤tMilliseconds); @@ -1225,8 +1339,6 @@ int main(void) { if (switchPosition == AM_SWITCH_CUSTOM) { - *magneticSwitchWaitCounter = 0; - *waitingForMagneticSwitch = configSettings->enableMagneticSwitch; if (configSettings->enableMagneticSwitch || *shouldSetTimeFromGPS) { @@ -1277,19 +1389,21 @@ int main(void) { if (magneticSwitchEnabled) GPS_enableMagneticSwitch(); - /* Reset flag */ + /* Reset LED flags */ - bool shouldRecalculateWaitingTime = true; + bool enableLED = (switchPosition == AM_SWITCH_DEFAULT) || configSettings->enableLED; - bool shouldChangeMagnetWaitingState = false; + bool shouldSuppressLED = *poweredDownWithShortWaitInterval; - bool enableLED = (switchPosition == AM_SWITCH_DEFAULT) || configSettings->enableLED; + *poweredDownWithShortWaitInterval = false; /* Calculate time until next activity */ - int64_t timeUntilPreparationStart = (int64_t)*timeOfNextRecording * MILLISECONDS_IN_SECOND - (int64_t)*recordingPreparationPeriod - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + int64_t timeUntilPreparationStart; + + int64_t timeUntilNextGPSTimeSetting; - int64_t timeUntilNextGPSTimeSetting = (int64_t)*timeOfNextGPSTimeSetting * MILLISECONDS_IN_SECOND - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + calculateTimeToNextEvent(currentTime, currentMilliseconds, &timeUntilPreparationStart, &timeUntilNextGPSTimeSetting); /* If the GPS synchronisation window has passed then cancel it */ @@ -1299,22 +1413,20 @@ int main(void) { *timeOfNextGPSTimeSetting = UINT32_MAX; - timeUntilNextGPSTimeSetting = (int64_t)*timeOfNextGPSTimeSetting * MILLISECONDS_IN_SECOND - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + calculateTimeToNextEvent(currentTime, currentMilliseconds, &timeUntilPreparationStart, &timeUntilNextGPSTimeSetting); } - /* Decide on the activity this wake up period */ + /* Set the time from the GPS */ if (*shouldSetTimeFromGPS && *waitingForMagneticSwitch == false) { /* Set the time from the GPS */ - if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(); + if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(AM_SD_CARD_NORMAL_SPEED); GPS_fixResult_t fixResult = setTimeFromGPS(true, currentTime + GPS_MAX_TIME_SETTING_PERIOD); - AudioMoth_getTime(¤tTime, ¤tMilliseconds); - /* Update the schedule if successful */ if (fixResult == GPS_SUCCESS) { @@ -1325,21 +1437,27 @@ int main(void) { /* Schedule the next recording */ + AudioMoth_getTime(¤tTime, ¤tMilliseconds); + uint32_t scheduleTime = currentTime + ROUNDED_UP_DIV(currentMilliseconds + *recordingPreparationPeriod, MILLISECONDS_IN_SECOND); scheduleRecording(scheduleTime, timeOfNextRecording, durationOfNextRecording, timeOfNextGPSTimeSetting); } - /* If time setting was cancelled with the magnet switch then set the flag */ + /* If time setting was cancelled with the magnet switch then start waiting for the magnetic switch */ - if (fixResult == GPS_CANCELLED_BY_MAGNETIC_SWITCH) { + if (fixResult == GPS_CANCELLED_BY_MAGNETIC_SWITCH) startWaitingForMagneticSwith(); - shouldChangeMagnetWaitingState = true; + /* Power down */ - } + SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); + + } - } else if (timeUntilPreparationStart <= 0) { + /* Make a recording */ + + if (timeUntilPreparationStart <= 0) { /* Enable energy saver mode */ @@ -1349,13 +1467,9 @@ int main(void) { if (writtenConfigurationToFileInThisSession == false && *writtenConfigurationToFile == false) { - if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(); - - if (fileSystemEnabled) { + if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(configSettings->sampleRateDivider == 1 ? AM_SD_CARD_HIGH_SPEED : AM_SD_CARD_NORMAL_SPEED); - *writtenConfigurationToFile = writeConfigurationToFile(configSettings, firmwareDescription, firmwareVersion, (uint8_t*)AM_UNIQUE_ID_START_ADDRESS, deploymentID, defaultDeploymentID); - - } + if (fileSystemEnabled) *writtenConfigurationToFile = writeConfigurationToFile(configSettings, firmwareDescription, firmwareVersion, (uint8_t*)AM_UNIQUE_ID_START_ADDRESS, deploymentID, defaultDeploymentID); } @@ -1397,7 +1511,7 @@ int main(void) { AudioMoth_disableTemperature(); - if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(); + if (!fileSystemEnabled) fileSystemEnabled = AudioMoth_enableFileSystem(configSettings->sampleRateDivider == 1 ? AM_SD_CARD_HIGH_SPEED : AM_SD_CARD_NORMAL_SPEED); if (fileSystemEnabled) { @@ -1441,14 +1555,6 @@ int main(void) { } - /* If recording was cancelled with the magnet switch then set the flag */ - - if (recordingState == MAGNETIC_SWITCH) { - - shouldChangeMagnetWaitingState = true; - - } - /* Update the time and calculate earliest schedule start time */ AudioMoth_getTime(¤tTime, ¤tMilliseconds); @@ -1459,14 +1565,16 @@ int main(void) { if (switchPosition == AM_SWITCH_CUSTOM) { - if (recordingState == RECORDING_OKAY || recordingState == SUPPLY_VOLTAGE_LOW || recordingState == SDCARD_WRITE_ERROR) { + /* Update schedule time as if the recording has ended correctly */ - /* Schedule as if the recording has ended correctly */ + if (recordingState == RECORDING_OKAY || recordingState == SUPPLY_VOLTAGE_LOW || recordingState == SDCARD_WRITE_ERROR) { scheduleTime = MAX(scheduleTime, *timeOfNextRecording + *durationOfNextRecording); } + /* Calculate the next recording schedule */ + scheduleRecording(scheduleTime, timeOfNextRecording, durationOfNextRecording, timeOfNextGPSTimeSetting); } @@ -1483,147 +1591,171 @@ int main(void) { } - } else if (timeUntilNextGPSTimeSetting <= 0 && timeUntilPreparationStart > GPS_MIN_TIME_SETTING_PERIOD * MILLISECONDS_IN_SECOND) { + /* If recording was cancelled with the magnetic switch then start waiting for the magnetic switch */ - /* Set the time from the GPS */ + if (recordingState == MAGNETIC_SWITCH) { + + startWaitingForMagneticSwith(); - AudioMoth_enableFileSystem(); + SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); - GPS_fixResult_t fixResult = setTimeFromGPS(enableLED, *timeOfNextRecording - ROUNDED_UP_DIV(*recordingPreparationPeriod, MILLISECONDS_IN_SECOND)); + } - AudioMoth_getTime(¤tTime, ¤tMilliseconds); + /* Power down with short interval if the next recording is due */ - /* Update the next scheduled GPS fix time */ + if (switchPosition == AM_SWITCH_CUSTOM) { - *timeOfNextGPSTimeSetting = UINT32_MAX; + calculateTimeToNextEvent(currentTime, currentMilliseconds, &timeUntilPreparationStart, &timeUntilNextGPSTimeSetting); - /* If time setting was cancelled with the magnet switch then set the flag */ + if (timeUntilPreparationStart < DEFAULT_WAIT_INTERVAL) { - if (fixResult == GPS_CANCELLED_BY_MAGNETIC_SWITCH) { + *poweredDownWithShortWaitInterval = true; - shouldChangeMagnetWaitingState = true; + SAVE_SWITCH_POSITION_AND_POWER_DOWN(SHORT_WAIT_INTERVAL); + } + } - } else if (magneticSwitch || (magneticSwitchEnabled && GPS_isMagneticSwitchClosed())) { - - shouldChangeMagnetWaitingState = true; + /* Power down */ + + SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); + + } + + /* Update the time from the GPS */ - } else if (enableLED && timeUntilPreparationStart > MILLISECONDS_IN_SECOND && timeUntilNextGPSTimeSetting > MILLISECONDS_IN_SECOND) { + if (timeUntilNextGPSTimeSetting <= 0 && timeUntilPreparationStart > GPS_MIN_TIME_SETTING_PERIOD * MILLISECONDS_IN_SECOND) { - /* Determine if LED should be shown on this cycle */ + /* Set the time from the GPS */ - bool showFlash = true; + AudioMoth_enableFileSystem(AM_SD_CARD_NORMAL_SPEED); - if (magneticSwitchEnabled) { + GPS_fixResult_t fixResult = setTimeFromGPS(enableLED, *timeOfNextRecording - ROUNDED_UP_DIV(*recordingPreparationPeriod, MILLISECONDS_IN_SECOND)); - uint32_t flashModulo = WAITING_LED_FLASH_INTERVAL / MAGNETIC_SWITCH_WAIT_INTERVAL; + /* Update the next scheduled GPS fix time */ - if (*waitingForMagneticSwitch) flashModulo *= MAGNETIC_SWITCH_WAIT_MULTIPLIER; + *timeOfNextGPSTimeSetting = UINT32_MAX; - showFlash = *magneticSwitchWaitCounter % flashModulo == 0; + /* If time setting was cancelled with the magnet switch then start waiting for the magnetic switch */ - *magneticSwitchWaitCounter = (*magneticSwitchWaitCounter + 1) % flashModulo; + if (fixResult == GPS_CANCELLED_BY_MAGNETIC_SWITCH) startWaitingForMagneticSwith(); - } + /* Power down */ - /* Flash LED to indicate waiting */ + SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); - if (showFlash) { + } - /* Choose which LED to show */ + /* Power down if switch position has changed */ - if (*recordingErrorHasOccurred) { + if (switchPosition != *previousSwitchPosition) SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); - FLASH_LED(Both, WAITING_LED_FLASH_DURATION); + /* Calculate the wait intervals */ - } else { + int64_t waitIntervalMilliseconds = WAITING_LED_FLASH_INTERVAL; - FLASH_LED(Green, WAITING_LED_FLASH_DURATION); + uint32_t waitIntervalSeconds = WAITING_LED_FLASH_INTERVAL / MILLISECONDS_IN_SECOND; - } + if (*waitingForMagneticSwitch) { + + waitIntervalMilliseconds *= MAGNETIC_SWITCH_WAIT_MULTIPLIER; + + waitIntervalSeconds *= MAGNETIC_SWITCH_WAIT_MULTIPLIER; - /* Update the time until recording preparation should start or next GPS time setting */ + } - shouldRecalculateWaitingTime = false; + /* Wait for the next event whilst flashing the LED */ - timeUntilPreparationStart -= WAITING_LED_FLASH_DURATION; + bool startedRealTimeClock = false; - timeUntilNextGPSTimeSetting -= WAITING_LED_FLASH_DURATION; + while (true) { - } + /* Update the time */ - } + AudioMoth_getTime(¤tTime, ¤tMilliseconds); - /* Change the magnet waiting state */ + /* Handle magnetic switch event */ - if (shouldChangeMagnetWaitingState) { + bool magneticSwitchEvent = magneticSwitch || (magneticSwitchEnabled && GPS_isMagneticSwitchClosed()); - for (uint32_t i = 0; i < MAGNETIC_SWITCH_CHANGE_FLASHES; i += 1) { + if (magneticSwitchEvent) { if (*waitingForMagneticSwitch) { - FLASH_LED(Green, SHORT_LED_FLASH_DURATION); + stopWaitingForMagneticSwith(¤tTime, ¤tMilliseconds); } else { - FLASH_LED(Red, SHORT_LED_FLASH_DURATION); + startWaitingForMagneticSwith(); } - - AudioMoth_delay(SHORT_LED_FLASH_DURATION); + + SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); } - /* Update the time */ + /* Handle switch position change */ - AudioMoth_getTime(¤tTime, ¤tMilliseconds); + bool switchEvent = switchPositionChanged || AudioMoth_getSwitchPosition() != *previousSwitchPosition; + + if (switchEvent) SAVE_SWITCH_POSITION_AND_POWER_DOWN(DEFAULT_WAIT_INTERVAL); + + /* Calculate the time to the next event */ + + calculateTimeToNextEvent(currentTime, currentMilliseconds, &timeUntilPreparationStart, &timeUntilNextGPSTimeSetting); - /* Schedule recordings for the new state */ + int64_t timeToEarliestEvent = MIN(timeUntilPreparationStart, timeUntilNextGPSTimeSetting); - if (*waitingForMagneticSwitch) { + /* Flash LED */ - uint32_t scheduleTime = currentTime + ROUNDED_UP_DIV(currentMilliseconds + *recordingPreparationPeriod, MILLISECONDS_IN_SECOND); + bool shouldFlashLED = *waitingForMagneticSwitch || (enableLED && shouldSuppressLED == false && timeToEarliestEvent > MINIMUM_LED_FLASH_INTERVAL); - scheduleRecording(scheduleTime, timeOfNextRecording, durationOfNextRecording, timeOfNextGPSTimeSetting); + if (shouldFlashLED) { - *waitingForMagneticSwitch = false; + if (*recordingErrorHasOccurred) { - } else { + FLASH_LED(Both, WAITING_LED_FLASH_DURATION); - *timeOfNextRecording = UINT32_MAX; + } else { - *durationOfNextRecording = UINT32_MAX; + FLASH_LED(Green, WAITING_LED_FLASH_DURATION); - *timeOfNextGPSTimeSetting = UINT32_MAX; + } - *magneticSwitchWaitCounter = 0; + } - *waitingForMagneticSwitch = true; + /* Check there is time to sleep */ - } + if (timeToEarliestEvent < waitIntervalMilliseconds) { + + /* Calculate the remaining time to power down */ - } + uint32_t timeToWait = timeToEarliestEvent < 0 ? 0 : timeToEarliestEvent; - /* Calculate the time until recording preparation should start or next GPS time setting */ + SAVE_SWITCH_POSITION_AND_POWER_DOWN(timeToWait); - if (shouldRecalculateWaitingTime) { + } - timeUntilPreparationStart = (int64_t)*timeOfNextRecording * MILLISECONDS_IN_SECOND - (int64_t)*recordingPreparationPeriod - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + /* Start the real time clock if it isn't running */ - timeUntilNextGPSTimeSetting = (int64_t)*timeOfNextGPSTimeSetting * MILLISECONDS_IN_SECOND - (int64_t)currentTime * MILLISECONDS_IN_SECOND - (int64_t)currentMilliseconds; + if (startedRealTimeClock == false) { - } + AudioMoth_startRealTimeClock(waitIntervalSeconds); - /* Determine how long to power down */ + startedRealTimeClock = true; - int64_t timeToEarliestEvent = MIN(timeUntilPreparationStart, timeUntilNextGPSTimeSetting) - EM4_WAKEUP_PERIOD; + } - uint32_t interval = magneticSwitchEnabled ? MAGNETIC_SWITCH_WAIT_INTERVAL : WAITING_LED_FLASH_INTERVAL; + /* Enter deep sleep */ - int64_t timeToWait = MAX(0, MIN(timeToEarliestEvent, interval)); + AudioMoth_deepSleep(); - SAVE_SWITCH_POSITION_AND_POWER_DOWN(timeToWait); + /* Handle time overflow on awakening */ + + AudioMoth_checkAndHandleTimeOverflow(); + + } } @@ -1743,7 +1875,7 @@ void GPS_handleFixEvent(uint32_t time, uint32_t milliseconds, GPS_fixTime_t *fix static char fixBuffer[128]; - sprintf(fixBuffer, "Received GPS fix - %02d %02d.%04d %c %03d %02d.%04d %c at %02d/%02d/%04d %02d:%02d:%02d.%03d UTC.", fixPosition->latitudeDegrees, fixPosition->latitudeMinutes, fixPosition->latitudeTenThousandths, fixPosition->latitudeDirection, fixPosition->longitudeDegrees, fixPosition->longitudeMinutes, fixPosition->longitudeTenThousandths, fixPosition->longitudeDirection, fixTime->day, fixTime->month, fixTime->year, fixTime->hours, fixTime->minutes, fixTime->seconds, fixTime->milliseconds); + sprintf(fixBuffer, "Received GPS fix - %02d°%02d.%04d'%c %03d°%02d.%04d'%c at %02d/%02d/%04d %02d:%02d:%02d.%03d UTC.", fixPosition->latitudeDegrees, fixPosition->latitudeMinutes, fixPosition->latitudeTenThousandths, fixPosition->latitudeDirection, fixPosition->longitudeDegrees, fixPosition->longitudeMinutes, fixPosition->longitudeTenThousandths, fixPosition->longitudeDirection, fixTime->day, fixTime->month, fixTime->year, fixTime->hours, fixTime->minutes, fixTime->seconds, fixTime->milliseconds); writeGPSLogMessage(time, milliseconds, fixBuffer); @@ -1799,7 +1931,7 @@ inline void AudioMoth_handleDirectMemoryAccessInterrupt(bool isPrimaryBuffer, in /* Apply filter to samples */ - bool thresholdExceeded = DigitalFilter_filter(source, buffers[writeBuffer] + writeBufferIndex, configSettings->sampleRateDivider, numberOfRawSamplesInDMATransfer, configSettings->amplitudeThreshold); + bool thresholdExceeded = DigitalFilter_applyFilter(source, buffers[writeBuffer] + writeBufferIndex, configSettings->sampleRateDivider, numberOfRawSamplesInDMATransfer); numberOfDMATransfers += 1; @@ -2045,19 +2177,25 @@ static void encodeCompressionBuffer(uint32_t numberOfCompressedBuffers) { } -/* Generate filename from time */ +/* Generate foldername and filename from time */ -static void generateFilename(char *fileName, uint32_t timestamp, bool amplitudeThresholdEnabled) { +static void generateFolderAndFilename(char *foldername, char *filename, uint32_t timestamp, bool prefixFoldername, bool triggeredRecording) { + + struct tm time; time_t rawTime = timestamp + configSettings->timezoneHours * SECONDS_IN_HOUR + configSettings->timezoneMinutes * SECONDS_IN_MINUTE; - struct tm *time = gmtime(&rawTime); + gmtime_r(&rawTime, &time); + + sprintf(foldername, "%04d%02d%02d", 1900 + time.tm_year, 1 + time.tm_mon, time.tm_mday); - uint32_t length = sprintf(fileName, "%04d%02d%02d_%02d%02d%02d", 1900 + time->tm_year, time->tm_mon + 1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); + uint32_t length = prefixFoldername ? sprintf(filename, "%s/", foldername) : 0; + + length += sprintf(filename + length, "%s_%02d%02d%02d", foldername, time.tm_hour, time.tm_min, time.tm_sec); - char *extension = amplitudeThresholdEnabled ? "T.WAV" : ".WAV"; + char *extension = triggeredRecording ? "T.WAV" : ".WAV"; - strcpy(fileName + length, extension); + strcpy(filename + length, extension); } @@ -2117,7 +2255,7 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t if (AudioMoth_hasInvertedOutput()) sampleMultiplier = -sampleMultiplier; - DigitalFilter_applyAdditionalGain(sampleMultiplier); + DigitalFilter_setAdditionalGain(sampleMultiplier); /* Calculate the number of samples in each DMA transfer (while ensuring that number of samples written to the SRAM buffer on each DMA transfer is a power of two so each SRAM buffer is filled after an integer number of DMA transfers) */ @@ -2133,7 +2271,7 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t /* Calculate the minimum amplitude threshold duration */ - uint32_t numberOfAmplitudeThresholdBuffers = ROUNDED_UP_DIV(configSettings->minimumTriggerDuration * effectiveSampleRate, NUMBER_OF_SAMPLES_IN_BUFFER); + uint32_t minimumNumberOfTriggeredBuffersToWrite = ROUNDED_UP_DIV(configSettings->minimumTriggerDuration * effectiveSampleRate, NUMBER_OF_SAMPLES_IN_BUFFER); /* Initialise termination conditions */ @@ -2151,19 +2289,51 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t AudioMoth_initialiseDirectMemoryAccess(primaryBuffer, secondaryBuffer, numberOfRawSamplesInDMATransfer); - /* Show LED for SD card activity */ + /* Determine if amplitude threshold is enabled */ - if (enableLED) AudioMoth_setRedLED(true); + bool frequencyTriggerEnabled = configSettings->enableFrequencyTrigger; - /* Determine if amplitude threshold is enabled */ + bool amplitudeThresholdEnabled = frequencyTriggerEnabled ? false : configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; + + /* Configure the digital filter for the appropriate trigger */ + + if (frequencyTriggerEnabled) { + + uint32_t frequency = MIN(effectiveSampleRate / 2, FILTER_FREQ_MULTIPLIER * configSettings->frequencyTriggerCentreFrequency); + + uint32_t windowLength = MIN(FREQUENCY_TRIGGER_WINDOW_MAXIMUM, MAX(FREQUENCY_TRIGGER_WINDOW_MINIMUM, 1 << configSettings->frequencyTriggerWindowLengthShift)); + + float percentageThreshold = (float)configSettings->frequencyTriggerThresholdPercentageMantissa * powf(10.0f, (float)configSettings->frequencyTriggerThresholdPercentageExponent); + + DigitalFilter_setFrequencyTrigger(windowLength, effectiveSampleRate, frequency, percentageThreshold); + + } + + if (amplitudeThresholdEnabled) { + + DigitalFilter_setAmplitudeThreshold(configSettings->amplitudeThreshold); + + } + + /* Show LED for SD card activity */ - bool amplitudeThresholdEnabled = configSettings->amplitudeThreshold > 0 || configSettings->enableAmplitudeThresholdDecibelScale || configSettings->enableAmplitudeThresholdPercentageScale; + if (enableLED) AudioMoth_setRedLED(true); /* Open a file with the current local time as the name */ static char filename[MAXIMUM_FILE_NAME_LENGTH]; - generateFilename(filename, timeOfNextRecording, amplitudeThresholdEnabled); + static char foldername[MAXIMUM_FILE_NAME_LENGTH]; + + generateFolderAndFilename(foldername, filename, timeOfNextRecording, configSettings->enableDailyFolders, frequencyTriggerEnabled || amplitudeThresholdEnabled); + + if (configSettings->enableDailyFolders) { + + bool directoryExists = AudioMoth_doesDirectoryExist(foldername); + + if (directoryExists == false) FLASH_LED_AND_RETURN_ON_ERROR(AudioMoth_makeDirectory(foldername)); + + } FLASH_LED_AND_RETURN_ON_ERROR(AudioMoth_openFile(filename)); @@ -2223,9 +2393,9 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t uint32_t totalNumberOfCompressedSamples = 0; - uint32_t amplitudeThresholdBuffersWritten = 0; + uint32_t numberOfTriggeredBuffersWritten = 0; - bool amplitudeThresholdHasBeenTriggered = false; + bool triggerHasOccurred = false; /* Start processing DMA transfers */ @@ -2247,13 +2417,17 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t /* Check if this buffer should actually be written to the SD card */ - bool writeIndicated = writeIndicator[readBuffer] || amplitudeThresholdEnabled == false; + bool writeIndicated = (amplitudeThresholdEnabled == false && frequencyTriggerEnabled == false) || writeIndicator[readBuffer]; - amplitudeThresholdHasBeenTriggered |= writeIndicated; + if (frequencyTriggerEnabled && configSettings->sampleRateDivider > 1) writeIndicated = DigitalFilter_applyFrequencyTrigger(buffers[readBuffer], NUMBER_OF_SAMPLES_IN_BUFFER); - amplitudeThresholdBuffersWritten = writeIndicated ? 0 : amplitudeThresholdBuffersWritten + 1; + /* Ensure the minimum number of buffers will be written */ - bool shouldWriteThisSector = writeIndicated || (amplitudeThresholdHasBeenTriggered && amplitudeThresholdBuffersWritten < numberOfAmplitudeThresholdBuffers); + triggerHasOccurred |= writeIndicated; + + numberOfTriggeredBuffersWritten = writeIndicated ? 0 : numberOfTriggeredBuffersWritten + 1; + + bool shouldWriteThisSector = writeIndicated || (triggerHasOccurred && numberOfTriggeredBuffersWritten < minimumNumberOfTriggeredBuffersToWrite); /* Compress the buffer or write the buffer to SD card */ @@ -2394,7 +2568,7 @@ static AM_recordingState_t makeRecording(uint32_t timeOfNextRecording, uint32_t if (timeOffset > 0) { - generateFilename(newFilename, timeOfNextRecording + timeOffset, amplitudeThresholdEnabled); + generateFolderAndFilename(foldername, newFilename, timeOfNextRecording + timeOffset, configSettings->enableDailyFolders, frequencyTriggerEnabled || amplitudeThresholdEnabled); if (enableLED) AudioMoth_setRedLED(true); @@ -2442,11 +2616,13 @@ static void scheduleRecording(uint32_t currentTime, uint32_t *timeOfNextRecordin /* Calculate the number of seconds of this day */ + struct tm time; + time_t rawTime = currentTime; - struct tm *time = gmtime(&rawTime); + gmtime_r(&rawTime, &time); - uint32_t currentSeconds = SECONDS_IN_HOUR * time->tm_hour + SECONDS_IN_MINUTE * time->tm_min + time->tm_sec; + uint32_t currentSeconds = SECONDS_IN_HOUR * time.tm_hour + SECONDS_IN_MINUTE * time.tm_min + time.tm_sec; /* Check each active start stop period */