diff --git a/spybug/aaa_config.hh b/spybug/aaa_config.hh index d33e71e..19d1ca1 100644 --- a/spybug/aaa_config.hh +++ b/spybug/aaa_config.hh @@ -24,6 +24,8 @@ #define TIMER_COMPARE 1000 /* 16MHz / 1000 = 16kHz. */ #define FLUSH_SAMPLES 64000 /* Flush WAV file every n samples. */ #define PIN_SS 10 + +#define REC_FILE_FMT "REC_%03u.WAV" /* Must be all caps; must use %u in some form exactly once. */ /********************** END USER CONFIGURATION **********************/ diff --git a/spybug/cmd.cpp b/spybug/cmd.cpp new file mode 100644 index 0000000..f3f7838 --- /dev/null +++ b/spybug/cmd.cpp @@ -0,0 +1,191 @@ +// Copyright 2022 Darwin Schuppan +// SPDX license identifier: MIT + +#include "cmd.hh" + +#include +#include + +#include "aaa_config.hh" +#include "fstr.hh" +#include "io.hh" +#include "settings.hh" +#include "sys.hh" + +static bool sd_initialized = false; + +static void try_init_sd() { + if (sd_initialized) + return; + printf(F("Initializing SD card...")); + #ifdef PIN_COMPONENT_SWITCH + digitalWrite(PIN_COMPONENT_SWITCH, COMPONENT_SWITCH_ON); + delay(500); /* Wait for components to initialize. */ + #endif + if (SD.begin(PIN_SS)) { + printf(F("Done.\n")); + sd_initialized = true; + } else + printf(F("\nError initializing SD card.\n")); +} + +static void sd_error() { + printf(F("Error reading from SD card.\n")); + sd_initialized = false; +} + +static void command_loop() { + // Process Commands + if (Serial.available()) { + char args[6][20]; + size_t len = 0; + size_t n_args = 0; + int c = getchar(); + while (c != '\n' && n_args < 4) { + if (c == ' ') + c = getchar(); + else { + do { + args[n_args][len++] = c; + c = getchar(); + } while (c != ' ' && c != '\n' && len < 20-1); + args[n_args][len] = 0; + len = 0; + n_args++; + } + } + if (n_args >= 1) { + if (fstreq(args[0], F("set"))) { + if ((n_args == 3 || n_args == 4) && fstreq(args[1], F("wait"))) { + float n = atof(args[2]); + unsigned long mins; + if (n_args == 4 && (fstreq(args[3], F("hours")) || fstreq(args[3], F("hour")))) + mins = 60.f * n; + else + mins = n; + settings.recording_delay = mins; + settings.save(); + printf(F("Set waiting time to %lu minutes or "), settings.recording_delay); + print_special((float)settings.recording_delay / 60.f); + printf(F(" hours.\n")); + } else if (n_args == 3 && fstreq(args[1], F("serial"))) { + if (fstreq(args[2], F("on"))) { + settings.serial_log = true; + settings.save(); + printf(F("Serial log enabled.\n")); + } else if (fstreq(args[2], F("off"))) { + settings.serial_log = false; + settings.save(); + printf(F("Serial log disabled.\n")); + } else { + printf(F("Usage: 'set serial [on|off]'.\n")); + } + } else { + printf(F("Usage: 'set wait [minutes|hours]' or 'set serial [on|off]'.\n")); + } + } else if (fstreq(args[0], F("get"))) { + if (n_args == 2 && fstreq(args[1], F("wait"))) { + printf(F("Current waiting time: %lu minutes or "), settings.recording_delay); + print_special((float)settings.recording_delay / 60.f); + printf(F(" hours.\n")); + } else { + printf(F("Usage: 'get wait'.\n")); + } + } else if (fstreq(args[0], F("sd"))) { + try_init_sd(); + if (sd_initialized) { + if (n_args == 2 && fstreq(args[1], F("list"))) { + File root = SD.open("/"); + if (root) { + printf(F("Files:\n")); + bool files = false; + size_t cols = 0; + while (1) { + File entry = root.openNextFile(); + if (!entry) + break; + files = true; + if (cols >= 3) { + cols = 0; + printf(F("\n")); + } + printf(F(" %s"), entry.name()); + cols++; + entry.close(); + } + if (!files) + printf(F("[NONE]")); + printf(F("\n")); + } else + sd_error(); + root.close(); + } else if (n_args == 3 && fstreq(args[1], F("remove"))) { + if (SD.remove(args[2])) + printf(F("Deleted '%s'."), args[2]); + else + printf(F("Error removing '%s'."), args[2]); + } else if (n_args == 2 && fstreq(args[1], F("remove_all"))) { + File root = SD.open("/"); + if (root) { + printf(F("Deleted:\n")); + bool deleted = false; + size_t cols = 0; + while (1) { + File entry = root.openNextFile(); + if (!entry) + break; + const char *name = entry.name(); + unsigned int garbage; + if (!entry.isDirectory() && sscanf(name, REC_FILE_FMT, &garbage) == 1) { + if (cols >= 3) { + cols = 0; + printf(F("\n")); + } + deleted = true; + SD.remove(name); + printf(F(" %s"), name); + cols++; + } + entry.close(); + } + if (!deleted) + printf(F("[NONE]")); + printf(F("\n")); + } else + sd_error(); + root.close(); + } else if (n_args != 1) { + printf(F("Usage: 'sd list', 'sd remove ' or 'sd remove_all'.\n")); + } + } + } else if (fstreq(args[0], F("help"))) { + printf(F("Commands:\n")); + printf(F(" help -- Display this page.\n")); + printf(F(" exit -- Leave command mode.\n")); + printf(F(" get wait -- Display current delay setting.\n")); + printf(F(" set wait [minutes|hours] -- Change current delay setting.\n")); + printf(F(" set serial [on|off] -- Write log to serial output.\n")); + printf(F(" sd -- Initialize the SD card.\n")); + printf(F(" sd list -- List file on SD card.\n")); + printf(F(" sd remove -- Delete a file from SD card.\n")); + printf(F(" sd remove_all -- Delete all recordings from SD card.\n")); + } else if (fstreq(args[0], F("exit"))) { + printf(F("Bye!\n")); + Serial.flush(); + full_reset(); + } else + printf(F("Invalid command: '%s'. Type 'help' for a list of commands.\n"), args[0]); + } else { + printf(F("Please specify a command. Type 'help' for a list of commands.\n")); + } + } +} + +void cmd() { + printf(F("You are now in command mode. Reset to exit. Type 'help' for a list of commands.\n")); + while (Serial.available()) Serial.read(); + while (1) { + command_loop(); + delay(50); + } +} diff --git a/spybug/cmd.hh b/spybug/cmd.hh new file mode 100644 index 0000000..455c6f0 --- /dev/null +++ b/spybug/cmd.hh @@ -0,0 +1,6 @@ +// Copyright 2022 Darwin Schuppan +// SPDX license identifier: MIT + +#pragma once + +void cmd(); diff --git a/spybug/io.hh b/spybug/io.hh index a77b0e9..ddb6433 100644 --- a/spybug/io.hh +++ b/spybug/io.hh @@ -6,3 +6,9 @@ #include void io_setup(); + +#define print_special(x) { Serial.print(x); } +#define die(fmt, ...) { disable_recording_interrupts(); if (settings.serial_log) { printf(F("Fatal: ")); printf(fmt, ##__VA_ARGS__); Serial.flush(); } while(1); } +#define dbg(fmt, ...) { printf(F("Debug: ")); printf(fmt, ##__VA_ARGS__); } +#define info(fmt, ...) { if (settings.serial_log) printf(fmt, ##__VA_ARGS__); } +#define info_special(x) { if (settings.serial_log) Serial.print(x); } diff --git a/spybug/settings.cpp b/spybug/settings.cpp new file mode 100644 index 0000000..56fd2e8 --- /dev/null +++ b/spybug/settings.cpp @@ -0,0 +1,6 @@ +// Copyright 2022 Darwin Schuppan +// SPDX license identifier: MIT + +#include "settings.hh" + +EEPROM_Settings_Class settings; diff --git a/spybug/settings.hh b/spybug/settings.hh index bcd065a..1c1b5af 100644 --- a/spybug/settings.hh +++ b/spybug/settings.hh @@ -13,3 +13,5 @@ struct EEPROM_Settings_Class { inline void load() { EEPROM.get(EEADDR_SETTINGS, *this); } inline void save() { EEPROM.put(EEADDR_SETTINGS, *this); } }; + +extern EEPROM_Settings_Class settings; diff --git a/spybug/spybug.ino b/spybug/spybug.ino index b8afe13..ca458b8 100644 --- a/spybug/spybug.ino +++ b/spybug/spybug.ino @@ -30,68 +30,18 @@ #include #include -#include -#include -#include -#include #include "aaa_config.hh" +#include "cmd.hh" #include "fstr.hh" #include "io.hh" #include "settings.hh" +#include "sys.hh" #if !defined(__AVR_ATmega328P__) || F_CPU != 16000000 #error "This program only works on ATmega328P devices with a clock frequency of 16MHz!" #endif -void (*full_reset)() = nullptr; - -#define print_special(x) { Serial.print(x); } -#define die(fmt, ...) { disable_recording_interrupts(); if (settings.serial_log) { printf(F("Fatal: ")); printf(fmt, ##__VA_ARGS__); Serial.flush(); } while(1); } -#define dbg(fmt, ...) { printf(F("Debug: ")); printf(fmt, ##__VA_ARGS__); } -#define info(fmt, ...) { if (settings.serial_log) printf(fmt, ##__VA_ARGS__); } -#define info_special(x) { if (settings.serial_log) Serial.print(x); } - -volatile bool wdt_int_sleep_mode = false; -ISR (WDT_vect) { - if (wdt_int_sleep_mode) - wdt_disable(); - else - full_reset(); -} - -/* Based on https://github.com/rocketscream/Low-Power. */ -static void low_power_sleep_minutes(unsigned long t) { - wdt_int_sleep_mode = true; - ADCSRA &= ~_BV(ADEN); /* Disable ADC. */ - for (unsigned long i = 0; 8ul * i < 60ul * t; i++) { - // Power Down for 8s - wdt_enable(WDTO_8S); /* Start watchdog timer for 8s. */ - WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ - do { - set_sleep_mode(SLEEP_MODE_PWR_DOWN); - cli(); - sleep_enable(); - sleep_bod_disable(); - sei(); - sleep_cpu(); - sleep_disable(); - sei(); - } while (0); - } - ADCSRA |= _BV(ADEN); /* Re-enable ADC. */ - wdt_int_sleep_mode = false; -} - -static void wdt_enable_with_full_reset() { - wdt_enable(WDTO_8S); /* Start watchdog timer for 8s. */ - WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ -} - -static inline void disable_recording_interrupts() { - TIMSK1 &= ~(_BV(OCIE1A) | _BV(OCIE1B)); -} - enum AdcChannel : uint8_t { AdcChannel0 = 0, AdcChannel1 = 1, @@ -106,7 +56,6 @@ enum AdcChannel : uint8_t { AdcChannelGnd = 15, }; -EEPROM_Settings_Class settings; File file; #if defined(SAMPLE_MODE_U8) #define SAMPLE_BUF_SIZE 256 @@ -227,111 +176,23 @@ static void wav_write_header(uint32_t nsamples) { } } -void show_help() { - printf(F("Commands:\n")); - printf(F(" help -- Display this page.\n")); - printf(F(" exit -- Leave command mode.\n")); - printf(F(" get wait -- Display current delay setting.\n")); - printf(F(" set wait [minutes|hours] -- Change current delay setting.\n")); - printf(F(" set serial [on|off] -- Write log to serial output.\n")); -} - -void command_loop() { - // Process Commands - if (Serial.available()) { - char args[6][20]; - size_t len = 0; - size_t n_args = 0; - int c = getchar(); - while (c != '\n' && n_args < 4) { - if (c == ' ') - c = getchar(); - else { - do { - args[n_args][len++] = c; - c = getchar(); - } while (c != ' ' && c != '\n' && len < 20-1); - args[n_args][len] = 0; - len = 0; - n_args++; - } - } - if (n_args >= 1) { - if (fstreq(args[0], F("set"))) { - if ((n_args == 3 || n_args == 4) && fstreq(args[1], F("wait"))) { - float n = atof(args[2]); - unsigned long mins; - if (n_args == 4 && (fstreq(args[3], F("hours")) || fstreq(args[3], F("hour")))) - mins = 60.f * n; - else - mins = n; - settings.recording_delay = mins; - settings.save(); - printf(F("Set waiting time to %lu minutes or "), settings.recording_delay); - print_special((float)settings.recording_delay / 60.f); - printf(F(" hours.\n")); - } else if (n_args == 3 && fstreq(args[1], F("serial"))) { - if (fstreq(args[2], F("on"))) { - settings.serial_log = true; - settings.save(); - printf(F("Serial log enabled.\n")); - } else if (fstreq(args[2], F("off"))) { - settings.serial_log = false; - settings.save(); - printf(F("Serial log disabled.\n")); - } else { - printf(F("Usage: 'set serial [on|off]'.\n")); - } - } else { - printf(F("Invalid usage of 'set'.\n")); - show_help(); - } - } else if (fstreq(args[0], F("get"))) { - if (n_args == 2 && fstreq(args[1], F("wait"))) { - printf(F("Current waiting time: %lu minutes or "), settings.recording_delay); - print_special((float)settings.recording_delay / 60.f); - printf(F(" hours.\n")); - } else { - printf(F("Invalid usage of 'get'.\n")); - show_help(); - } - } else if (fstreq(args[0], F("help"))) { - show_help(); - } else if (fstreq(args[0], F("exit"))) { - printf(F("Bye!\n")); - Serial.flush(); - full_reset(); - } else - printf(F("Invalid command: '%s'. Type 'help' for a list of commands.\n"), args[0]); - } else { - printf(F("Please specify a command. Type 'help' for a list of commands.\n")); - } - } -} - void setup() { // Serial Setup Serial.begin(9600); /* Set baud rate. */ io_setup(); /* Add printf support. */ + // Component Switch Setup +#ifdef PIN_COMPONENT_SWITCH + pinMode(PIN_COMPONENT_SWITCH, OUTPUT); +#endif // Load EEPROM Data settings.load(); // Handle Commands info(F("Type anything in the next 4s to enter command mode.\n")); for (size_t i = 0; i < 4 * 4; i++) { - if (Serial.available()) { - printf(F("You are now in command mode. Reset to exit. Type 'help' for a list of commands.\n")); - while (Serial.available()) Serial.read(); - while (1) { - command_loop(); - delay(50); - } - } + if (Serial.available()) + cmd(); delay(250); } - // Component Switch Setup -#ifdef PIN_COMPONENT_SWITCH - pinMode(PIN_COMPONENT_SWITCH, OUTPUT); -#endif // Delayed Triggering if (settings.recording_delay) { #ifdef PIN_COMPONENT_SWITCH @@ -360,7 +221,7 @@ void setup() { char filename[32]; do { filenum++; - snprintf(filename, 32, "rec_%03u.wav", filenum); + snprintf(filename, 32, REC_FILE_FMT, filenum); } while (SD.exists(filename)); // Open File file = SD.open(filename, O_READ | O_WRITE | O_CREAT); /* Seeking doesn't seem to work with FILE_WRITE?! */ diff --git a/spybug/sys.cpp b/spybug/sys.cpp new file mode 100644 index 0000000..fb31e04 --- /dev/null +++ b/spybug/sys.cpp @@ -0,0 +1,35 @@ +// Copyright 2022 Darwin Schuppan +// SPDX license identifier: MIT + +#include "sys.hh" + +static volatile bool wdt_int_sleep_mode = false; +ISR (WDT_vect) { + if (wdt_int_sleep_mode) + wdt_disable(); + else + full_reset(); +} + +/* Based on https://github.com/rocketscream/Low-Power. */ +void low_power_sleep_minutes(unsigned long t) { + wdt_int_sleep_mode = true; + ADCSRA &= ~_BV(ADEN); /* Disable ADC. */ + for (unsigned long i = 0; 8ul * i < 60ul * t; i++) { + // Power Down for 8s + wdt_enable(WDTO_8S); /* Start watchdog timer for 8s. */ + WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ + do { + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + cli(); + sleep_enable(); + sleep_bod_disable(); + sei(); + sleep_cpu(); + sleep_disable(); + sei(); + } while (0); + } + ADCSRA |= _BV(ADEN); /* Re-enable ADC. */ + wdt_int_sleep_mode = false; +} diff --git a/spybug/sys.hh b/spybug/sys.hh new file mode 100644 index 0000000..d89742f --- /dev/null +++ b/spybug/sys.hh @@ -0,0 +1,22 @@ +// Copyright 2022 Darwin Schuppan +// SPDX license identifier: MIT + +#pragma once + +#include +#include +#include +#include + +static void (*full_reset)() = nullptr; + +void low_power_sleep_minutes(unsigned long t); + +static inline void wdt_enable_with_full_reset() { + wdt_enable(WDTO_8S); /* Start watchdog timer for 8s. */ + WDTCSR |= (1 << WDIE); /* Enable watchdog interrupt. */ +} + +static inline void disable_recording_interrupts() { + TIMSK1 &= ~(_BV(OCIE1A) | _BV(OCIE1B)); +}