diff --git a/.gitignore b/.gitignore index c01bd8c..15f7d3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ */__pycache__/ +*.DS_Store diff --git a/Arduino Sketches/Main/Incuvers_Incubator/Env_CO2_COZIR.h b/Arduino Sketches/Main/Incuvers_Incubator/Env_CO2_COZIR.h index 3453961..0916d44 100644 --- a/Arduino Sketches/Main/Incuvers_Incubator/Env_CO2_COZIR.h +++ b/Arduino Sketches/Main/Incuvers_Incubator/Env_CO2_COZIR.h @@ -1,4 +1,4 @@ -#ifdef INCLUDE_CO2 +#ifdef INCLUDE_CO2 #define CO2_STEP_THRESH 0.7 #define CO2_MULTIPLIER 10.0 #define CO2_DELTA_JUMP 3000 @@ -13,22 +13,24 @@ class IncuversCO2System { private: int pinAssignment_Valve; int mode; - + long tickTime; long actionpoint; long startCO2At; long shutCO2At; + long lastCalibration; + boolean recentlyCalibrated; boolean enabled; boolean on; boolean stepping; boolean started; boolean alarmOver; boolean alarmUnder; - + float level; float setPoint; - + IncuversSerialSensor* iSS; void CheckJumpStatus() { @@ -39,7 +41,7 @@ class IncuversCO2System { if (this->on) { if (this->tickTime >= this->shutCO2At) { digitalWrite(pinAssignment_Valve, LOW); - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.print(F("CO2 shut ")); Serial.print((this->tickTime-this->shutCO2At)); Serial.println(F("ms late")); @@ -51,24 +53,24 @@ class IncuversCO2System { } void GetCO2Reading_Cozir() { - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.println(F("GetCO2Reading_Cozir")); #endif - + String cozirString = ""; int reading; - + for (int i = 0; i < 2; i++) { cozirString = this->iSS->GetSerialSensorReading(6, 10); // Data in the stream looks like "Z 00400" - // The first number is the filtered value and the number after + // The first number is the filtered value and the number after // the 'z' is the raw value. We want the filtered value - + reading = GetIntegerSensorReading('Z', cozirString, -100); - + if (reading > 0 && reading < 300000) { - level = (float)((CO2_MULTIPLIER * reading)/10000); + level = (float)((CO2_MULTIPLIER * reading)/10000); #ifdef DEBUG_CO2 Serial.print(" CO2 level: "); Serial.println(level); @@ -78,13 +80,13 @@ class IncuversCO2System { #ifdef DEBUG_CO2 Serial.print(F("\tCO2 sensor returned invalid read, attempt")); Serial.println(i); - #endif + #endif } } } - + void CheckCO2Maintenance() { - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.print(F("CO2Maintenance()")); #endif if (level < setPoint && level >= 0) { @@ -95,12 +97,12 @@ class IncuversCO2System { delay(CO2_DELTA_STEPPING); digitalWrite(pinAssignment_Valve, LOW); this->actionpoint = this->tickTime; - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.println(F("\tCO2 step mode")); #endif } // there is no else, we need to wait for the bleedtime to expire. } else { - // below the setpoint and the stepping threshold, + // below the setpoint and the stepping threshold, if (!this->on && this->tickTime > (this->actionpoint + CO2_BLEEDTIME_JUMP)) { if (this->started == false) { this->started = true; @@ -108,7 +110,7 @@ class IncuversCO2System { } else { if (this->startCO2At + ALARM_CO2_OPEN_PERIOD < this->tickTime) { alarmUnder = true; - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.println(F("\tCO2 under-saturation alarm thrown")); #endif } @@ -116,16 +118,16 @@ class IncuversCO2System { this->on = true; this->shutCO2At = (this->tickTime + CO2_DELTA_JUMP); digitalWrite(pinAssignment_Valve, HIGH); - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.print(F("\tCO2 opening from ")); Serial.print(this->tickTime); Serial.print(F(" until ")); Serial.println(this->shutCO2At); #endif } else { - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.println(F("\tCO2 under but bleed remaining")); - #endif + #endif } } } else { @@ -135,7 +137,7 @@ class IncuversCO2System { if (level > (setPoint * ALARM_THRESH)) { // Alarm alarmOver = true; - #ifdef DEBUG_CO2 + #ifdef DEBUG_CO2 Serial.println(F("\tO2 over-saturation alarm")); #endif } @@ -147,17 +149,19 @@ class IncuversCO2System { #ifdef DEBUG_CO2 Serial.println(F("CO2::Setup")); #endif - + this->enabled = false; level = -100; + this->recentlyCalibrated = false; + this->lastCalibration=0; // Setup Serial Interface this->iSS = new IncuversSerialSensor(); - this->iSS->Initialize(rxPin, txPin, "K 2", "Z"); - + this->iSS->Initialize(rxPin, txPin, "K 2", "Z"); + //Setup the gas system this->pinAssignment_Valve = relayPin; pinMode(this->pinAssignment_Valve, OUTPUT); - + #ifdef DEBUG_CO2 Serial.println(F("Enabled")); Serial.print(F(" Rx: ")); @@ -167,14 +171,14 @@ class IncuversCO2System { Serial.print(F(" Relay: ")); Serial.println(relayPin); #endif - + this->MakeSafeState(); } void SetSetPoint(float tempSetPoint) { this->setPoint = tempSetPoint; } - + void MakeSafeState() { if (this->enabled) { digitalWrite(this->pinAssignment_Valve, LOW); // Set LOW (solenoid closed off) @@ -187,9 +191,9 @@ class IncuversCO2System { void DoTick() { if (this->enabled) { this->tickTime = millis(); - + if (mode == 2 && !this->stepping) { - this->CheckJumpStatus(); + this->CheckJumpStatus(); } this->GetCO2Reading_Cozir(); @@ -197,7 +201,13 @@ class IncuversCO2System { if (mode == 2) { this->CheckCO2Maintenance(); } + + } + // keep recently_calibrated flag true for 5 days + if (recentlyCalibrated and abs(millis() - this->lastCalibration)>432000000) { + recentlyCalibrated = false; } + } float getCO2Level() { @@ -237,19 +247,40 @@ class IncuversCO2System { alarmUnder = false; } + void CalibrateFreshAir() { + String cozirString = ""; + #ifdef DEBUG_SERIAL + Serial.print(F("Calibrate CO2 with fresh-air has been invoked")); + Serial.println(F("calling GetSerialSensorReading()...")); + #endif + cozirString = this->iSS->SendStringCommand("G"); + #ifdef DEBUG_SERIAL + Serial.print(F("Returned from GetSerialSensorReading()")); + Serial.print(cozirString); + Serial.print(F("should be: G 32950\r\n")); + #endif + this->recentlyCalibrated= true; + this->lastCalibration = millis(); + } + + boolean isRecentlyCalibrated() { + return this->recentlyCalibrated; + } + + }; #else class IncuversCO2System { public: void SetupCO2(int rxPin, int txPin, int relayPin) { - pinMode(relayPin, OUTPUT); + pinMode(relayPin, OUTPUT); digitalWrite(relayPin, LOW); // Set LOW (solenoid closed off) } void SetSetPoint(float tempSetPoint) { } - + void MakeSafeState() { } @@ -280,6 +311,9 @@ class IncuversCO2System { void ResetAlarms() { } + + void CalibrateFreshAir() { + } + }; #endif - diff --git a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Incubator.ino b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Incubator.ino index e493bfb..434baff 100644 --- a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Incubator.ino +++ b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Incubator.ino @@ -1,25 +1,25 @@ /* - * INCUVERS INCUBATOR - * Date: March 2019 + * INCUVERS INCUBATOR + * Date: June 2019 * Software version: 1.12 * Hardware version: 1.0.2 - * http://incuvers.com/ - * + * http://incuvers.com/ + * * Incuvers team: * Dr. Sebastian Hadjiantoniou * David Sean * Tim Spekkens */ -#define SOFTWARE_VER_STRING "v1.12 (05)" +#define SOFTWARE_VER_STRING "v1.12 (06)" /* Changelog - * + * * 1.12 - Added support for PiLink write-back. * - Modified the serial output overall format to match the PiLink input format * - Modified the serial output value format to match the PiLink input value format * - Added support for Control Board 1.0.2 - * + * * 1.11 - General code clean up and housekeeping. * - Switched serial sensors from streaming mode to on-demand polling. * - Added support for Modbus-based or voltage-based luminox sensors. @@ -30,28 +30,28 @@ * - Modified the minimum allowed value for O2 and CO2 setpoint. * - Fixed heat resume after settings screen. * - Modified the LCD screen to only update once per second. - * + * * 1.10 - Added support for chamber lighting control. * - Started making modules/skeletons to save on memory in Atmega328 implementations. - * + * * 1.9 - Moved key configuration elements to hardware-specific settings store. * - Rewrote the serial sensor monitoring systems. * - Subdivided code into several files in order to aid in maintenance. * - Added selectable fan modes * - improved the DEBUG options for smaller compiled results in production environments. * - removed ability to import settings - * + * * 1.8 - Addition of support for Digital O2 sensors. * * 1.7 - Addition of O2 sensor and related hardware - * + * * 1.6 - Bug fixes pertaining to display, alarms, and menu functionality. - * + * * 1.5 - Rewriting of alarm handling code. * - Addition of better sensor error detection and warning. - * + * * 1.4 - Rewrote the majority of the code for better stack efficiency and to allow for parallel operations. - * + * * 1.3 - Added the ability to save settings to the EEPROM. * - Fixed error leading to temperature sensors becoming swapped. * - Improved error condition shutdown handling. @@ -79,8 +79,8 @@ //#define DEBUG_EM true //#define DEBUG_CO2 true //#define DEBUG_O2 true -#define DEBUG_TEMP true -//#define DEBUG_PILINK true +//#define DEBUG_TEMP true +#define DEBUG_PILINK true //#define DEBUG_LIGHT true //#define DEBUG_MEMORY true @@ -101,7 +101,7 @@ // OneWire and DallasTemperature libraries used for reading the heat sensors #include "OneWire.h" #include "DallasTemperature.h" -#ifndef USE_2560 +#ifndef USE_2560 // Serial library used for querying the CO2/O2 sensor (ATMEGA 328 compatibility) #include "SoftwareSerial.h" #endif @@ -113,9 +113,9 @@ #include "Wire.h" #include "LiquidTWI2.h" // EEPROM library used for accessing settings -#include +#include -// Incuvers modules +// Incuvers modules #include "Incuvers_Common.h" #include "Incuvers_EnvironmentalManager.h" @@ -139,10 +139,8 @@ #ifdef INCLUDE_CO2 #include "Env_CO2_COZIR.h" #endif -#ifdef INCLUDE_LIGHT - #include "Env_Light.h" -#endif +#include "Env_Light.h" #include "Env_Heat.h" #include "Incuvers_Settings.h" #include "Incuvers_UI.h" @@ -164,7 +162,7 @@ void setup() { iUI = new IncuversUI(); iUI->SetupUI(); iUI->DisplayStartup(); - + iSettings = new IncuversSettingsHandler(); int runMode = 0; // 0 = uninitialized // 1 = saved settings @@ -181,7 +179,7 @@ void setup() { iLight = new IncuversLightingSystem(); iSettings->AttachIncuversModule(iLight); - + iCO2 = new IncuversCO2System(); iSettings->AttachIncuversModule(iCO2); @@ -190,9 +188,9 @@ void setup() { iPi = new IncuversPiLink(); iPi->SetupPiLink(iSettings); - + iUI->AttachSettings(iSettings); - iUI->DisplayRunMode(runMode); + iUI->DisplayRunMode(runMode); if (runMode == 2) { // Don't have the info we need, load default settings and go into setup iUI->EnterSetupMode(); @@ -206,9 +204,9 @@ void loop() { #ifdef DEBUG_GENERAL Serial.println(F("loop() call detected the Arduino has been on for a month, need to reset")); #endif - asm volatile (" jmp 0"); + asm volatile (" jmp 0"); } - + // Give all the modules a chance to do some work - While we used to use mini-ticks to support multiple Software-emulated serial connections, switching away from streaming mode // on the serial sensors negates the need to employ these extra steps. iHeat->DoTick(); @@ -216,6 +214,6 @@ void loop() { iO2->DoTick(); iHeat->DoQuickTick(); iLight->DoTick(); - iUI->DoTick(); + iUI->DoTick(); iPi->DoTick(); } diff --git a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_PiLink.h b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_PiLink.h index f15baf3..1fc937a 100644 --- a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_PiLink.h +++ b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_PiLink.h @@ -55,7 +55,7 @@ class IncuversPiLink { char* includedCRC = strtok(NULL, "$"); char* payload = strtok(NULL, "$"); // dump the rest of the message into this variable in order to computer the CRC char* ignoredData = strtok(stringRead, "$"); // return to the same location in the string in order to extract the parameters - + if (CheckCRCOnMessage(includedCRC, payload)) { boolean processingComplete = false; @@ -86,6 +86,12 @@ class IncuversPiLink { Serial.println(F("Requested CO2 set point is outside of min/max")); #endif } + } else if (strcmp(param, "CG") == 0) { + this->incSet->freshAirCalibrateCO2(); +#ifdef DEBUG_PILINK + Serial.println(F("CO2 Calibration requested")); +#endif + } else if (strcmp(param, "OP") == 0) { float newO2 = (float)value * 0.01; if (newO2 > OO_MIN && newO2 < OO_MAX) { diff --git a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Settings.h b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Settings.h index f0b4f31..c6bc3fe 100644 --- a/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Settings.h +++ b/Arduino Sketches/Main/Incuvers_Incubator/Incuvers_Settings.h @@ -1,7 +1,7 @@ // Hardware settings definitions #define HARDWARE_IDENT_OLD "M1c" #define HARDWARE_IDENT_NEW "M1I" -#define HARDWARE_ADDRS 4 +#define HARDWARE_ADDRS 4 // Settings definitions #define SETTINGS_IDENT_CURR 112 @@ -67,15 +67,15 @@ struct SettingsStruct { struct VolatileValuesStruct { bool wifiMACSet; - byte wifiMAC[18]; + char wifiMAC[18]; bool wiredMACSet; - byte wiredMAC[18]; + char wiredMAC[18]; bool ipV4Set; - byte activeV4IP[16]; + char activeV4IP[16]; bool ipV6Set; - byte activeV6IP[42]; - byte piSerial[18]; - byte valuesVersion; + char activeV6IP[42]; + char piSerial[18]; + byte valuesVersion; }; class IncuversSettingsHandler { @@ -83,50 +83,50 @@ class IncuversSettingsHandler { HardwareStruct settingsHardware; SettingsStruct settingsHolder; VolatileValuesStruct settingsRuntime; - + IncuversHeatingSystem* incHeat; IncuversLightingSystem* incLight; IncuversCO2System* incCO2; IncuversO2System* incO2; - boolean newHardwareConfig; - + boolean newHardwareConfig; + int personalityCount; void InitializeRuntimeSettings() { - strcpy_P(this->settingsRuntime.wifiMAC, (const char *) F("000000000000")); + strcpy_P(this->settingsRuntime.wifiMAC, (const char *) F("000000000000")); this->settingsRuntime.wifiMACSet = false; - strcpy_P(this->settingsRuntime.wiredMAC, (const char *) F("000000000000")); + strcpy_P(this->settingsRuntime.wiredMAC, (const char *) F("000000000000")); this->settingsRuntime.wiredMACSet = false; strcpy_P(this->settingsRuntime.activeV4IP, (const char *) F("0.0.0.0")); this->settingsRuntime.ipV4Set = false; strcpy_P(this->settingsRuntime.activeV6IP, (const char *) F("0::0")); this->settingsRuntime.ipV6Set = false; strcpy_P(this->settingsRuntime.piSerial, (const char *) F("0")); - this->settingsRuntime.valuesVersion = 0; + this->settingsRuntime.valuesVersion = 0; } - + int VerifyEEPROMHeader(int startAddress, boolean isHardware) { #ifdef DEBUG_EEPROM Serial.println(F("VerifyHead")); #endif - + byte eepromContent[4]; boolean success = true; boolean newStyle = false; int ret; - + int j; - if (isHardware) { + if (isHardware) { j = 3; } else { j = 1; } - + for (int i = 0; i < j; i++) { eepromContent[i] = EEPROM.read(startAddress + i); } - + if (isHardware) { for (int i = 0; i < j; i++) { if (eepromContent[i] != HARDWARE_IDENT_NEW[i]) { @@ -154,17 +154,17 @@ class IncuversSettingsHandler { } } else { if (eepromContent[0] == SETTINGS_IDENT_CURR) { - ret = (int)eepromContent[0]; + ret = (int)eepromContent[0]; } else { ret = -1; } } - + #ifdef DEBUG_EEPROM Serial.print(F(" /VerifyHead; returning: ")); Serial.println(ret); #endif - + return ret; } @@ -173,9 +173,9 @@ class IncuversSettingsHandler { Serial.println(F("HardwareSet")); #endif int headerCheck; - + headerCheck = VerifyEEPROMHeader(HARDWARE_ADDRS, true); - + if (headerCheck == -1) { // uninitialized hardware #ifdef DEBUG_EEPROM @@ -188,9 +188,9 @@ class IncuversSettingsHandler { Serial.print(sizeof(settingsHardware)); Serial.println(F(" bytes of hardware definitions.")); #endif - + for (int i = 0; i < sizeof(settingsHardware); i++) { - *((char*)&settingsHardware + i) = EEPROM.read(HARDWARE_ADDRS + i); + *((char*)&settingsHardware + i) = EEPROM.read(HARDWARE_ADDRS + i); } #ifdef DEBUG_EEPROM Serial.print(settingsHardware.ident[0]); @@ -255,11 +255,11 @@ class IncuversSettingsHandler { Serial.print(F(" @ ")); Serial.println(SETTINGS_ADDRS); #endif - + for (unsigned int i = 0; i < sizeof(this->settingsHolder); i++) { - *((char*)&this->settingsHolder + i) = EEPROM.read(SETTINGS_ADDRS + i); + *((char*)&this->settingsHolder + i) = EEPROM.read(SETTINGS_ADDRS + i); } - + #ifdef DEBUG_EEPROM Serial.println(settingsHolder.ident); Serial.println(settingsHolder.fanMode); @@ -276,9 +276,9 @@ class IncuversSettingsHandler { #endif return 1; } - + public: - + // Defaults void ResetSettingsToDefaults() { @@ -289,13 +289,13 @@ class IncuversSettingsHandler { this->settingsHolder.fanMode = 4; // Heat setup this->settingsHolder.heatMode = 1; - this->settingsHolder.heatSetPoint = TEMPERATURE_DEF; + this->settingsHolder.heatSetPoint = TEMPERATURE_DEF; // CO2 setup this->settingsHolder.CO2Mode = 2; - this->settingsHolder.CO2SetPoint = CO2_DEF; + this->settingsHolder.CO2SetPoint = CO2_DEF; // O2 setup this->settingsHolder.O2Mode = 2; - this->settingsHolder.O2SetPoint = OO_DEF; + this->settingsHolder.O2SetPoint = OO_DEF; // Lighting this->settingsHolder.lightMode = 0; this->settingsHolder.millisOn = 60000; // 14 hrs = 50400000 milliseconds @@ -308,14 +308,14 @@ class IncuversSettingsHandler { Serial.println(F("/Defaults")); #endif } - + void PerformSaveSettings() { #ifdef DEBUG_EEPROM Serial.println(F("SaveSettings")); #endif - + this->settingsHolder.ident = SETTINGS_IDENT_CURR; - + #ifdef DEBUG_EEPROM Serial.print(F("\tID: ")); Serial.print(SETTINGS_IDENT_CURR); @@ -323,26 +323,26 @@ class IncuversSettingsHandler { Serial.print(sizeof(settingsHolder)); Serial.print(" bytes"); #endif - + for (unsigned int i=0; i < sizeof(settingsHolder); i++) { EEPROM.write(SETTINGS_ADDRS + i, *((char*)&this->settingsHolder + i)); #ifdef DEBUG_EEPROM Serial.print("."); #endif } - + #ifdef DEBUG_EEPROM Serial.println(" Done."); Serial.println(F("/SaveSettings")); #endif } - + int PerformLoadSettings() { #ifdef DEBUG_EEPROM Serial.println(F("LoadSettings")); #endif int runMode = 0; - + if (ReadHardwareSettings()) { if (VerifyEEPROMHeader((int)SETTINGS_ADDRS, false) == SETTINGS_IDENT_CURR) { runMode = ReadCurrentSettings(); @@ -353,14 +353,14 @@ class IncuversSettingsHandler { #endif ResetSettingsToDefaults(); } - } - + } + #ifdef DEBUG_EEPROM Serial.println(F("/LoadSettings")); #endif return runMode; } - + void CheckSettings() { this->personalityCount = 0; if (this->settingsHolder.heatMode == 1) { @@ -375,7 +375,7 @@ class IncuversSettingsHandler { if (this->settingsHolder.O2Mode > 0) { this->personalityCount++; } - + #ifdef DEBUG_GENERAL Serial.print(F("Counted ")); Serial.print(this->personalityCount); @@ -387,7 +387,7 @@ class IncuversSettingsHandler { this->incHeat = iHeat; if (this->newHardwareConfig) { - this->incHeat->SetupNewHeating(PINASSIGN_HEATDOOR, + this->incHeat->SetupNewHeating(PINASSIGN_HEATDOOR, PINASSIGN_HEATCHAMBER, this->settingsHardware.sensorAddrDoorTemp[3], this->settingsHardware.sensorAddrDoorTemp[1], @@ -397,9 +397,9 @@ class IncuversSettingsHandler { this->settingsHolder.fanMode, this->settingsHolder.heatSetPoint); } else { - this->incHeat->SetupHeating(PINASSIGN_HEATDOOR, - PINASSIGN_HEATCHAMBER, - PINASSIGN_ONEWIRE_BUS, + this->incHeat->SetupHeating(PINASSIGN_HEATDOOR, + PINASSIGN_HEATCHAMBER, + PINASSIGN_ONEWIRE_BUS, this->settingsHardware.sensorAddrDoorTemp, this->settingsHardware.sensorAddrChamberTemp, this->settingsHolder.heatMode, @@ -427,11 +427,11 @@ class IncuversSettingsHandler { void AttachIncuversModule(IncuversCO2System* iCO2) { this->incCO2 = iCO2; - - this->incCO2->SetupCO2(this->settingsHardware.CO2RxPin, + + this->incCO2->SetupCO2(this->settingsHardware.CO2RxPin, this->settingsHardware.CO2TxPin, this->settingsHardware.CO2RelayPin); - this->incCO2->UpdateMode(this->settingsHolder.CO2Mode); + this->incCO2->UpdateMode(this->settingsHolder.CO2Mode); this->incCO2->SetSetPoint(this->settingsHolder.CO2SetPoint); } @@ -441,11 +441,11 @@ class IncuversSettingsHandler { void AttachIncuversModule(IncuversO2System* iO2) { this->incO2 = iO2; - - this->incO2->SetupO2(this->settingsHardware.O2RxPin, + + this->incO2->SetupO2(this->settingsHardware.O2RxPin, this->settingsHardware.O2TxPin, this->settingsHardware.O2RelayPin); - this->incO2->UpdateMode(this->settingsHolder.O2Mode); + this->incO2->UpdateMode(this->settingsHolder.O2Mode); this->incO2->SetSetPoint(this->settingsHolder.O2SetPoint); } @@ -475,6 +475,7 @@ class IncuversSettingsHandler { piLink = String(piLink + F("&CC|") + String(incCO2->getCO2Level() * 100, 0)); // CO2, reading piLink = String(piLink + F("&CS|") + GetIndicator(incCO2->isCO2Open(), incCO2->isCO2Stepping(), false, true)); // CO2, status piLink = String(piLink + F("&CA|") + GetIndicator(incCO2->isAlarmed(), false, false, true)); // CO2, alarms + piLink = String(piLink + F("&CG|") + String(incCO2->isRecentlyCalibrated(), DEC)); // CO2, reading // O2 system piLink = String(piLink + F("&OM|") + String(this->settingsHolder.O2Mode, DEC)); // O2, mode piLink = String(piLink + F("&OP|") + String(this->settingsHolder.O2SetPoint * 100, 0)); // O2, setpoint @@ -489,17 +490,17 @@ class IncuversSettingsHandler { if (includeCRC) { CRC32 crc; - + for (int i = 0; i < piLink.length(); i++) { crc.update(piLink[i]); } - + piLink = String(String(piLink.length(), DEC) + F("~") + PadHexToLen(String(crc.finalize(), HEX), 8) + F("$") + piLink); // CRC to detect corrupted entries } - + return piLink; } - + int getPersonalityCount() { return personalityCount; } @@ -521,7 +522,7 @@ class IncuversSettingsHandler { this->settingsHolder.fanMode = mode; this->incHeat->UpdateFanMode(mode); } - + float getTemperatureSetPoint() { return this->settingsHolder.heatSetPoint; } @@ -539,7 +540,7 @@ class IncuversSettingsHandler { this->settingsHolder.CO2Mode = mode; this->incCO2->UpdateMode(mode); } - + float getCO2SetPoint() { return this->settingsHolder.CO2SetPoint; } @@ -549,6 +550,10 @@ class IncuversSettingsHandler { this->incCO2->SetSetPoint(this->settingsHolder.CO2SetPoint); } + void freshAirCalibrateCO2() { + this->incCO2->CalibrateFreshAir(); + } + int getO2Mode() { return this->settingsHolder.O2Mode; } @@ -557,7 +562,7 @@ class IncuversSettingsHandler { this->settingsHolder.O2Mode = mode; this->incO2->UpdateMode(mode); } - + float getO2SetPoint() { return this->settingsHolder.O2SetPoint; } @@ -568,16 +573,16 @@ class IncuversSettingsHandler { } String getHardware() { - return String(this->settingsHardware.hVer[0])+"."+String(this->settingsHardware.hVer[1])+"."+String(this->settingsHardware.hVer[2]); + return String(this->settingsHardware.hVer[0])+"."+String(this->settingsHardware.hVer[1])+"."+String(this->settingsHardware.hVer[2]); } - + String getSerial() { String serial = String( (char *) this->settingsRuntime.piSerial); serial.trim(); return serial; } - void setSerial(byte serial[]) { + void setSerial(char serial[]) { strcpy(this->settingsRuntime.piSerial, (char *) serial); this->settingsRuntime.valuesVersion++; } @@ -596,12 +601,12 @@ class IncuversSettingsHandler { } } - void setIP4(byte ip[]) { + void setIP4(char ip[]) { strcpy(this->settingsRuntime.activeV4IP, (char *) ip); this->settingsRuntime.ipV4Set = true; this->settingsRuntime.valuesVersion++; } - + String getWireMAC() { if (this->settingsRuntime.wiredMACSet) { String wireMAC = String( (char *) this->settingsRuntime.wiredMAC); @@ -612,12 +617,12 @@ class IncuversSettingsHandler { } } - void setWireMAC(byte mac[]) { + void setWireMAC(char mac[]) { strcpy(this->settingsRuntime.wiredMAC, (char *) mac); this->settingsRuntime.wiredMACSet = true; this->settingsRuntime.valuesVersion++; } - + String getWifiMAC() { if (this->settingsRuntime.wifiMACSet) { String wifiMAC = String( (char *) this->settingsRuntime.wifiMAC); @@ -628,12 +633,12 @@ class IncuversSettingsHandler { } } - void setWifiMAC(byte mac[]) { + void setWifiMAC(char mac[]) { strcpy(this->settingsRuntime.wifiMAC, (char *) mac); this->settingsRuntime.wifiMACSet = true; this->settingsRuntime.valuesVersion++; } - + void MakeSafeState() { incHeat->MakeSafeState(); incLight->MakeSafeState(); @@ -656,7 +661,7 @@ class IncuversSettingsHandler { this->settingsHolder.lightMode = mode; this->incLight->UpdateMode(mode); } - + boolean HasCO2Sensor() { return settingsHardware.hasCO2Sensor; } @@ -679,16 +684,16 @@ class IncuversSettingsHandler { boolean HasLighting() { return settingsHardware.lightingSupport; } - + void ResetAlarms() { incHeat->ResetAlarms(); incCO2->ResetAlarms(); incO2->ResetAlarms(); } - + int getAlarmMode() { return this->settingsHolder.alarmMode; } - + }; diff --git a/Arduino Sketches/Main/Incuvers_Incubator/SenseWrap_Serial.h b/Arduino Sketches/Main/Incuvers_Incubator/SenseWrap_Serial.h index 786cd23..139d4c9 100644 --- a/Arduino Sketches/Main/Incuvers_Incubator/SenseWrap_Serial.h +++ b/Arduino Sketches/Main/Incuvers_Incubator/SenseWrap_Serial.h @@ -2,7 +2,7 @@ class IncuversSerialSensor { private: -#ifndef USE_2560 +#ifndef USE_2560 SoftwareSerial* dC; #else HardwareSerial* dC; @@ -12,8 +12,8 @@ class IncuversSerialSensor { public: void Initialize(int pinRx, int pinTx, String modeSet, String reqStr) { - -#ifndef USE_2560 + +#ifndef USE_2560 this->dC = new SoftwareSerial(pinRx, pinTx); // Rx,Tx #else switch (pinRx) { @@ -37,7 +37,7 @@ class IncuversSerialSensor { break; } #endif - this->setupString = modeSet; + this->setupString = modeSet; this->requestString = reqStr; //this->StartSensor(); @@ -48,12 +48,12 @@ class IncuversSerialSensor { String resp = ""; char inChar; long escapeAfter = millis() + READSENSOR_TIMEOUT; - + #ifdef DEBUG_SERIAL Serial.println(F("Starting...")); #endif this->dC->begin(9600); - + #ifdef DEBUG_SERIAL Serial.print(F("InitISS - modeSet: ")); Serial.print(this->setupString); @@ -61,11 +61,11 @@ class IncuversSerialSensor { #endif this->dC->print(this->setupString); this->dC->print("\r\n"); - + while(!confirmed && escapeAfter > millis()) { if (this->dC->available() > 0) { inChar = (char)this->dC->read(); - if (inChar != '\n' && inChar != '\r' ) { + if (inChar != '\n' && inChar != '\r' ) { resp += inChar; } if (inChar == '\n') { @@ -73,7 +73,7 @@ class IncuversSerialSensor { #ifdef DEBUG_SERIAL Serial.print(resp); #endif - confirmed = true; + confirmed = true; } } } @@ -81,7 +81,7 @@ class IncuversSerialSensor { Serial.println(""); #endif } - + String GetSerialSensorReading(int minLen, int maxLen) { #ifdef DEBUG_SERIAL @@ -91,7 +91,7 @@ class IncuversSerialSensor { Serial.print(maxLen); Serial.println(F(")")); #endif - + char inChar; String inString = ""; // string to hold incoming data from a serial sensor boolean stringComplete = false; // was all data from sensor received? Check @@ -100,11 +100,11 @@ class IncuversSerialSensor { int j = 0; long escapeAfter = millis() + READSENSOR_TIMEOUT; - #ifndef USE_2560 + #ifndef USE_2560 // We don't have a hardware serial interface, so make our software serial interface active. this->dC->listen(); #endif - + while (!dataReadComplete) { // request a reading this->dC->print(this->requestString); @@ -117,16 +117,16 @@ class IncuversSerialSensor { i=0; inString = ""; stringComplete = false; - + while(!stringComplete) { if (this->dC->available() > 0) { inChar = (char)this->dC->read(); - if (inChar != '\n' && inChar != '\r' ) { + if (inChar != '\n' && inChar != '\r' ) { inString += inChar; i++; } if (inChar == '\n' && inString.length() > minLen) { - stringComplete = true; + stringComplete = true; #ifdef DEBUG_SERIAL Serial.print(F("\tCompleted String: ")); Serial.println(inString); @@ -160,7 +160,7 @@ class IncuversSerialSensor { } j++; - + if(escapeAfter < millis()){ #ifdef DEBUG_SERIAL Serial.print(F("\tEscaping after ")); @@ -169,7 +169,7 @@ class IncuversSerialSensor { #endif dataReadComplete=true; } - + } // clear the queue, just in case @@ -187,6 +187,104 @@ class IncuversSerialSensor { #endif return inString; } + + String SendStringCommand(String commandString) { + #ifdef DEBUG_SERIAL + Serial.print(F("SenString(")); + Serial.print(minLen); + Serial.print(F(",")); + Serial.print(maxLen); + Serial.println(F(")")); + #endif + char inChar; + String inString = ""; // string to hold incoming data from a serial sensor + boolean stringComplete = false; // was all data from sensor received? Check + boolean dataReadComplete = false; + int minLen = 6; + int maxLen = 10; + int i = 0; + int j = 0; + long escapeAfter = millis() + READSENSOR_TIMEOUT; + + #ifndef USE_2560 + // We don't have a hardware serial interface, so make our software serial interface active. + this->dC->listen(); + #endif + + while (!dataReadComplete) { + // request a reading + this->dC->print(commandString); + this->dC->print("\r\n"); + #ifdef DEBUG_SERIAL + Serial.print(F("Sending Request: ")); + Serial.println(commandString); + #endif + delay(200); // there will likely be a 100ms delay on the response + i=0; + inString = ""; + stringComplete = false; + + while(!stringComplete) { + if (this->dC->available() > 0) { + inChar = (char)this->dC->read(); + if (inChar != '\n' && inChar != '\r' ) { + inString += inChar; + i++; + } + if (inChar == '\n' && inString.length() > minLen) { + stringComplete = true; + #ifdef DEBUG_SERIAL + Serial.print(F("\tCompleted String: ")); + Serial.println(inString); + #endif + } + } + + if (escapeAfter < millis()) { + #ifdef DEBUG_SERIAL + Serial.println(F("\tEscaping the try due to timeout")); + #endif + // fake the end of the string as we hit the timeout + stringComplete = true; + } + } + + if (i >= minLen && i <= maxLen) { + dataReadComplete = true; + + #ifdef DEBUG_SERIAL + Serial.print(F("Data read complete: ")); + Serial.println(i); + #endif + } else { + #ifdef DEBUG_SERIAL + Serial.print(F("Data read incomplete with i: ")); + Serial.print(i); + Serial.print(F(", string: ")); + Serial.print(inString); + #endif + } + + j++; + + if(escapeAfter < millis()){ + #ifdef DEBUG_SERIAL + Serial.print(F("\tEscaping after ")); + Serial.print(j); + Serial.println(F(" loops")); + #endif + dataReadComplete=true; + } + + } + + #ifdef DEBUG_SERIAL + Serial.print(F("\tReturning: ")); + Serial.println(inString); + #endif + return inString; + } + }; boolean IsNumeric(String string) { @@ -266,8 +364,6 @@ int GetIntegerSensorReading(char index, String sensorOutput, int invalidValue) { Serial.print(F("\tValue: ")); Serial.println(value); #endif - + return value; } - - diff --git a/Arduino Sketches/Support/HardwareInitTool/HardwareInitTool.ino b/Arduino Sketches/Support/HardwareInitTool/HardwareInitTool.ino index 47bb31f..9872ca6 100644 --- a/Arduino Sketches/Support/HardwareInitTool/HardwareInitTool.ino +++ b/Arduino Sketches/Support/HardwareInitTool/HardwareInitTool.ino @@ -227,7 +227,7 @@ void setup() { hardwareDefinition.hVer[1]=0; hardwareDefinition.hVer[2]=2; // Hardware serial number - hardwareDefinition.serial=99999; // Serial number as printed inside the top cover + hardwareDefinition.serial=9999; // Serial number as printed inside the top cover // Temperature sensors hardwareDefinition.countOfTempSensors=3; // Count of temperature sensors installed hardwareDefinition.sensorAddrOne[0] = 1; @@ -321,4 +321,3 @@ void loop() { delay(1000); } } - diff --git a/README.md b/README.md index 9f9ac83..7bed649 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Administrative: |`MWR` |Pi wired MAC address | ASCII string | ... | |`MWF` |Pi wifi MAC address | ASCII string | ... | |`SS` |Save settings |Boolean | `1` (save) | - + |`CG` |CO2 calibration | Boolean |`1` (true) Environmental: @@ -139,3 +139,21 @@ Environmental: |`CP` |CO2 set point | Hundredths of % |`520` (5.20%) |`OM` |O2 mode | Enum | `2` (maintain) | |`OP` |O2 set point | Hundredths of % |`2000` (20.00%) + +#### Gas sensor calibration + The needs to be calibrated once in a while. + Triggering the calibration procedure through the UI isn't yet supported (a manual call to `iCO2->CalibrateFreshAir()` is needed). +The calibration is can be triggered by sending the string the parameter pair `CG|1` from a connected raspberry pi (via the message `4~3ab55f3b$CG|1`). + +The Arduino will confirm the request by advertising a recent calibration by broadcasting `CG|1` instead of `CG|0`. +After 5 days or a reboot, the messages will revert to the default broadcast of `CG|0`. + +The O2 sensor does not need a periodic calibration. + +##### Fresh-air calibration procedure. +Prior to a calibration call, the environment in the incubator must be as close as possible to "fresh-air" conditions (400ppm is assumed). +Open the door of the chamber with heating turned off and the fans turned on. +The CO2 and O2 controls must both be set to `monitor`). +Adequate time must have elapsed (at least 5 minutes) for the interior of the chamber to equilibrate. +Shut the door and make a system call to `CalibrateFreshAir()` by sending `CG|1`. +The CO2 sensor is now calibrated. diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index a664595..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = monitor -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/build/.gitignore b/doc/build/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/doc/build/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/doc/source/_static/logo.png b/doc/source/_static/logo.png deleted file mode 100644 index 450ca1b..0000000 Binary files a/doc/source/_static/logo.png and /dev/null differ diff --git a/doc/source/arduino.rst b/doc/source/arduino.rst deleted file mode 100644 index a9e45bc..0000000 --- a/doc/source/arduino.rst +++ /dev/null @@ -1,7 +0,0 @@ -Arduino Board -============= - -Low-level environmental controls are handled by the Arduino board. -A minimal interface is provided for user input (push buttons) and output (LCD screen). - -The Arduino code can be found under the directory `Arduino Sketches`. diff --git a/doc/source/conf.py b/doc/source/conf.py deleted file mode 100644 index 3586f44..0000000 --- a/doc/source/conf.py +++ /dev/null @@ -1,192 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# - -import os -import sys -sys.path.insert(0, os.path.abspath('../../')) -# -- Project information ----------------------------------------------------- - -project = 'Incubator monitor' -copyright = '2019, Incuvers Inc' -author = 'David Sean, Tim Spekkens' - -# The short X.Y version -version = '0.1' -# The full version, including alpha/beta/rc tags -release = '0.1' - - -# -- General configuration --------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = '.rst' - -# The master toctree document. -master_doc = 'index' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = None - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'alabaster' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# The default sidebars (for documents that don't match any pattern) are -# defined by theme itself. Builtin themes are using these templates by -# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', -# 'searchbox.html']``. -# -# html_sidebars = {} - -html_logo = "./_static/logo.png" -html_favicon = "./_static/favicon.ico" - -# -- Options for HTMLHelp output --------------------------------------------- - -# Output file base name for HTML help builder. -htmlhelp_basename = 'RPi_monitordoc' - - -# -- Options for LaTeX output ------------------------------------------------ - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, 'RPi_monitor.tex', 'RPi\\_monitor Documentation', - 'David Sean, Tim Spekkens', 'manual'), -] - - -# -- Options for manual page output ------------------------------------------ - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'rpi_monitor', 'RPi_monitor Documentation', - [author], 1) -] - - -# -- Options for Texinfo output ---------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - (master_doc, 'RPi_monitor', 'RPi_monitor Documentation', - author, 'RPi_monitor', 'One line description of project.', - 'Miscellaneous'), -] - - -# -- Options for Epub output ------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] - - - -# -- Extension configuration ------------------------------------------------- - -# -- Options for todo extension ---------------------------------------------- - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = True - - - diff --git a/doc/source/index.rst b/doc/source/index.rst deleted file mode 100644 index 6641562..0000000 --- a/doc/source/index.rst +++ /dev/null @@ -1,32 +0,0 @@ -.. RPi_monitor documentation master file, created by - sphinx-quickstart on Tue Feb 26 09:36:40 2019. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Incuvers telemetric chamber documentation -========================================= - - -The Incuvers telemetric chambers have two basic components: -a bare-metal environmental control unit based on an Arduino, -and a high level programming interface based on a Raspberry-Pi. - -If used as a regular `dumb` incubator, one does not need to use the Raspberry-Pi interface, however this will severely limit the capacities. -The Raspberry-Pi allows to implement time-based environmental programs, logging, time-lapse imaging, real-time imaging and even connects to the cloud via your local wifi or wired network for cloud-based controlls/monitoring (by logging on the Incuvers app with desktop/tablet/mobile phone). - - - - -Arduino -======= -.. toctree:: - :maxdepth: 2 - - arduino.rst - -Raspberry Pi -============ -.. toctree:: - :maxdepth: 2 - - rpi.rst diff --git a/doc/source/monitor.rst b/doc/source/monitor.rst deleted file mode 100644 index 0d72eaf..0000000 --- a/doc/source/monitor.rst +++ /dev/null @@ -1,24 +0,0 @@ -monitor package -=============== - -Submodules ----------- - -monitor.Message module ----------------------- - -.. automodule:: monitor.Message - :members: - :undoc-members: - :show-inheritance: - - - - -Module contents ---------------- - -.. automodule:: monitor - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/rpi.rst b/doc/source/rpi.rst deleted file mode 100644 index 9f14645..0000000 --- a/doc/source/rpi.rst +++ /dev/null @@ -1,13 +0,0 @@ -Raspberry Pi -============ - -High-level environmental controls are handled by a Raspberry-Pi connected to the Arduino board. - -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - monitor - - - diff --git a/monitor/Message.py b/monitor/Message.py deleted file mode 100644 index f6ad475..0000000 --- a/monitor/Message.py +++ /dev/null @@ -1,484 +0,0 @@ -import base64 -import binascii -import json -import uuid -import serial -import socket -import struct -import sys -import threading -import time -import fcntl -import commands - -""" -.. module:: Message - :platform: Raspbian - :synopsis: Incubator module for serial communication between RPi and Arduino - -""" - - -class ArduinoDirectiveHandler(): - """ Class that handles sending commands to the arduino and verifying - that the csommands have been accepted and acted uopn by the control - board. - """ - - def __init__(self): - ''' Initialization - - Args: - None - ''' - self.queuedictionary = {} - - def enqueue_parameter_update(self, param, value): - """ Adds a parameter to the update queue, the updates will be sent - at a later time. If this update follows another requested update, - overwrite it. The item will remain in the queue until the action - has been verified - - Args: - param (str): the identifier of the parameter to update - value (int): the value of the parameter to update - """ - - if param in self.queuedictionary: - print("The " + param + - " parameter was already in the dictionary, updating.") - self.queuedictionary[param] = value - - def get_arduino_command_string(self): - """ Returns a complete string to send to the arduino in order to - update the running parameters. Commands can be no longer than 80 - characters and won't involve removing the command from the queue, - a separate system will verify that the change has been made. - - Args: - None - """ - arduino_command_string = "" - for param in self.queuedictionary: - if (len(arduino_command_string) + len(param) + len(str(self.queuedictionary[param])) + 2) <= 80: - if len(arduino_command_string) > 0: - arduino_command_string = arduino_command_string + "&" - arduino_command_string = arduino_command_string + \ - param + "|" + str(self.queuedictionary[param]) - commandLen = len(arduino_command_string) - arduino_command_string = arduino_command_string + "\r" - calcCRC = binascii.crc32(arduino_command_string.encode()) & 0xFFFFFFFF - - arduino_command_string = str(commandLen) + "~" + format(calcCRC, - 'x') + "$" + arduino_command_string + "\n" - - return arduino_command_string - - def verify_arduino_response(self, last_sensor_frame): - """ Will check all commands in the queuedictionary and pop off any - for which the state coming from the arduino matches the desired level - - Args: - last_sensor_frame (dict): the last sensorframe received from the Arduino - """ - - return() - - def clear_queue(self): - """ - Command to erase everything in the queue. - - Args: - None - """ - - self.queuedictionary.clear() - - def get_arduino_mac_update_command_string(self, param="MWR", mac_addr="00:00:00:00:00:00"): - """ Should only be called shortly after a boot - - Args: - param (str): three-letter code for the start of the parameter (MWR for Ethernet, MWF for Wifi) - mac_addr (str): colon-separated string of the current system's MAC address - """ - self.clear_queue() - self.enqueue_parameter_update(param, "".join(mac_addr.split(":"))) - cmd_string = self.get_arduino_command_string() - self.clear_queue() - return cmd_string - - def get_arduino_ip_update_command_string(self, ip_addr): - """ Should only be called after a boot or when noticing an IP - change - - Args: - ip_addr (str): string containing the IP address of the item - """ - self.clear_queue() - self.enqueue_parameter_update("IP4", ip_addr) - cmd_string = self.get_arduino_command_string() - self.clear_queue() - return cmd_string - - def get_arduino_serial_update_command_string(self, serial): - """ Should only be called after a boot - - Args: - serial (str): string containing the serial of the rPi - """ - self.clear_queue() - self.enqueue_parameter_update("ID", serial) - cmd_string = self.get_arduino_command_string() - self.clear_queue() - return cmd_string - - -class Interface(): - """ Class that holds everything important concerning communication. - - Mainly stuff about the serial connection, but also - identification and internet connectivity. Objects indended - to be displayed on the Incubator LCD should be found in this class. - - Attributes: - serial_connection (:obj:`Serial`): serial connection from GPIO - """ - - def __init__(self): - ''' Initialization - Args: - - None - ''' - self.serial_connection = serial.Serial(port='/dev/serial0', baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, xonxoff=False, rtscts=False, dsrdtr=False) - - def connects_to_internet(self, host="8.8.8.8", port=53, timeout=3): - """ Tests internet connectivity - Test intenet connectivity by checking with Google - Host: 8.8.8.8 (google-public-dns-a.google.com) - OpenPort: 53/tcp - Service: domain (DNS/TCP) - - Args: - host (str): the host to test connection - port (int): the port to use - timeout (timeout): the timeout value (in seconds) - """ - try: - socket.setdefaulttimeout(timeout) - socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) - return True - except Exception as err: - print(err.message) - return False - - def get_serial_number(self): - """ Get uniqeu serial number - Get a unique serial number from the cpu, shortened - for easy readability. - - Args: - None - """ - serial = "0000000000" - try: - with open('/proc/cpuinfo', 'r') as fp: - for line in fp: - if line[0:6] == 'Serial': - serial = line[10:26] - serial = serial.lstrip("0") - if serial == "0000000000": - raise TypeError('Could not extract serial from /proc/cpuinfo') - except FileExistsError as err: - serial = "0000000000" - print(err.message) - - except TypeError as err: - serial = "0000000000" - print(err.message) - return serial - - def get_iface_list(self): - """ Get the list of interfaces - - Args: - None - - Returns: - """ - return "Not implemented" - - def get_iface_hardware_address(self, iface): - """ Get the hardware (MAC) address of the given interface - - Args: - iface (str): interface to return the mac off. - - Returns: - str: colon-delimited hardware address - """ - try: - mac = open('/sys/class/net/'+iface+'/address').readline() - except: - hex = uuid.getnode() - mac = ':'.join(['{:02x}'.format((hex >> ele) & 0xff) - for ele in range(0, 8*6, 8)][::-1]) - - return mac[0:17] - - def get_mac_address(self): - """ Get the ethernet MAC address - This returns in the MAC address in the canonical human-reable form - - Args: - None - """ - hex = uuid.getnode() - formatted = ':'.join(['{:02x}'.format((hex >> ele) & 0xff) - for ele in range(0, 8*6, 8)][::-1]) - return formatted - - def get_ip_address(self): - """ Get the primary IP address - - Args: - None - - Returns: - String: standard representation of the device's IP address - """ - ip = commands.getoutput('hostname -I') - ip = ip.rstrip() - return ip - - -class Sensors(): - """ Class to handle sensor communication - - Attributes: - sensorframe (dict : Dictionary holding all sensor key-value pairs - verbosity (int) : set sthe amount of information printed to screen - arduino_link (): Instance of the Interface() class - lock (): Instance of threading Lock to - monitor (): Process that continuously read incomming serial messages - """ - - def __init__(self): - """ Initialization - """ - # dictionary to hold incubator status - self.sensorframe = {} - - # set verbosity=1 to view more - self.verbosity = 1 - - self.arduino_link = Interface() - - self.lock = threading.Lock() - self.monitor = threading.Thread(target=self.monitor_incubator_message) - self.monitor.setDaemon(True) - self.monitor.start() - - def __str__(self): - with self.lock: - toReturn = self.sensorframe - return str(toReturn) - - def json_sensorframe(self): - """ JSON object containing arduino serial message - Returns a JSON formated string of the current state - - Args: - None - """ - with self.lock: - toReturn = {'incubator': self.sensorframe} - return toReturn - - def monitor_incubator_message(self): - ''' Processes serial messages from Arduino - This runs continuously to maintain an updated - status of the sensors from the arduino - - Args: - None - ''' - if (self.verbosity == 1): - print("starting monitor") - while True: -# if self.arduino_link.serial_connection.in_waiting: - try: - line = self.arduino_link.serial_connection.readline().rstrip() - if self.checksum_passed(line): - if (self.verbosity == 1): - print("checksum passed") - with self.lock: - self.save_message_dict(line) - else: - if (self.verbosity == 1): - print('Ign: ' + line.decode().rstrip()) - except BaseException as e: - time.sleep(1) - print('Error: ', e) - # return -# else: - # print('Waiting... ' + format(self.arduino_link.serial_connection.in_waiting)) - # time.sleep(4) - - def pop_param(self, msg, char): - ''' pop - - Args: - char (char): a special character occuring only once - msg (str): a string containing the the special character - - Returns: - (sub_msg[0], sub_msg[1]) : two sub strings on either side of char. - When there is more than one occurence of char (or when it is not present), - returns the tuple (False, msg) - ''' - - msg = msg.decode().split(char) - if len(msg) != 2: - if (self.verbosity == 1): - print("Corrupt message: while splitting with special character '{}' ".format(char)) - return False, msg - return msg[0], msg[1] - - def checksum_passed(self, msg): - ''' Check if the checksum passed - recompute message checksum and compares with appended hash - - Args: - msg (str): a string containing the message, having the following - format: Len~CRC32$Param|Value&Param|Value - - Returns: - `True` when `msg` passes the checksum, `False` otherwise - ''' - # pop the Len - msg_len, msg = self.pop_param(msg, '~') - if msg_len == False: - return False - # pop the Crc - msg_crc, msg = self.pop_param(msg, '$') - if msg_crc == False: - return False - - # compare CRC32 - calcCRC = binascii.crc32(msg.rstrip()) & 0xffffffff - if format(calcCRC, 'x') == msg_crc.lstrip("0"): - if (self.verbosity == 1): - print("CRC32 matches; message: " + msg) - return True - else: - if (self.verbosity == 1): - print( - "CRC32 Fail: calculated " + - format(calcCRC, 'x') + - " but received " + - msg_crc) - return False - - def save_message_dict(self, msg): - ''' - Takes the serial output from the incubator and creates a dictionary - using the two letter ident as a key and the value as a value. - - Args: - msg (string): The raw serial message (including the trailing checksum) - - Only use a message that passed the checksum! - ''' - - # pop the Len and CRC out - tmp, msg = self.pop_param(msg, '$') - - if tmp == False: - return False - self.sensorframe = {} - for params in msg.split('&'): - kvp = params.split("|") - if len(kvp) != 2: - print("ERROR: bad key-value pair") - else: - self.sensorframe[kvp[0].encode("utf-8")] = kvp[1].encode("utf-8") - self.sensorframe['Time'] = time.strftime( - "%Y-%m-%d %H:%M:%S", time.gmtime()) - return - - def send_message_to_arduino(self, msg): - ''' - Will transmit a string to the Arduino via the serial link. - - Args: - msg (string): The message string to be sent to the Arduino, including checksums et all. - ''' - - try: - self.arduino_link.serial_connection.write(msg.encode()) - self.arduino_link.serial_connection.flush() - except BaseException as e: - time.sleep(1) - print('Error: ', e) - - - -if __name__ == '__main__': - print("PySerial version: " + serial.__version__) - - test_connections = False - test_connections = True - if test_connections: - iface = Interface() - print("Hardware serial number: {}".format(iface.get_serial_number())) - print("Network interfaces: {}".format(iface.get_iface_list())) - print("Hardware address (ethernet): {}".format(iface.get_iface_hardware_address("eth0"))) - print("Hardware address (wifi): {}".format(iface.get_iface_hardware_address("wlan0"))) - print("Primary IP address: {}".format(iface.get_ip_address())) - print("Can connect to the internet: {}".format(iface.connects_to_internet())) - print("Can contact the mothership: {}".format( - iface.connects_to_internet(host='35.183.143.177', port=80))) - print("Has serial connection with Arduino: {}".format(iface.serial_connection.is_open)) - del iface - - test_commandset = True - if test_commandset: - arduino_handler = ArduinoDirectiveHandler() - mon = Sensors() - - msg0 = arduino_handler.get_arduino_serial_update_command_string(mon.arduino_link.get_serial_number()) - print("Sending message: " + msg0) - mon.send_message_to_arduino(msg0) - time.sleep(5) - - msg1 = arduino_handler.get_arduino_ip_update_command_string(mon.arduino_link.get_ip_address()) - print("Sending message: " + msg1) - mon.send_message_to_arduino(msg1) - time.sleep(5) - - msg2 = arduino_handler.get_arduino_mac_update_command_string("MWR", mon.arduino_link.get_iface_hardware_address("eth0")) - print("Sending message: " + msg2) - mon.send_message_to_arduino(msg2) - time.sleep(5) - - msg3 = arduino_handler.get_arduino_mac_update_command_string("MWF", mon.arduino_link.get_iface_hardware_address("wlan0")) - print("Sending message: " + msg3) - mon.send_message_to_arduino(msg3) - - del arduino_handler - - monitor_serial = False - monitor_serial = True - if monitor_serial: - mon = Sensors() - mon.verbosity = 0 - print("Has serial connection with Arduino: {}".format( - mon.arduino_link.serial_connection.is_open)) - print("Displaying serial data") - while True: - time.sleep(5) - # print("trying...") - print(mon.sensorframe) - # print(mon.arduino_link.serial_connection.readline()) - del mon diff --git a/monitor/__init__.py b/monitor/__init__.py deleted file mode 100644 index a07ee10..0000000 --- a/monitor/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .Message import Sensors, Interface diff --git a/monitor/requirements.txt b/monitor/requirements.txt deleted file mode 100644 index a5d42eb..0000000 --- a/monitor/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pyserial>=3.4 diff --git a/tests/common.py b/tests/common.py deleted file mode 100644 index be1eb30..0000000 --- a/tests/common.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys -import os -sys.path.insert(0, os.path.abspath('../../Model-1')) diff --git a/tests/interface.py b/tests/interface.py deleted file mode 100644 index b6aca27..0000000 --- a/tests/interface.py +++ /dev/null @@ -1,60 +0,0 @@ -import common -import monitor -import unittest -import time - -class TestInterface(unittest.TestCase): - iface = monitor.Interface() - - def test_serial_number(self): - # print("Hardware serial number: {}".format(iface.get_serial_number())) - # this is only valid for the demo unit - self.assertEqual(self.iface.get_serial_number(), "00000000f8aa31fe") - - def test_get_mac_address(self): - # print("Hardware address (ethernet): {}".format(iface.get_mac_address())) - self.assertEqual(self.iface.get_mac_address(), "b8:27:eb:ff:64:ab") - - def test_internet_connection(self): - # print("Can contact the mothership: {}".format(iface.connects_to_internet())) - self.assertTrue(self.iface.connects_to_internet()) - # print("Can contact the mothership: {}".format(iface.connects_to_internet(host = '35.183.143.177', port = 80))) - self.assertTrue(self.iface.connects_to_internet(host='35.183.143.177', port=80)) - - def serial_connection(self): - # print("Has serial connection with Arduino: {}".format(iface.serial_connection.is_open())) - self.unittest.assertTrue(self.iface.serial_connection.is_open()) - - - -class TestArduino(unittest.TestCase): - #arduino_handler = monitor.ArduinoDirectiveHandler() - mon = monitor.Sensors() - mon.verbosity=1 - tags=['TP','TC','TD','TA','TS','TM','FM','CP','CC','CS','CA','CM','OP','OC','OS','OM','OA'] - tags.append('time') - tags.append('ID') - tags.append('IV') - timeout = 0 - while len(mon.sensorframe)==0 and timeout<30: - # leave some time to get a valid sensorframe - time.sleep(1) - timeout+=1 - print(timeout, mon.sensorframe) - - - def test_message_rcv(self): - self.assertFalse(len(mon.sensorframe)==0) - - def test_message_len(self): - print(len(self.tags)) - print(self.mon.sensorframe) - self.assertTrue(len(self.mon.sensorframe)==len(self.tags)) - - def test_tags(self): - for tag in self.tags: - self.assertTrue(tag in self.mon.sensorframe) - - -if __name__ == '__main__': - unittest.main()