diff --git a/resources/arduino_files/camera_board/camera_board.ino b/resources/arduino_files/camera_board/camera_board.ino index 6e038d5a8..b17a0710f 100644 --- a/resources/arduino_files/camera_board/camera_board.ino +++ b/resources/arduino_files/camera_board/camera_board.ino @@ -5,162 +5,261 @@ #include "Adafruit_Sensor.h" #include "DHT.h" -#define DHT_TYPE DHT22 // DHT 22 (AM2302) - -/* DECLARE PINS */ const int DHT_PIN = 9; // DHT Temp & Humidity Pin -const int CAM_01_RELAY = 5; -const int CAM_02_RELAY = 6; +const int CAM_0_RELAY = 5; +const int CAM_1_RELAY = 6; const int RESET_PIN = 12; +// Utitlity Methods + +void toggle_led() { + digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); +} + +void turn_pin_on(int pin_num) { + digitalWrite(pin_num, HIGH); +} + +void turn_pin_off(int pin_num) { + digitalWrite(pin_num, LOW); +} + +// IO Handlers. -/* CONSTANTS */ -Adafruit_MMA8451 accelerometer = Adafruit_MMA8451(); +class AccelerometerHandler { + public: + void init() { + ready_ = false; + has_reading_ = false; + ready(); + } + void collect() { + if (ready()) { + accelerometer_.getEvent(&event_); + orientation_ = accelerometer_.getOrientation(); // Orientation + has_reading_ = true; + } + } + bool report() { + if (has_reading_) { + Serial.print(", \"accelerometer\":{\"x\":"); + Serial.print(event_.acceleration.x); + Serial.print(", \"y\":"); + Serial.print(event_.acceleration.y); + Serial.print(", \"z\":"); + Serial.print(event_.acceleration.z); + Serial.print(", \"o\": "); Serial.print(orientation_); + Serial.print("}"); + return true; + } + return false; + } + bool ready() { + if (!ready_) { + ready_ = accelerometer_.begin(); + if (!ready_) { + Serial.println("Accelerometer not ready, or not present."); + } else { + // Check Accelerometer range + // accelerometer.setRange(MMA8451_RANGE_2_G); + // Serial.print("Accelerometer Range = "); Serial.print(2 << accelerometer.getRange()); + // Serial.println("G"); + } + } + return ready_; + } -// Setup DHT22 -DHT dht(DHT_PIN, DHT_TYPE); + private: + Adafruit_MMA8451 accelerometer_; + sensors_event_t event_; + uint8_t orientation_; + bool ready_; + bool has_reading_; +} acc_handler; -int led_value = LOW; +class DHTHandler { + public: + DHTHandler() : dht_(DHT_PIN, DHT22) {} // AM2302 + + void init() { + dht_.begin(); + } + + void collect() { + // Force readHumidity to actually talk to the device; + // otherwise will read at most every 2 seconds, which + // is sometimes just a too far apart. + // Note that the underlying read() routine has some big + // delays (250ms and 40ms, plus some microsecond scale delays). + humidity_ = dht_.readHumidity(/*force=*/true); + // readTemperature will use the data collected by + // readHumidity, which is just fine. + temperature_ = dht_.readTemperature(); + } + + void report() { + Serial.print(", \"humidity\":"); + Serial.print(humidity_); + Serial.print(", \"temp_00\":"); + Serial.print(temperature_); + } + + private: + DHT dht_; + float humidity_ = 0; + float temperature_ = 0; // Celcius +} dht_handler; + +unsigned long end_setup_millis; +unsigned long next_report_millis; +int report_num = 0; + +class LedHandler { + public: + void init() { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, false); + + // Provide a visible signal that setup has been entered. + if (Serial) { + // 2 seconds of fast blinks. + for (int i = 0; i < 40; ++i) { + delay(50); + toggle_led(); + } + Serial.println("LED blink complete"); + } else { + // 2 seconds of slow blinks. + for (int i = 0; i < 10; ++i) { + delay(200); + toggle_led(); + } + } + } + + void update() { + unsigned long now = millis(); + if (next_change_ms_ <= now) { + toggle_led(); + next_change_ms_ += (Serial ? 1000 : 100); + if (next_change_ms_ <= now) { + next_change_ms_ = now; + } + } + } + + private: + unsigned long next_change_ms_ = 0; +} led_handler; void setup(void) { Serial.begin(9600); Serial.flush(); - // Turn off LED inside camera box - pinMode(LED_BUILTIN, OUTPUT); - digitalWrite(LED_BUILTIN, LOW); + led_handler.init(); // Setup Camera relays - pinMode(CAM_01_RELAY, OUTPUT); - pinMode(CAM_02_RELAY, OUTPUT); - - pinMode(RESET_PIN, OUTPUT); + pinMode(CAM_0_RELAY, OUTPUT); + pinMode(CAM_1_RELAY, OUTPUT); // Turn on Camera relays - turn_pin_on(CAM_01_RELAY); - turn_pin_on(CAM_02_RELAY); - - if (! accelerometer.begin()) { - while (1); - } + turn_pin_on(CAM_0_RELAY); + turn_pin_on(CAM_1_RELAY); - dht.begin(); + acc_handler.init(); + dht_handler.init(); - // Check Accelerometer range - // accelerometer.setRange(MMA8451_RANGE_2_G); - // Serial.print("Accelerometer Range = "); Serial.print(2 << accelerometer.getRange()); - // Serial.println("G"); + Serial.println("EXIT setup()"); + next_report_millis = end_setup_millis = millis(); } void loop() { - + led_handler.update(); + if (Serial) { + main_loop(); + } +} + +static int inputs = 0; + +void main_loop() { + unsigned long now = millis(); + if (next_report_millis <= now) { + // Schedule the next report for `interval' milliseconds from the last report, + // unless we've fallen behind. + constexpr int interval = 1000; + next_report_millis += interval; + if (next_report_millis <= now) { + next_report_millis = now + interval; + } + + // Collect the data. Since some of these operations take a while, keep updating the + // LED as appropriate. Could probably be done with an interrupt handler instead. + report_num++; + acc_handler.collect(); + led_handler.update(); + dht_handler.collect(); + led_handler.update(); + bool cam0 = digitalRead(CAM_0_RELAY); + bool cam1 = digitalRead(CAM_1_RELAY); + + // Format/output the results. + Serial.print("{\"name\":\"camera_board\", \"count\":"); + led_handler.update(); + Serial.print(millis() - end_setup_millis); + led_handler.update(); + Serial.print(", \"num\":"); + led_handler.update(); + Serial.print(report_num); + led_handler.update(); + Serial.print(", \"inputs\":"); + led_handler.update(); + Serial.print(inputs); + led_handler.update(); + Serial.print(", \"camera_00\":"); + led_handler.update(); + Serial.print(cam0); + led_handler.update(); + Serial.print(", \"camera_01\":"); + led_handler.update(); + Serial.print(cam1); + led_handler.update(); + acc_handler.report(); + led_handler.update(); + dht_handler.report(); + led_handler.update(); + Serial.println("}"); + led_handler.update(); + Serial.flush(); + led_handler.update(); + } + // Read any serial input - // - Input will be two comma separated integers, the + // - Input will be two integers (with anything in between them), the // first specifying the pin and the second the status // to change to (1/0). Cameras and debug led are // supported. // Example serial input: - // 4,1 # Turn fan on + // 5,1 # Turn camera 0 on + // 6,0 # Turn camera 1 off // 13,0 # Turn led off while (Serial.available() > 0) { + inputs++; int pin_num = Serial.parseInt(); int pin_status = Serial.parseInt(); switch (pin_num) { - case CAM_01_RELAY: - case CAM_02_RELAY: - if (pin_status == 1) { - turn_pin_on(pin_num); - } else if (pin_status == 0) { - turn_pin_off(pin_num); - } else if (pin_status == 9) { - toggle_pin(pin_num); - } - break; - case RESET_PIN: - if (pin_status == 1) { - turn_pin_off(RESET_PIN); - } - break; - case LED_BUILTIN: - digitalWrite(pin_num, pin_status); - break; + case CAM_0_RELAY: + case CAM_1_RELAY: + if (pin_status == 1) { + turn_pin_on(pin_num); + } else if (pin_status == 0) { + turn_pin_off(pin_num); + } + break; + case LED_BUILTIN: + digitalWrite(pin_num, pin_status); + break; } } - - // Begin reading values and outputting as JSON string - Serial.print("{"); - - read_status(); - - read_accelerometer(); - - read_dht_temp(); - - Serial.print("\"name\":\"camera_board\""); Serial.print(","); - - Serial.print("\"count\":"); Serial.print(millis()); - - Serial.println("}"); - - Serial.flush(); - delay(1000); -} - -void read_status() { - - Serial.print("\"power\":{"); - Serial.print("\"camera_00\":"); Serial.print(is_pin_on(CAM_01_RELAY)); Serial.print(','); - Serial.print("\"camera_01\":"); Serial.print(is_pin_on(CAM_02_RELAY)); Serial.print(','); - Serial.print("},"); -} - -/* ACCELEROMETER */ -void read_accelerometer() { - /* Get a new sensor event */ - sensors_event_t event; - accelerometer.getEvent(&event); - uint8_t o = accelerometer.getOrientation(); // Orientation - - Serial.print("\"accelerometer\":{"); - Serial.print("\"x\":"); Serial.print(event.acceleration.x); Serial.print(','); - Serial.print("\"y\":"); Serial.print(event.acceleration.y); Serial.print(','); - Serial.print("\"z\":"); Serial.print(event.acceleration.z); Serial.print(','); - Serial.print("\"o\": "); Serial.print(o); - Serial.print("},"); -} - -//// Reading temperature or humidity takes about 250 milliseconds! -//// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) -void read_dht_temp() { - float h = dht.readHumidity(); - float c = dht.readTemperature(); // Celsius - - Serial.print("\"humidity\":"); Serial.print(h); Serial.print(','); - Serial.print("\"temp_00\":"); Serial.print(c); Serial.print(","); -} - -/************************************ -* Utitlity Methods -*************************************/ - -void toggle_led() { - led_value = ! led_value; - digitalWrite(LED_BUILTIN, led_value); -} - -void toggle_pin(int pin_num) { - digitalWrite(pin_num, !digitalRead(pin_num)); -} - -void turn_pin_on(int camera_pin) { - digitalWrite(camera_pin, HIGH); -} - -void turn_pin_off(int camera_pin) { - digitalWrite(camera_pin, LOW); -} - -int is_pin_on(int camera_pin) { - return digitalRead(camera_pin); } diff --git a/resources/arduino_files/telemetry_board/telemetry_board.ino b/resources/arduino_files/telemetry_board/telemetry_board.ino index a4efe14a3..55ac81ff4 100644 --- a/resources/arduino_files/telemetry_board/telemetry_board.ino +++ b/resources/arduino_files/telemetry_board/telemetry_board.ino @@ -1,259 +1,603 @@ +/* + The PANOPTES Baseline Unit (January 2017) telemetry board controls 5 relays on + the V0 power board, and reads 4 current sensors, 3 temperature sensors and one + combined humidity & temperature sensor. Each pass through loop() it checks for + serial input regarding switching the relays, and every 2 seconds it prints + out the values of the sensors and the settings of the relays. + + This program has a class for each type of sensor, with a common API: + 1) An Init() method that is called from setup. + 2) A Collect() method that is called during the sensor reading phase. + 3) A Report() method that is called to print out json data. + + The serial input should be in this format: + A,B + Where A and B are two positive integers. The can be an ASCII + new line or carriage return. + The first integer (A) specifies the digital output pin that controls the relay, + and the second (B) specifies the output value for that pin (0 or 1). + See "Digital output pins" below for a list of the relay pins. + If any other characters appear in the serial input, all serial input is ignored + until a new line or carriage return is received. +*/ + +#include #include #include "OneWire.h" #include "DallasTemperature.h" #include "DHT.h" +//////////////////////////////////////////////// +// __ __ _ // +// \ \ / / (_) // +// \ \ / /___ _ __ ___ _ ___ _ __ // +// \ \/ // _ \| '__|/ __|| | / _ \ | '_ \ // +// \ /| __/| | \__ \| || (_) || | | | // +// \/ \___||_| |___/|_| \___/ |_| |_| // +// // +//////////////////////////////////////////////// + +// Please update the version identifier when you +// make changes to this code. The value needs to +// be in JSON format (i.e. quoted and escaped if +// a string). +#define JSON_VERSION_ID "\"2017-09-02\"" + +// How often, in milliseconds, to emit a report. +#define REPORT_INTERVAL_MS 2000 + +// Type of Digital Humidity and Temperature (DHT) Sensor #define DHTTYPE DHT22 // DHT 22 (AM2302) -/* DECLARE PINS */ - -// Analog Pins -const int I_MAIN = A1; -const int I_FAN = A2; -const int I_MOUNT = A3; -const int I_CAMERAS = A4; - -// Digital Pins -const int AC_PIN = 11; -const int DS18_PIN = 10; // DS18B20 Temperature (OneWire) -const int DHT_PIN = 9; // DHT Temp & Humidity Pin - -const int COMP_RELAY = 8; // Computer Relay -const int CAMERAS_RELAY = 7; // Cameras Relay Off: 70s Both On: 800s One On: 350 -const int FAN_RELAY = 6; // Fan Relay Off: 0 On: 80s -const int WEATHER_RELAY = 5; // Weather Relay 250mA upon init and 250mA to read -const int MOUNT_RELAY = 4; // Mount Relay - +// Analog input pins to which the current sensors are attached. +const int I_MAIN = A1; // 12V input to power board. +const int I_FAN = A2; // Output to fan (at most a few hundred mA) +const int I_MOUNT = A3; // Output to mount (up to ~1A) +const int I_CAMERAS = A4; // Output to cameras (up to ~1.2A) + +// Scaling factors for the current sensor values. Given the use +// of the Sparkfun current sensor, which has a potentiometer on +// it, these need to be modified for each individual board. Switching +// to a fixed scale sensor, such as on the Infineon board, will +// remove this need. Another choice would be to output the raw analog +// values, and leave it up to a later system to scale or normalize +// the values. const float main_amps_mult = 2.8; const float fan_amps_mult = 1.8; const float mount_amps_mult = 1.8; -const float camera_amps_mult = 1.0; - -const int NUM_DS18 = 3; // Number of DS18B20 Sensors - - -/* CONSTANTS */ -/* -For info on the current sensing, see: - http://henrysbench.capnfatz.com/henrys-bench/arduino-current-measurements/the-acs712-current-sensor-with-an-arduino/ - http://henrysbench.capnfatz.com/henrys-bench/arduino-current-measurements/acs712-current-sensor-user-manual/ -*/ -const float mV_per_amp = 0.185; -const float ACS_offset = 0.; +const float cameras_amps_mult = 1.0; +// Digital input pins +const int AC_PIN = 11; // Is there any AC input? +const int DS18_PIN = 10; // DS18B20 Temperature (OneWire) +const int DHT_PIN = 9; // DHT Temp & Humidity Pin -uint8_t sensors_address[NUM_DS18][8]; +// Digital output pins +const int COMP_RELAY = 8; // Computer Relay: change with caution; it runs the show. +const int CAMERAS_RELAY = 7; // Cameras Relay Off: 70s Both On: 800s One On: 350 +const int FAN_RELAY = 6; // Fan Relay Off: 0 On: 80s +const int WEATHER_RELAY = 5; // Weather Relay 250mA upon init and 250mA to read +const int MOUNT_RELAY = 4; // Mount Relay -// Temperature chip I/O OneWire ds(DS18_PIN); -DallasTemperature sensors(&ds); - -// Setup DHT22 -DHT dht(DHT_PIN, DHTTYPE); -int led_value = LOW; +// Utitlity Methods +void turn_pin_on(int pin_num) { + digitalWrite(pin_num, HIGH); +} -void setup() { - Serial.begin(9600); - Serial.flush(); - - pinMode(LED_BUILTIN, OUTPUT); - - sensors.begin(); - - pinMode(AC_PIN, INPUT); - - pinMode(COMP_RELAY, OUTPUT); - pinMode(CAMERAS_RELAY, OUTPUT); - pinMode(FAN_RELAY, OUTPUT); - pinMode(WEATHER_RELAY, OUTPUT); - pinMode(MOUNT_RELAY, OUTPUT); - - // Turn relays on to start - digitalWrite(COMP_RELAY, HIGH); - digitalWrite(CAMERAS_RELAY, HIGH); - digitalWrite(FAN_RELAY, HIGH); - digitalWrite(WEATHER_RELAY, HIGH); - digitalWrite(MOUNT_RELAY, HIGH); +void turn_pin_off(int pin_num) { + digitalWrite(pin_num, LOW); +} - dht.begin(); +bool is_pin_on(int pin_num) { + return digitalRead(pin_num) != LOW; +} +void toggle_pin(int pin_num) { + digitalWrite(pin_num, !digitalRead(pin_num)); } -void loop() { +void toggle_led() { + toggle_pin(LED_BUILTIN); +} - // Read any serial input - // - Input will be two comma separated integers, the - // first specifying the pin and the second the status - // to change to (1/0). Only the fan and the debug led - // are currently supported. - // Example serial input: - // 4,1 # Turn fan on - // 13,0 # Turn led off - while (Serial.available() > 0) { - int pin_num = Serial.parseInt(); - int pin_status = Serial.parseInt(); - - switch (pin_num) { - case COMP_RELAY: - /* The computer shutting itself off: - * - Power down - * - Wait 30 seconds - * - Power up - */ - if (pin_status == 0){ - turn_pin_off(COMP_RELAY); - delay(1000 * 30); - turn_pin_on(COMP_RELAY); +////////////////////////////////////////////////////////////////////////////// +// Input Handlers: the support collecting the values of various sensors/pins, +// and then reporting it later. + +class DHTHandler { + public: + DHTHandler(uint8_t pin, uint8_t type) : dht_(pin, type) {} + + void Init() { + dht_.begin(); + } + + void Collect() { + // Force readHumidity to actually talk to the device; + // otherwise will read at most every 2 seconds, which + // is sometimes just a little too far apart. + // Note that the underlying read() routine has some big + // delays (250ms and 40ms, plus some microsecond scale delays). + humidity_ = dht_.readHumidity(/*force=*/true); + // readTemperature will use the data collected by + // readHumidity, which is just fine. + temperature_ = dht_.readTemperature(); + } + + void Report() { + // This is being added to a JSON dictionary, so print a comma + // before the quoted name, which is then followed by a colon. + Serial.print(", \"humidity\":"); + Serial.print(humidity_); + Serial.print(", \"temp_00\":"); + Serial.print(temperature_); + } + + private: + DHT dht_; + float humidity_; + float temperature_; // Celcius +} dht_handler(DHT_PIN, DHTTYPE); + +// DallasTemperatureHandler collects temp values from Dallas One-Wire temp sensors. +template +class DallasTemperatureHandler { + public: + DallasTemperatureHandler(OneWire* one_wire) : dt_(one_wire), device_count_(0) {} + + void Init() { + dt_.begin(); + dt_.setWaitForConversion(true); + + uint8_t devices = dt_.getDeviceCount(); + for (uint8_t device_num = 0; device_num < devices; ++device_num) { + if (dt_.getAddress(devices_[device_count_].address, device_num)) { + devices_[device_count_].index = device_num; + ++device_count_; + if (device_count_ >= kMaxSensors) { + break; + } } - break; - case CAMERAS_RELAY: - case FAN_RELAY: - case WEATHER_RELAY: - case MOUNT_RELAY: - if (pin_status == 1) { - turn_pin_on(pin_num); - } else if (pin_status == 0) { - turn_pin_off(pin_num); - } else if (pin_status == 9) { - toggle_pin(pin_num); + } + // Sort the devices so we get a consistent order from run to run... + // ... assuming the devices haven't been swapped, in which case we + // need to redetermine their addresses. + // Arduino libraries don't include sort(), so adapted insertion sort + // code in the ArduinoSort library on github for just this purpose. + for (uint8_t i = 1; i < device_count_; i++) { + for (uint8_t j = i; j > 0 && devices_[j - 1] > devices_[j]; j--) { + DeviceInfo tmp = devices_[j - 1]; + devices_[j - 1] = devices_[j]; + devices_[j] = tmp; } - break; - case LED_BUILTIN: - digitalWrite(pin_num, pin_status); - break; } - } + } + void Collect() { + // Ask all of the sensors to start a temperature "conversion"; I think + // this means the analog-to-digital conversion, which stores the result + // in a register in the sensor. requestTemperatures() will return when + // the conversion is complete. + dt_.requestTemperatures(); + for (uint8_t i = 0; i < device_count_; i++) { + DeviceInfo& device = devices_[i]; + device.temperature = dt_.getTempC(device.address); + } + } + void Report() { + if (device_count_ > 0) { + // This is being added to a JSON dictionary, so print a comma + // before the quoted name, which is then followed by a colon. + Serial.print(", \"temperature\":["); + for (uint8_t i = 0; i < device_count_; i++) { + if (i != 0) { + Serial.print(","); + } + DeviceInfo& device = devices_[i]; + Serial.print(device.temperature); + } + Serial.print("]"); + } + } + + void PrintDeviceInfo() { + for (uint8_t i = 0; i < device_count_; i++) { + const DeviceInfo& device = devices_[i]; + const DeviceAddress& addr = device.address; + Serial.print("Device #"); + Serial.print(device.index); + Serial.print(" address: "); + for (int j = 0; j < sizeof device.address; ++j) { + Serial.print(static_cast(device.address[j]), HEX); + Serial.print(" "); + } + uint8_t resolution = dt_.getResolution(device.address); + Serial.print(" resolution (bits): "); + Serial.println(resolution); + } + } - Serial.print("{"); + private: + struct DeviceInfo { + bool operator>(const DeviceInfo& rhs) const { + return memcmp(address, rhs.address, sizeof address) < 0; + } + DeviceAddress address; + float temperature; + uint8_t index; + }; + + DallasTemperature dt_; + DeviceInfo devices_[kMaxSensors]; + uint8_t device_count_; +}; + +// There are 3 DS18B20 sensors in the Jan 2017 Telemetry Board design. +DallasTemperatureHandler<3> dt_handler(&ds); + +// Base class of handlers below which emit a different name for each +// instance of a sub-class. +class BaseNameHandler { + protected: + BaseNameHandler(char* name) : name_(name) {} + void PrintName() { + // Print quoted name for JSON dictionary key. The decision of + // whether to add a comma before this is made by the caller. + Serial.print('"'); + Serial.print(name_); + Serial.print("\":"); + } + + private: + const char* const name_; +}; + +class CurrentHandler : public BaseNameHandler { + public: + CurrentHandler(char* name, int pin, float scale) + : BaseNameHandler(name), pin_(pin), scale_(scale) {} + void Collect() { + reading_ = analogRead(pin_); + amps_ = reading_ * scale_; + } + void ReportReading() { + PrintName(); + Serial.print(reading_); + } + void ReportAmps() { + PrintName(); + Serial.print(amps_); + } + + private: + const int pin_; + const float scale_; + + int reading_; + float amps_; +}; + +// One CurrentHandler instance for each of the current sensors. +CurrentHandler current_handlers[] = { + {"main", I_MAIN, main_amps_mult}, + {"fan", I_FAN, fan_amps_mult}, + {"mount", I_MOUNT, mount_amps_mult}, + {"cameras", I_CAMERAS, cameras_amps_mult}, +}; + +class DigitalInputHandler : public BaseNameHandler { + public: + DigitalInputHandler(char* name, int pin) + : BaseNameHandler(name), pin_(pin) {} + void Collect() { + reading_ = digitalRead(pin_); + } + void Report() { + PrintName(); + Serial.print(reading_); + } + private: + const int pin_; + int reading_; +}; + +// One DigitalInputHandler for each of the GPIO pins for which we report the value. +// Most are actually output pins, but the Arduino API allows us to read the value +// that is being output. +DigitalInputHandler di_handlers[] = { + {"computer", COMP_RELAY}, + {"fan", FAN_RELAY}, + {"mount", MOUNT_RELAY}, + {"cameras", CAMERAS_RELAY}, + {"weather", WEATHER_RELAY}, + {"main", AC_PIN}, +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// General reporting code. + +// Due to limitations of the Arduino preprocessor, we must place the following all on one line: +template void ReportCollection(const char* name, T(&handlers)[size]) { + // This is being added to a JSON dictionary, so print a comma + // before the quoted name, which is then followed by a colon. + Serial.print(", \""); + Serial.print(name); + Serial.print("\": {"); + bool first = true; + for (auto& handler : handlers) { + if (first) { + first = false; + } else { + Serial.print(", "); + } + handler.Report(); + } + Serial.print('}'); +} - read_voltages(); +// Due to limitations of the Arduino preprocessor, we must place the following all on one line: +template void PrintCollection(const char* name, T(&handlers)[size], void (T::*mf)()) { + // This is being added to a JSON dictionary, so print a comma + // before the quoted name, which is then followed by a colon. + Serial.print(", \""); + Serial.print(name); + Serial.print("\": {"); + bool first = true; + for (auto& handler : handlers) { + if (first) { + first = false; + } else { + Serial.print(","); + } + (handler.*mf)(); + } + Serial.print('}'); +} - read_dht_temp(); +// Produce a single JSON line with the current values reported by +// the sensors and the settings of the relays. +void Report(unsigned long now) { + static unsigned long report_num = 0; - read_ds18b20_temp(); + // Collect values from all of the sensors & replays. + dht_handler.Collect(); + dt_handler.Collect(); + for (auto& di_handler : di_handlers) { + di_handler.Collect(); + } + for (auto& handler : current_handlers) { + handler.Collect(); + } - Serial.print("\"name\":\"telemetry_board\""); Serial.print(","); + // Print the collected values as JSON. + Serial.print("{\"name\":\"telemetry_board\", \"count\":"); + Serial.print(millis()); + Serial.print(", \"num\":"); + Serial.print(++report_num); + Serial.print(", \"ver\":"); + Serial.print(JSON_VERSION_ID); - Serial.print("\"count\":"); Serial.print(millis()); + ReportCollection("power", di_handlers); + PrintCollection("current", current_handlers, &CurrentHandler::ReportReading); + PrintCollection("amps", current_handlers, &CurrentHandler::ReportAmps); + dht_handler.Report(); + dt_handler.Report(); Serial.println("}"); - - // Simple heartbeat - // toggle_led(); - delay(1000); -} - -/* Toggle Pin */ -void turn_pin_on(int camera_pin) { - digitalWrite(camera_pin, HIGH); -} - -void turn_pin_off(int camera_pin) { - digitalWrite(camera_pin, LOW); } -void toggle_pin(int pin_num) { - digitalWrite(pin_num, !digitalRead(pin_num)); -} - -int is_pin_on(int camera_pin) { - return digitalRead(camera_pin); -} +////////////////////////////////////////////////////////////////////////////////// +// Serial input support + +// CharBuffer stores characters and supports (minimal) parsing of +// the buffered characters. +template +class CharBuffer { + public: + CharBuffer() { + Reset(); + } + void Reset() { + write_cursor_ = read_cursor_ = 0; + } + bool Append(char c) { + if (write_cursor_ < buf_ + kBufferSize) { + buf_[write_cursor_++] = c; + return true; + } + return false; + } + bool Empty() { + return read_cursor_ >= write_cursor_; + } + char Next() { + return buf_[read_cursor_++]; + } + char Peek() { + return buf_[read_cursor_]; + } + bool ParseInt(int* output) { + int& v = *output; + v = 0; + size_t len = 0; + while (!Empty() && isdigit(Peek())) { + char c = Next(); + v = v * 10 + c - '0'; + ++len; + if (len > 5) { + return false; + } + } + return len > 0; + } + bool MatchAndConsume(char c) { + if (Empty() || Peek() != c) { + return false; + } + Next(); + return true; + } + + private: + char buf_[kBufferSize]; + uint8_t write_cursor_; + uint8_t read_cursor_; +}; + +// Accumulates a line, parses it and takes the requested action if it is valid. +class SerialInputHandler { + public: + void Handle() { + while (Serial && Serial.available() > 0) { + int c = Serial.read(); + if (wait_for_new_line_) { + if (IsNewLine(c)) { + wait_for_new_line_ = false; + input_buffer_.Reset(); + } + } else if (IsNewLine(c)) { + ProcessInputBuffer(); + wait_for_new_line_ = false; + input_buffer_.Reset(); + } else if (isprint(c)) { + if (!input_buffer_.Append(static_cast(c))) { + wait_for_new_line_ = true; + } + } else { + // Input is not an acceptable character. + wait_for_new_line_ = true; + } + } + } + + private: + // Allow the input line to end with NL, CR NL or CR. + bool IsNewLine(int c) { + return c == '\n' || c == '\r'; + } + + void ProcessInputBuffer() { + int pin_num, pin_status; + if (input_buffer_.ParseInt(&pin_num) && + input_buffer_.MatchAndConsume(',') && + input_buffer_.ParseInt(&pin_status) && + input_buffer_.Empty()) { + switch (pin_num) { + case COMP_RELAY: + /* The computer shutting itself off: + - Power down + - Wait 30 seconds + - Power up + */ + if (pin_status == 0) { + turn_pin_off(COMP_RELAY); + delay(1000 * 30); + turn_pin_on(COMP_RELAY); + } + break; + case CAMERAS_RELAY: + case FAN_RELAY: + case WEATHER_RELAY: + case MOUNT_RELAY: + if (pin_status == 1) { + turn_pin_on(pin_num); + } else if (pin_status == 0) { + turn_pin_off(pin_num); + } else if (pin_status == 9) { + toggle_pin(pin_num); + } + break; + case LED_BUILTIN: + digitalWrite(pin_num, pin_status); + break; + } + } + } + + CharBuffer<8> input_buffer_; + bool wait_for_new_line_{false}; +} serial_input_handler; + +// A simple count-down timer. +class IntervalTimer { + public: + IntervalTimer(unsigned int interval_ms) + : interval_(interval_ms), remaining_(interval_ms) {} + bool HasExpired() { + unsigned long now = millis(); + unsigned long elapsed = now - last_time_; + last_time_ = now; + // Note: not checking for elapsed being so large that it + // exceeds the ability to be represented in remaining_. + remaining_ -= elapsed; + if (remaining_ <= 0) { + remaining_ += interval_; + if (remaining_ < 0) { + remaining_ = interval_; + } + return true; + } + return false; + } -/* Read Voltages - -Gets the AC probe as well as the values of the current on the AC I_ pins - -https://www.arduino.cc/en/Reference/AnalogRead - - */ -void read_voltages() { - int ac_reading = digitalRead(AC_PIN); - - int main_reading = analogRead(I_MAIN); - float main_amps = (main_reading / 1023.) * main_amps_mult; -// float main_amps = ((main_voltage - ACS_offset) / mV_per_amp); - - int fan_reading = analogRead(I_FAN); - float fan_amps = (fan_reading / 1023.) * fan_amps_mult; -// float fan_amps = ((fan_voltage - ACS_offset) / mV_per_amp); - - int mount_reading = analogRead(I_MOUNT); - float mount_amps = (mount_reading / 1023.) * mount_amps_mult; -// float mount_amps = ((mount_voltage - ACS_offset) / mV_per_amp); - - int camera_reading = analogRead(I_CAMERAS); - float camera_amps = (camera_reading / 1023.) * 1; -// float camera_amps = ((camera_voltage - ACS_offset) / mV_per_amp); - - Serial.print("\"power\":{"); - Serial.print("\"computer\":"); Serial.print(is_pin_on(COMP_RELAY)); Serial.print(','); - Serial.print("\"fan\":"); Serial.print(is_pin_on(FAN_RELAY)); Serial.print(','); - Serial.print("\"mount\":"); Serial.print(is_pin_on(MOUNT_RELAY)); Serial.print(','); - Serial.print("\"cameras\":"); Serial.print(is_pin_on(CAMERAS_RELAY)); Serial.print(','); - Serial.print("\"weather\":"); Serial.print(is_pin_on(WEATHER_RELAY)); Serial.print(','); - Serial.print("\"main\":"); Serial.print(ac_reading); Serial.print(','); - Serial.print("},"); - - Serial.print("\"current\":{"); - Serial.print("\"main\":"); Serial.print(main_reading); Serial.print(','); - Serial.print("\"fan\":"); Serial.print(fan_reading); Serial.print(','); - Serial.print("\"mount\":"); Serial.print(mount_reading); Serial.print(','); - Serial.print("\"cameras\":"); Serial.print(camera_reading); - Serial.print("},"); - -// Serial.print("\"volts\":{"); -// Serial.print("\"main\":"); Serial.print(main_voltage); Serial.print(','); -// Serial.print("\"fan\":"); Serial.print(fan_voltage); Serial.print(','); -// Serial.print("\"mount\":"); Serial.print(mount_voltage); Serial.print(','); -// Serial.print("\"cameras\":"); Serial.print(camera_voltage); -// Serial.print("},"); - - Serial.print("\"amps\":{"); - Serial.print("\"main\":"); Serial.print(main_amps); Serial.print(','); - Serial.print("\"fan\":"); Serial.print(fan_amps); Serial.print(','); - Serial.print("\"mount\":"); Serial.print(mount_amps); Serial.print(','); - Serial.print("\"cameras\":"); Serial.print(camera_amps); - Serial.print("},"); -} + private: + unsigned long last_time_{0}; + long remaining_; + const unsigned int interval_; +}; -//// Reading temperature or humidity takes about 250 milliseconds! -//// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor) -void read_dht_temp() { - float h = dht.readHumidity(); - float c = dht.readTemperature(); // Celsius +////////////////////////////////////////////////////////////////////////////////// +// Primary Arduino defined methods: setup(), called once at start, and loop(), +// called repeatedly (roughly as soon as it returns). - // Check if any reads failed and exit early (to try again). - // if (isnan(h) || isnan(t)) { - // Serial.println("Failed to read from DHT sensor!"); - // return; - // } +void setup() { + Serial.begin(9600); + Serial.flush(); - Serial.print("\"humidity\":"); Serial.print(h); Serial.print(','); - Serial.print("\"temp_00\":"); Serial.print(c); Serial.print(','); -} + pinMode(LED_BUILTIN, OUTPUT); -void read_ds18b20_temp() { + // Setup relay pins. + pinMode(COMP_RELAY, OUTPUT); + pinMode(CAMERAS_RELAY, OUTPUT); + pinMode(FAN_RELAY, OUTPUT); + pinMode(WEATHER_RELAY, OUTPUT); + pinMode(MOUNT_RELAY, OUTPUT); - sensors.requestTemperatures(); + // Turn relays on to start + digitalWrite(COMP_RELAY, HIGH); + digitalWrite(CAMERAS_RELAY, HIGH); + digitalWrite(FAN_RELAY, HIGH); + digitalWrite(WEATHER_RELAY, HIGH); + digitalWrite(MOUNT_RELAY, HIGH); - Serial.print("\"temperature\":["); + // Setup communications with the sensors. + dht_handler.Init(); + dt_handler.Init(); + // dt_handler.PrintDeviceInfo(); - for (int x = 0; x < NUM_DS18; x++) { - Serial.print(sensors.getTempCByIndex(x)); Serial.print(","); - } - Serial.print("],"); + pinMode(AC_PIN, INPUT); } - -/************************************ -* Utitlity Methods -*************************************/ - -void toggle_led() { - led_value = ! led_value; - digitalWrite(LED_BUILTIN, led_value); +void loop() { + serial_input_handler.Handle(); + + // Every REPORT_INTERVAL_MS we want to produce a report on sensor values, + // and relay settings. + static IntervalTimer report_timer(REPORT_INTERVAL_MS); + if (report_timer.HasExpired()) { + digitalWrite(LED_BUILTIN, HIGH); + Report(millis()); + digitalWrite(LED_BUILTIN, LOW); + } else if (!Serial) { + // Do a rapid blink of the LED if there is apparently no serial + // line connection. Note that we still call the serial input + // handler and print reports, just in case !Serial is wrong. + static IntervalTimer fast_blink_timer(100); + if (fast_blink_timer.HasExpired()) { + toggle_led(); + } + } }