commit c3121c907ae16c4f40cc8069f08668c875a7eb28 Author: 2ManyProjects Date: Mon Mar 10 19:31:28 2025 -0500 BioController diff --git a/AutoHumidity.ino b/AutoHumidity.ino new file mode 100755 index 0000000..c6912d8 --- /dev/null +++ b/AutoHumidity.ino @@ -0,0 +1,371 @@ + + +// #define DHTTYPE DHT11 // DHT 11 + +//Controls Mushroom Grow tent +/* + Fogger (main Fan) + + UV light + + Humidity / Temp 1 + + Humidity / Temp 2 + + Heatemitter + + Potential CO2 meter + +*/ +Adafruit_AHTX0 aht; +Adafruit_SCD30 scd30; // address: 0x61 +// int upperDHT = 5;// address: 0x38 +// int lowerDHT = 6; +int heatPin = 44; +int foggerFanPin = 46; +int uvPin = 7; +int mainFan = 42; +// DHT upper(upperDHT, DHTTYPE); +// DHT lower(lowerDHT, DHTTYPE); +GasSensor co2Sensor = { {0, 0, 0, 0, 0}, 0, 0, scd30}; +HTSensor upperSensor = {{0, 0, 0, 0, 0}, 0, 0, {0, 0, 0, 0, 0}, 0, 0, aht}; +LvlSensor waterSensor = { {0, 0, 0, 0, 0}, 0, 4, 3, 20}; +// HTSensor lowerSensor = {{0, 0, 0, 0, 0}, 0, 0, {0, 0, 0, 0, 0}, 0, 0, lower}; +MarthaDrain heatLamp = {heatPin, false, 'H'}; +MarthaDrain fogger = {foggerFanPin, false, 'W'}; +MarthaDrain UVLight = {uvPin, false, 'L'}; +MarthaDrain AirFlowFan = {mainFan, false, 'F'}; +unsigned long lastAutoReading; +int autoReadIntervalMs = 500; +unsigned long loopIndex = 0; +//TODO: add General Airflow fan + +void setupAutoHumidity(float goal, float goalh){ + aht.begin(); + // lowerSensor.aht.begin() ; + pinMode(waterSensor.trig, OUTPUT); // Initializing Trigger Output and Echo Input + pinMode(waterSensor.echo, INPUT_PULLUP); + pinMode(heatLamp.pin, OUTPUT); + pinMode(fogger.pin, OUTPUT); //Fogger connects to the mister + fan + pinMode(UVLight.pin, OUTPUT); + pinMode(AirFlowFan.pin, OUTPUT); + digitalWrite(heatLamp.pin, HIGH); + digitalWrite(fogger.pin, HIGH); + digitalWrite(UVLight.pin, HIGH); + digitalWrite(AirFlowFan.pin, HIGH); + lastAutoReading = millis(); + initHTSensor(upperSensor); + // initHTSensor(lowerSensor); + setAllGoalTemps(goal); + setAllGoalHum(goalh); + while(!co2Sensor.sensor.begin()){ + Serial.println("failed to init chip, please check if the chip connection is fine"); + delay(1000); + } + // co2Sensor.sensor.setMeasCycle(co2Sensor.sensor.eCycle_250ms); + + turnOnAutoDrain(AirFlowFan); +} +void AutoControlLoop() { + unsigned long currentTime = millis(); + if(currentTime - lastAutoReading >= autoReadIntervalMs){ + loopIndex += 1; + updateAutoSensors(); + updateAutoDrains(); + updateWaterLevel(); + if(loopIndex % 4 == 0){ //Updates every1.5 second + updateCO2Sensor(); + } + + lastAutoReading = currentTime; + } +} + +void updateWaterLevel(){ + digitalWrite(waterSensor.trig, LOW); // Set the trigger pin to low for 2uS + delayMicroseconds(2); + + digitalWrite(waterSensor.trig, HIGH); // Send a 10uS high to trigger ranging + delayMicroseconds(20); + + digitalWrite(waterSensor.trig, LOW); // Send pin low again + int distance = pulseIn(waterSensor.echo, HIGH,26000); // Read in times pulse + + Serial.print("distance: "); + Serial.print(distance); + // distance= distance/58; + // Serial.print(", "); + // Serial.print(distance); + Serial.println(""); +// if(abs(device.sensor.history[0] - tempC) > WARNING_LOG_DELTA_THRESHOLD){ + // //SOME SORT OF LOGGING HERE + // } + for(byte i = (sizeof(waterSensor.history) / sizeof(waterSensor.history[0])) - 1; i >= 1; i--){ + waterSensor.history[i] = waterSensor.history[i - 1]; + } + waterSensor.history[0] = distance; + int sum = 0; + for(byte i = 0; i < (sizeof(waterSensor.history) / sizeof(waterSensor.history[0])); i++){ + sum += waterSensor.history[i]; + } + int newAgg = sum / (sizeof(waterSensor.history) / sizeof(waterSensor.history[0])); + waterSensor.currentAgg = newAgg; + +} + +void updateCO2Sensor(){ + if(co2Sensor.sensor.dataReady() && co2Sensor.sensor.read()){ // handle not ready or error reading + int co2 = (int)co2Sensor.sensor.CO2; + float hum = co2Sensor.sensor.relative_humidity; + float temp = co2Sensor.sensor.temperature; + Serial.print("CO2: "); + Serial.print(co2); + Serial.print("ppm, Temp: "); + Serial.print(temp); + Serial.print(" C, Hum: "); + Serial.print(hum); + Serial.println("%"); + + // if(abs(device.sensor.history[0] - tempC) > WARNING_LOG_DELTA_THRESHOLD){ + // //SOME SORT OF LOGGING HERE + // } + for(byte i = (sizeof(co2Sensor.history) / sizeof(co2Sensor.history[0])) - 1; i >= 1; i--){ + co2Sensor.history[i] = co2Sensor.history[i - 1]; + } + co2Sensor.history[0] = co2; + int sum = 0; + for(byte i = 0; i < (sizeof(co2Sensor.history) / sizeof(co2Sensor.history[0])); i++){ + sum += co2Sensor.history[i]; + } + int newAgg = sum / (sizeof(co2Sensor.history) / sizeof(co2Sensor.history[0])); + co2Sensor.currentAgg = newAgg; + co2Sensor.currentTemp = temp; + co2Sensor.currentHum = hum; + + } else { + Serial.println("Data is not ready!"); + if(!co2Sensor.sensor.begin()){ + Serial.println("failed to init chip, please check if the chip connection is fine"); + } + // co2Sensor.sensor.setMeasCycle(co2Sensor.sensor.eCycle_250ms); + } + /*! + * @brief Set baseline + * @param get from getBaseline.ino + */ + // co2Sensor.sensor.writeBaseLine(0x847B); +} + +void updateAutoDrains(){ + if(abs(upperSensor.goalValTemp - upperSensor.currentAggTemp) > HEAT_DELTA){ + if(upperSensor.goalValTemp > upperSensor.currentAggTemp){ + turnOnHeat(); + }else if(upperSensor.goalValTemp < upperSensor.currentAggTemp){ + turnOffHeat(); + } + } + + if(abs(upperSensor.goalValHum - upperSensor.currentAggHum) > HUM_DELTA){ + if(upperSensor.goalValHum > upperSensor.currentAggHum){ + // if(!fogger.isActive){ + // Serial.println("*************FOGGER ON *************"); + // fogger.isActive = true; + // // analogWrite(fogger.pin, 255); + // delay(1000); + // } + turnOnAutoDrain(fogger); + }else if(upperSensor.goalValHum < upperSensor.currentAggHum){ + // if(fogger.isActive){ + // Serial.println("*************FOGGER off *************"); + // fogger.isActive = false; + // // analogWrite(fogger.pin, 0); + // delay(1000); + // } + turnOffAutoDrain(fogger); + } + } + if(loopIndex % 10 < UV_UP_TIME){ // + turnOnAutoDrain(UVLight); + }else{ + turnOffAutoDrain(UVLight); + } +} +void updateAutoSensors(){ + getHTData(upperSensor); +} + +float *getMarthaData(float (& upperArray)[14], float (& lowerArray)[14], float (& co2Array)[7], bool (& heatState), bool (& foggerState), bool (& UVState)){ + + heatState = heatLamp.isActive; + foggerState = fogger.isActive; + UVState = UVLight.isActive; + upperArray[0] = upperSensor.currentAggTemp; + upperArray[1] = upperSensor.tempHistory[0]; + upperArray[2] = upperSensor.tempHistory[1]; + upperArray[3] = upperSensor.tempHistory[2]; + upperArray[4] = upperSensor.tempHistory[3]; + upperArray[5] = upperSensor.tempHistory[4]; + upperArray[6] = upperSensor.goalValTemp; + upperArray[7] = upperSensor.currentAggHum; + upperArray[8] = upperSensor.humHistory[0]; + upperArray[9] = upperSensor.humHistory[1]; + upperArray[10] = upperSensor.humHistory[2]; + upperArray[11] = upperSensor.humHistory[3]; + upperArray[12] = upperSensor.humHistory[4]; + upperArray[13] = upperSensor.goalValHum; + + lowerArray[0] = waterSensor.currentAgg; + lowerArray[1] = waterSensor.history[0]; + lowerArray[2] = waterSensor.history[1]; + lowerArray[3] = waterSensor.history[2]; + lowerArray[4] = waterSensor.history[3]; + lowerArray[5] = waterSensor.history[4]; + lowerArray[6] = waterSensor.goalVal; + + co2Array[0] = co2Sensor.currentAgg; + co2Array[1] = co2Sensor.history[0]; + co2Array[2] = co2Sensor.history[1]; + co2Array[3] = co2Sensor.history[2]; + co2Array[4] = co2Sensor.history[3]; + co2Array[5] = co2Sensor.history[4]; + co2Array[6] = co2Sensor.goalVal; +} + + +void getHTData(HTSensor &device) +{ + sensors_event_t humidity, temp; + aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data + Serial.print("Temperature: "); Serial.print(temp.temperature); + Serial.println(" degrees C"); + Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); + Serial.println("% rH"); + float h = humidity.relative_humidity; + float c = temp.temperature; + + // Check if any reads failed + if (isnan(h) || isnan(c)) { + // Serial.println(F("Failed to read from DHT sensor!")); + }else { + if(abs(device.tempHistory[0] - c) > WARNING_LOG_DELTA_THRESHOLD){ + //SOME SORT OF LOGGING HERE + + } + for(byte i = (sizeof(device.tempHistory) / sizeof(device.tempHistory[0])) - 1; i >= 1; i--){ + device.tempHistory[i] = device.tempHistory[i - 1]; + device.humHistory[i] = device.humHistory[i - 1]; + } + device.tempHistory[0] = c; + device.humHistory[0] = h; + float tempSum = 0; + float humSum = 0; + for(byte i = 0; i < (sizeof(device.tempHistory) / sizeof(device.tempHistory[0])); i++){ + tempSum += device.tempHistory[i]; + humSum += device.humHistory[i]; + } + float newAggTemp = tempSum / (sizeof(device.tempHistory) / sizeof(device.tempHistory[0])); + float newAggHum = humSum / (sizeof(device.humHistory) / sizeof(device.humHistory[0])); + device.currentAggTemp = newAggTemp; + device.currentAggHum = newAggHum; + Serial.print("H "); + Serial.println(newAggHum); + } +} + +void turnOnAutoDrain(MarthaDrain &device) +{ + if(!device.isActive){ + if(device.type == 'W'){ //water + turnOnAutoDrain(AirFlowFan); + } + device.isActive = true; + digitalWrite(device.pin, LOW); + } +} + +void turnOffAutoDrain(MarthaDrain &device) +{ + if(device.isActive){ + if(device.type == 'W'){ //water + turnOffAutoDrain(AirFlowFan); + } + device.isActive = false; + digitalWrite(device.pin, HIGH); + } +} +void turnOnHeat() +{ + if(!heatLamp.isActive){ + heatLamp.isActive = true; + digitalWrite(heatLamp.pin, LOW); + } +} + +void turnOffHeat() +{ + if(heatLamp.isActive){ + heatLamp.isActive = false; + digitalWrite(heatLamp.pin, HIGH); + } +} + + + + +void setAllGoalHum(float g1){ + upperSensor.goalValHum = g1; + // lowerSensor.goalValHum = g1; +} + +void setAllGoalTemps(float g1){ + upperSensor.goalValTemp = g1; + // lowerSensor.goalValTemp = g1; +} + +void setGoalCO2(int g){ + co2Sensor.goalVal = g; +} + + + +void initHTSensor(HTSensor &device) +{ + sensors_event_t humidity, temp; + aht.getEvent(&humidity, &temp);// populate temp and humidity objects with fresh data + Serial.print("Temperature: "); Serial.print(temp.temperature); + Serial.println(" degrees C"); + Serial.print("Humidity: "); Serial.print(humidity.relative_humidity); + Serial.println("% rH"); + float h = humidity.relative_humidity; + float c = temp.temperature; + + // Check if any reads failed + if (isnan(h) || isnan(c)) { + Serial.println(F("Failed to read from DHT sensor!")); + device.currentAggTemp = 0; + device.tempHistory[0] = 0; + device.tempHistory[1] = 0; + device.tempHistory[2] = 0; + device.tempHistory[3] = 0; + device.tempHistory[4] = 0; + device.currentAggHum = 0; + device.humHistory[0] = 0; + device.humHistory[1] = 0; + device.humHistory[2] = 0; + device.humHistory[3] = 0; + device.humHistory[4] = 0; + }else { + device.currentAggTemp = c; + device.tempHistory[0] = c; + device.tempHistory[1] = c; + device.tempHistory[2] = c; + device.tempHistory[3] = c; + device.tempHistory[4] = c; + device.currentAggHum = h; + device.humHistory[0] = h; + device.humHistory[1] = h; + device.humHistory[2] = h; + device.humHistory[3] = h; + device.humHistory[4] = h; + } +} diff --git a/DataLogger.ino b/DataLogger.ino new file mode 100644 index 0000000..f84a792 --- /dev/null +++ b/DataLogger.ino @@ -0,0 +1,93 @@ + + + // ------- + // ads.setGain(GAIN_TWOTHIRDS); // 2/3x gain +/- 6.144V 1 bit = 0.1875mV (default) + // ads.setGain(GAIN_ONE); // 1x gain +/- 4.096V 1 bit = 0.125mV + // ads.setGain(GAIN_TWO); // 2x gain +/- 2.048V 1 bit = 0.0625mV + // ads.setGain(GAIN_FOUR); // 4x gain +/- 1.024V 1 bit = 0.03125mV + // ads.setGain(GAIN_EIGHT); // 8x gain +/- 0.512V 1 bit = 0.015625mV + + +const int size = 150; +const int voltNums = 4; +const int voltMeters[voltNums] = {0x48, 0x49, 0x4A, 0x4B}; // 0x48, 0x49, 0x4A, 0x4B +vReading adsArray[voltNums]; +float multiplier = 0.0078125F; // ADS1115 @ +/- 6.144V gain = 0.0078125mV/step +unsigned long lastDataLoggerReading; +int DataLoggerReadIntervalMs = 50; + +void setupDataLogger(){ + lastDataLoggerReading = millis(); + for(int x = 0; x < voltNums; x++){ + adsArray[x].sensor.setGain(GAIN_SIXTEEN); + adsArray[x].sensor.setDataRate(RATE_ADS1115_32SPS); + adsArray[x].sensor.begin(voltMeters[x]); + for (int y = 0; y < size - 1; y++){ + adsArray[x].readings1[y] = 0; + adsArray[x].readings2[y] = 0; + } + } + Serial.println("done DataLogger Init"); +} + + +void updateDataLogger(){ +// Serial.print(" ------------------------------- "); + for(int x = 0; x < (sizeof(voltMeters) / sizeof(voltMeters[0])); x++){ + adsArray[x].readings1[adsArray[x].readCnt] = adsArray[x].sensor.readADC_Differential_0_1() * multiplier; // read differential AIN0 - AIN1 + adsArray[x].readings2[adsArray[x].readCnt] = adsArray[x].sensor.readADC_Differential_2_3() * multiplier; // read differential AIN2 - AIN3 + + float diff1 = (adsArray[x].sensor.readADC_SingleEnded(1) * multiplier) - (adsArray[x].sensor.readADC_SingleEnded(0) * multiplier); + float diff2 = (adsArray[x].sensor.readADC_SingleEnded(3) * multiplier) - (adsArray[x].sensor.readADC_SingleEnded(2) * multiplier); + + // Serial.print(adsArray[x].readings1[adsArray[x].readCnt]); + // Serial.print("mV Prong 1 Avr: "); + // Serial.print(adsArray[x].avrProng1); + // Serial.print(" N "); Serial.print(adsArray[x].sensor.readADC_SingleEnded(0) * multiplier); + // Serial.print(" P "); Serial.println(adsArray[x].sensor.readADC_SingleEnded(1) * multiplier); + // Serial.print(" Diff1 "); Serial.println(diff1); + // Serial.print(adsArray[x].readings2[adsArray[x].readCnt]); Serial.print("mV Prong 2 Avr: "); Serial.print(adsArray[x].avrProng2); + // Serial.print(" N "); Serial.print(adsArray[x].sensor.readADC_SingleEnded(2) * multiplier); + // Serial.print(" P "); Serial.println(adsArray[x].sensor.readADC_SingleEnded(3) * multiplier); + // Serial.print(" Diff2 "); Serial.println(diff2); + + // Get the average + unsigned long totalreadings1 = 0; + unsigned long totalreadings2 = 0; + for (unsigned char cnt = 0; cnt < size; cnt++){ + totalreadings1 += adsArray[x].readings1[cnt]; + totalreadings2 += adsArray[x].readings2[cnt]; + } + adsArray[x].avrProng1 = totalreadings1 / size; + adsArray[x].avrProng2 = totalreadings2 / size; + adsArray[x].readCnt = adsArray[x].readCnt == size - 1 ? 0 : adsArray[x].readCnt + 1; + } + Serial.println(); + // Serial.print("----------------------------------------------------------------------");Serial.println((int16_t)0); +} + +void DataLoggerControlLoop() { + unsigned long currentTime = millis(); + if(currentTime - lastDataLoggerReading >= DataLoggerReadIntervalMs){ + updateDataLogger(); + + lastDataLoggerReading = currentTime; + } +} + + + + +float *getDataLoggerDump(float (& array)[8]){ + + array[0] = adsArray[0].avrProng1; + array[1] = adsArray[0].avrProng2; + array[2] = adsArray[1].avrProng1; + array[3] = adsArray[1].avrProng2; + array[4] = adsArray[2].avrProng1; + array[5] = adsArray[2].avrProng2; + array[6] = adsArray[3].avrProng1; + array[7] = adsArray[3].avrProng2; +} + + diff --git a/Incubator.ino b/Incubator.ino new file mode 100755 index 0000000..0f3bcf1 --- /dev/null +++ b/Incubator.ino @@ -0,0 +1,181 @@ + + +//Incubator +/* + Temp 1 + + Temp 2 + + HeatMat 1 + + HeatMat 2 + +*/ + + +OneWire t1(2); + +DallasTemperature incubatorSensors(&t1); +unsigned long lastIncubatorReading; +int incubatorReadIntervalMs = 500; + +// OneSensor tempPin1 = { {0, 0, 0, 0, 0}, 0, { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC }, 0}; +// OneSensor tempPin2 = { {0, 0, 0, 0, 0}, 0, { 0x28, 0xEE, 0xD5, 0x64, 0x1A, 0x16, 0x02, 0xEC }, 0}; +int mainSectionPin = 11; +int secondarySectionPin = 12; +IncubatorDrain heatSection1 = {mainSectionPin, false, 'H', { {0, 0, 0, 0, 0}, 0, { 0x28, 0xF6, 0xF2, 0x96, 0xF0, 0x01, 0x3C, 0x47 }, 0}}; +IncubatorDrain heatSection2 = {secondarySectionPin, false, 'H', { {0, 0, 0, 0, 0}, 0, { 0x28, 0xEB, 0x74, 0x96, 0xF0, 0x01, 0x3C, 0x01 }, 0}}; + + +void setupIncubator(int goal1, int goal2){ + pinMode( heatSection1.pin, OUTPUT); + pinMode( heatSection2.pin, OUTPUT); + digitalWrite(heatSection1.pin, HIGH); + digitalWrite(heatSection2.pin, HIGH); + incubatorSensors.begin(); + lastIncubatorReading = millis(); + initDrainSensor(heatSection1); + initDrainSensor(heatSection2); + setAllGoalTemps(goal1, goal2); + +} + +void IncubatorControlLoop () { + unsigned long currentTime = millis(); + if(currentTime - lastIncubatorReading >= incubatorReadIntervalMs){ + updateIncubatorSensors(); + updateIncubatorDrains(); + + lastIncubatorReading = currentTime; + } +} +void updateIncubatorDrains(){ + if(abs(heatSection1.sensor.goalVal - heatSection1.sensor.currentAgg) > HEAT_DELTA){ + if(heatSection1.sensor.goalVal > heatSection1.sensor.currentAgg){ + turnOnIncubatorDrain(heatSection1); + }else if(heatSection1.sensor.goalVal < heatSection1.sensor.currentAgg){ + turnOffIncubatorDrain(heatSection1); + } + } + + if(abs(heatSection2.sensor.goalVal - heatSection2.sensor.currentAgg) > HEAT_DELTA){ + if(heatSection2.sensor.goalVal > heatSection2.sensor.currentAgg){ + turnOnIncubatorDrain(heatSection2); + }else if(heatSection2.sensor.goalVal < heatSection2.sensor.currentAgg){ + turnOffIncubatorDrain(heatSection2); + } + } +} + +void turnOnIncubatorDrain(IncubatorDrain &device) +{ + if(!device.isActive){ + device.isActive = true; + digitalWrite(device.pin, LOW); + } +} + +void turnOffIncubatorDrain(IncubatorDrain &device) +{ + if(device.isActive){ + device.isActive = false; + digitalWrite(device.pin, HIGH); + } +} + +void initDrainSensor(IncubatorDrain &device) +{ + float tempC = incubatorSensors.getTempC(device.sensor.address); + device.sensor.currentAgg = tempC; + device.sensor.history[0] = tempC; + device.sensor.history[1] = tempC; + device.sensor.history[2] = tempC; + device.sensor.history[3] = tempC; + device.sensor.history[4] = tempC; +} + +void updateIncubatorSensors(){ + incubatorSensors.requestTemperatures(); + getTemperature(heatSection1); + getTemperature(heatSection2); +} + +void setMainChamber(float goal){ + heatSection1.sensor.goalVal = goal; +} + +void setSecondaryChamber(float goal){ + heatSection2.sensor.goalVal = goal; +} + +void setAllGoalTemps(float g1, float g2){ + heatSection1.sensor.goalVal = g1; + heatSection2.sensor.goalVal = g2; +} + + + +float *getSectionData(String isMain, float (& array)[8]){ + if(isMain.equals("Main")){ + array[0] = heatSection1.sensor.currentAgg; + array[1] = heatSection1.sensor.history[0]; + array[2] = heatSection1.sensor.history[1]; + array[3] = heatSection1.sensor.history[2]; + array[4] = heatSection1.sensor.history[3]; + array[5] = heatSection1.sensor.history[4]; + array[6] = heatSection1.sensor.goalVal; + array[7] = heatSection1.isActive ? 1 : 0; + }else { + array[0] = heatSection2.sensor.currentAgg; + array[1] = heatSection2.sensor.history[0]; + array[2] = heatSection2.sensor.history[1]; + array[3] = heatSection2.sensor.history[2]; + array[4] = heatSection2.sensor.history[3]; + array[5] = heatSection2.sensor.history[4]; + array[6] = heatSection2.sensor.goalVal; + array[7] = heatSection2.isActive ? 1 : 0; + } +} + +void setTemperature(String isMain, float goal){ + if(isMain.equals("Main")){ + setMainChamber(goal); + }else { + setSecondaryChamber(goal); + } +} + + +void getTemperature(IncubatorDrain &device) +{ + float tempC = incubatorSensors.getTempC(device.sensor.address); + if(abs(device.sensor.history[0] - tempC) > WARNING_LOG_DELTA_THRESHOLD){ + //SOME SORT OF LOGGING HERE + } + if(tempC > -120){ //-127c is disconnected + for(byte i = (sizeof(device.sensor.history) / sizeof(device.sensor.history[0])) - 1; i >= 1; i--){ + device.sensor.history[i] = device.sensor.history[i - 1]; + } + device.sensor.history[0] = tempC; + float sum = 0; + for(byte i = 0; i < (sizeof(device.sensor.history) / sizeof(device.sensor.history[0])); i++){ + sum += device.sensor.history[i]; + } + float newAgg = sum / (sizeof(device.sensor.history) / sizeof(device.sensor.history[0])); + device.sensor.currentAgg = newAgg; + printTemperature(device.sensor.address); + }else { + Serial.println("DHT Sensor disconnected"); + } +} + +void printTemperature(DeviceAddress deviceAddress) +{ + float tempC = incubatorSensors.getTempC(deviceAddress); + Serial.print(tempC); + Serial.print((char)176); + Serial.println("C "); +// Serial.print(DallasTemperature::toFahrenheit(tempC)); +// Serial.print((char)176); +// Serial.println("F"); +} diff --git a/MainController.ino b/MainController.ino new file mode 100755 index 0000000..a40f6fa --- /dev/null +++ b/MainController.ino @@ -0,0 +1,462 @@ +#include +#include +#include +#include "aWOT.h" +#include "StaticFiles.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; +EthernetServer server(80); +Application app; +unsigned long connectTime[MAX_SOCK_NUM]; +int WARNING_LOG_DELTA_THRESHOLD = 5; // > 5c temp diff in 0.500s is alarming and should be logged as anamolous +float HEAT_DELTA = 0.2; // Delta from goal Temp to neccessitate a change +float HUM_DELTA = 1; // Delta from goal Humidity to neccessitate a change +short UV_UP_TIME = 6; // 6/10 +// const String ip = "10.0.0.28"; +//272911496883 +IPAddress ip(10,0,0,28); +byte gateway[] = { 10, 0, 0, 1 }; +byte subnet[] = { 255, 255, 255, 0 }; + +struct OneSensor { + float history[5]; + float currentAgg; + uint8_t address[8]; + float goalVal; +}; +struct GasSensor { + int history[5]; + int currentAgg; + int goalVal; + Adafruit_SCD30 sensor; + float currentTemp; + float currentHum; +}; + +struct LvlSensor { + float history[5]; + float currentAgg; + int trig; + int echo; + float goalVal; +}; + +struct IncubatorDrain { + int pin; + bool isActive; + char type; // "L" light, "P" pump, "H" heat + OneSensor sensor; +}; +struct HTSensor { + float tempHistory[5]; + float currentAggTemp; + float goalValTemp; + float humHistory[5]; + float currentAggHum; + float goalValHum; + Adafruit_AHTX0 aht; +}; + +struct MarthaDrain { + int pin; + bool isActive; + char type; // "L" light, "P" pump, "H" heat +}; +struct vReading { + int16_t readings1[size] = {0}; + int16_t readings2[size] = {0}; + Adafruit_ADS1115 sensor; + // ADC object at I2C address 0x48 for addr pin = GND + float avrProng1 = 0; + float avrProng2 = 0; + unsigned char readCnt = 0; +}; + +void setup() { + Serial.begin(115200); + setupIncubator(22, 22); + setupAutoHumidity(18, 80); + setupDataLogger(); + // pinMode(46,OUTPUT) ; + // pinMode(44,OUTPUT) ; + // digitalWrite(44, LOW); + // digitalWrite(44, HIGH); + Ethernet.begin(mac, ip, gateway, subnet); + // while (!Ethernet.begin(mac, ip)) { + // delay(500); + // Serial.print("."); + // } + // Serial.println(Ethernet.localIP()); + app.get("/Incubator/Section", &getIncubatorSectionData); + app.put("/Incubator/Section", &setIncubatorSectionData); + app.put("/Martha/Temp", &setMarthGoalTemp); + app.put("/Martha/Hum", &setMarthGoalHum); + app.get("/Martha", &getMarthaData); + app.get("/Connection", &getMarthaData); + app.get("/Logger", &getDataLogger); + // app.get("/", &index); + app.use(staticFiles()); + Serial.println((int16_t)0); + server.begin(); + unsigned long thisTime = millis(); + + for(int i=0;i 750) + // { + // // then close the connection from this end. + // Serial.println(); + // Serial.println(F("Timeout")); + // client.flush(); + // client.stop(); + // } + // delay(1); + // } + // client.flush(); + // client.stop(); + + checkSockStatus(); + delay(1); + // Ethernet.begin(mac, ip, gateway, subnet); + // server.begin(); + // delay(100); +} + + +byte socketStat[MAX_SOCK_NUM]; +void checkSockStatus() +{ + unsigned long thisTime = millis(); + + for (int i = 0; i < MAX_SOCK_NUM; i++) { + uint8_t s = W5100.readSnSR(i); + // Serial.println(s); + + if((s == 0x17) || (s == 0x1C)) { + if(thisTime - connectTime[i] > 5000UL) { + Serial.print(F("\r\nSocket frozen: ")); + Serial.println(i); + W5100.execCmdSn(i, Sock_CLOSE); + } + } + else connectTime[i] = thisTime; + + socketStat[i] = W5100.readSnSR(i); + } +} + +void getSystemConnection(Request &req, Response &res){ + Serial.println("**********************************"); + char goalVal[64]; + req.query("goalVal", goalVal, 64); + Serial.println(atof(goalVal)); + setAllGoalTemps(atof(goalVal)); + aJsonObject *root; + root=aJson.createObject(); + aJson.addStringToObject(root, "status", "success"); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + +void setMarthGoalHum(Request &req, Response &res){ + Serial.println("**********************************"); + char goalVal[64]; + req.query("goalVal", goalVal, 64); + Serial.println(atof(goalVal)); + setAllGoalHum(atof(goalVal)); + aJsonObject *root; + root=aJson.createObject(); + aJson.addStringToObject(root, "status", "success"); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + +void setMarthGoalTemp(Request &req, Response &res){ + Serial.println("**********************************"); + char goalVal[64]; + req.query("goalVal", goalVal, 64); + Serial.println(atof(goalVal)); + setAllGoalTemps(atof(goalVal)); + aJsonObject *root; + root=aJson.createObject(); + aJson.addStringToObject(root, "status", "success"); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + + +void setIncubatorSectionData(Request &req, Response &res){ + Serial.println("**********************************"); + char sectionName[64]; + char goalVal[64]; + req.query("sectionName", sectionName, 64); + req.query("goalVal", goalVal, 64); + Serial.println(sectionName); + Serial.println(atof(goalVal)); + if(strcmp(sectionName, "Main") == 0){ + setTemperature("Main", atof(goalVal)); + }else if(strcmp(sectionName, "Secondary") == 0){ + setTemperature("Secondary", atof(goalVal)); + } + aJsonObject *root; + root=aJson.createObject(); + aJson.addStringToObject(root, "status", "success"); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + + + + +void getDataLogger(Request &req, Response &res){ + + float mainArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + Serial.println("**********************************"); + + getDataLoggerDump(mainArray); + aJsonObject *root; + root=aJson.createObject(); + + aJson.addNumberToObject(root, "LoggerA", mainArray[0]); + aJson.addNumberToObject(root, "LoggerB", mainArray[1]); + aJson.addNumberToObject(root, "LoggerC", mainArray[2]); + aJson.addNumberToObject(root, "LoggerD", mainArray[3]); + aJson.addNumberToObject(root, "LoggerE", mainArray[4]); + aJson.addNumberToObject(root, "LoggerF", mainArray[5]); + aJson.addNumberToObject(root, "LoggerG", mainArray[6]); + aJson.addNumberToObject(root, "LoggerH", mainArray[7]); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + + +void getIncubatorSectionData(Request &req, Response &res){ + + float mainArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + float secondArray[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + Serial.println("**********************************"); + char sectionName[64]; + req.query("sectionName", sectionName, 64); + Serial.println(sectionName); + // if(strcmp(sectionName, "Main") == 0){ + // getSectionData("Main", mainArray); + // }else if(strcmp(sectionName, "Secondary") == 0){ + // getSectionData("Secondary", mainArray); + // } + getSectionData("Main", mainArray); + getSectionData("Secondary", secondArray); + aJsonObject *root,*tmps, *firstRt, *secondRt, *secondTmps, *drainStates; + root=aJson.createObject(); + + aJson.addItemToObject(root, "mainSection", firstRt = aJson.createObject()); + aJson.addNumberToObject(firstRt, "avrTemp", mainArray[0]); + aJson.addNumberToObject(firstRt, "goalTemp", mainArray[6]); + // aJson.addBooleanToObject(firstRt, "isActive", mainArray[7] == 0.0? false : true); + aJson.addItemToObject(root, "DrainStates", drainStates = aJson.createObject()); + aJson.addBooleanToObject(drainStates, "mainSection", mainArray[7] == 0.0? false : true); + aJson.addBooleanToObject(drainStates, "secondSection", secondArray[7] == 0.0? false : true); + // aJson.addItemToObject(firstRt, "temps", tmps = aJson.createArray()); + // aJson.addItemToArray(tmps, aJson.createItem(mainArray[1])); + // aJson.addItemToArray(tmps, aJson.createItem(mainArray[2])); + // aJson.addItemToArray(tmps, aJson.createItem(mainArray[3])); + // aJson.addItemToArray(tmps, aJson.createItem(mainArray[4])); + // aJson.addItemToArray(tmps, aJson.createItem(mainArray[5])); + + aJson.addItemToObject(root, "secondSection", secondRt = aJson.createObject()); + aJson.addNumberToObject(secondRt, "avrTemp", secondArray[0]); + aJson.addNumberToObject(secondRt, "goalTemp", secondArray[6]); + // aJson.addBooleanToObject(secondRt, "isActive", secondArray[7] == 0.0? false : true); + // aJson.addItemToObject(secondRt, "temps", secondTmps = aJson.createArray()); + // aJson.addItemToArray(secondTmps, aJson.createItem(secondArray[1])); + // aJson.addItemToArray(secondTmps, aJson.createItem(secondArray[2])); + // aJson.addItemToArray(secondTmps, aJson.createItem(secondArray[3])); + // aJson.addItemToArray(secondTmps, aJson.createItem(secondArray[4])); + // aJson.addItemToArray(secondTmps, aJson.createItem(secondArray[5])); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + +void getMarthaData(Request &req, Response &res){ + + float upperSensor[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + float lowerSensor[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + float co2Sensor[7] = {0, 0, 0, 0, 0, 0, 0}; + bool heatState = false; + bool UVState = false; + bool foggerState = false; + Serial.println("**********************************"); + getMarthaData(upperSensor, lowerSensor, co2Sensor, heatState, foggerState, UVState); + + res.set("Content-Type", "application/json"); + aJsonStream stream(&req); + + aJsonObject *root, *upper, *upperTmps, *upperHum, *lower, *lowerTmps, *lowerHum, *co2, *co2Hist, *drainStates; + root=aJson.createObject(); + aJson.addBooleanToObject(root, "fogger", foggerState); + aJson.addBooleanToObject(root, "heat", heatState); + aJson.addBooleanToObject(root, "uv", UVState); + aJson.addItemToObject(root, "DrainStates", drainStates = aJson.createObject()); + aJson.addBooleanToObject(drainStates, "fogger", foggerState); + aJson.addBooleanToObject(drainStates, "heat", heatState); + aJson.addBooleanToObject(drainStates, "uv", UVState); + aJson.addItemToObject(root, "CO2", co2 = aJson.createObject()); + + aJson.addNumberToObject(co2, "avrCO2", co2Sensor[0]); + aJson.addNumberToObject(co2, "goalCO2", co2Sensor[6]); + // aJson.addItemToObject(co2, "history", co2Hist = aJson.createArray()); + // aJson.addItemToArray(co2Hist, aJson.createItem(co2Sensor[1])); + // aJson.addItemToArray(co2Hist, aJson.createItem(co2Sensor[2])); + // aJson.addItemToArray(co2Hist, aJson.createItem(co2Sensor[3])); + // aJson.addItemToArray(co2Hist, aJson.createItem(co2Sensor[4])); + // aJson.addItemToArray(co2Hist, aJson.createItem(co2Sensor[5])); + + + aJson.addItemToObject(root, "UpperSensor", upper = aJson.createObject()); + + aJson.addNumberToObject(upper, "avrTemp", upperSensor[0]); + aJson.addNumberToObject(upper, "goalTemp", upperSensor[6]); + // aJson.addItemToObject(upper, "temps", upperTmps = aJson.createArray()); + // aJson.addItemToArray(upperTmps, aJson.createItem(upperSensor[1])); + // aJson.addItemToArray(upperTmps, aJson.createItem(upperSensor[2])); + // aJson.addItemToArray(upperTmps, aJson.createItem(upperSensor[3])); + // aJson.addItemToArray(upperTmps, aJson.createItem(upperSensor[4])); + // aJson.addItemToArray(upperTmps, aJson.createItem(upperSensor[5])); + + aJson.addNumberToObject(upper, "avrHum", upperSensor[7]); + aJson.addNumberToObject(upper, "goalHum", upperSensor[13]); + // aJson.addItemToObject(upper, "hum", upperHum = aJson.createArray()); + // aJson.addItemToArray(upperHum, aJson.createItem(upperSensor[8])); + // aJson.addItemToArray(upperHum, aJson.createItem(upperSensor[9])); + // aJson.addItemToArray(upperHum, aJson.createItem(upperSensor[10])); + // aJson.addItemToArray(upperHum, aJson.createItem(upperSensor[11])); + // aJson.addItemToArray(upperHum, aJson.createItem(upperSensor[12])); + + aJson.addItemToObject(root, "WaterSensor", lower = aJson.createObject()); + + aJson.addNumberToObject(lower, "avrLevel", lowerSensor[0]); + aJson.addNumberToObject(lower, "goalLevel", lowerSensor[6]); + // aJson.addItemToObject(lower, "temps", lowerTmps = aJson.createArray()); + // aJson.addItemToArray(lowerTmps, aJson.createItem(lowerSensor[1])); + // aJson.addItemToArray(lowerTmps, aJson.createItem(lowerSensor[2])); + // aJson.addItemToArray(lowerTmps, aJson.createItem(lowerSensor[3])); + // aJson.addItemToArray(lowerTmps, aJson.createItem(lowerSensor[4])); + // aJson.addItemToArray(lowerTmps, aJson.createItem(lowerSensor[5])); + + // aJson.addNumberToObject(lower, "avrHum", lowerSensor[7]); + // aJson.addNumberToObject(lower, "goalHum", lowerSensor[13]); + // aJson.addItemToObject(lower, "hum", lowerHum = aJson.createArray()); + // aJson.addItemToArray(lowerHum, aJson.createItem(lowerSensor[8])); + // aJson.addItemToArray(lowerHum, aJson.createItem(lowerSensor[9])); + // aJson.addItemToArray(lowerHum, aJson.createItem(lowerSensor[10])); + // aJson.addItemToArray(lowerHum, aJson.createItem(lowerSensor[11])); + // aJson.addItemToArray(lowerHum, aJson.createItem(lowerSensor[12])); + aJson.print(root, &stream); + res.end(); + aJson.deleteItem(root); + Serial.println("**********************************"); +} + + + +//Controls Mushroom Grow tent +/* + Fogger + (main Fan) (controlled) + UV light (controlled) + + Humidity / Temp 1 + + Humidity / Temp 2 + + HeatMat (controlled) + + Co2 sensor + Air supply Fan (constantly on) + + General Circulation Fan (controlled) + + +*/ +//Incubator +/* + Temp 1 + + Temp 2 + + HeatMat 1 (controlled) + + HeatMat 2 (controlled) + +*/ +//Shrimp Aquaponica diff --git a/StaticFiles.h b/StaticFiles.h new file mode 100755 index 0000000..d93adf8 --- /dev/null +++ b/StaticFiles.h @@ -0,0 +1,21 @@ +void static_index(Request &req, Response &res) { + P(index) = + "\n" + "\n" + "Some thing isnt supposed to be here\n" + "\n" + "\n" + "

Wuzzup?

\n" + "\n" + ""; + + res.set("Content-Type", "text/html"); + res.printP(index); +} + +Router staticFileRouter; + +Router * staticFiles() { + staticFileRouter.get("/", &static_index); + return &staticFileRouter; +} diff --git a/aWOT.cpp b/aWOT.cpp new file mode 100755 index 0000000..610fdfd --- /dev/null +++ b/aWOT.cpp @@ -0,0 +1,1736 @@ +/* + aWOT, Express.js inspired microcontreller web framework for the Web of Things + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "aWOT.h" + +Response::Response(Client* client, uint8_t * writeBuffer, int writeBufferLength) + : m_stream(client), + m_headers(), + m_contentLenghtSet(false), + m_contentTypeSet(false), + m_keepAlive(false), + m_statusSent(0), + m_headersSent(false), + m_sendingStatus(false), + m_sendingHeaders(false), + m_headersCount(0), + m_mime(NULL), + m_bytesSent(0), + m_ended(false), + m_buffer(writeBuffer), + m_bufferLength(writeBufferLength), + m_bufFill(0) {} + +int Response::availableForWrite() { + return SERVER_OUTPUT_BUFFER_SIZE - m_bufFill - 1; +} + +void Response::beginHeaders() { + if (!m_statusSent) { + status(200); + } + + m_sendingHeaders = true; + + P(headerSeprator) = ": "; + for (int i = 0; i < m_headersCount; i++) { + print(m_headers[i].name); + printP(headerSeprator); + print(m_headers[i].value); + m_printCRLF(); + } +} + +int Response::bytesSent() { return m_bytesSent; } + +void Response::end() { + m_ended = true; +} + +void Response::endHeaders() { + m_printCRLF(); + m_flushBuf(); + m_sendingHeaders = false; + m_headersSent = true; +} + +bool Response::ended() { return m_ended; } + +void Response::flush() { + m_flushBuf(); + + m_stream->flush(); +} + +const char *Response::get(const char *name) { + for (int i = 0; i < m_headersCount; i++) { + if (Application::strcmpi(name, m_headers[i].name) == 0) { + return m_headers[m_headersCount].value; + } + } + + return NULL; +} + +bool Response::headersSent() { return m_headersSent; } + +void Response::printP(const unsigned char *string) { + if (m_shouldPrintHeaders()) { + m_printHeaders(); + } + + while (uint8_t value = pgm_read_byte(string++)) { + write(value); + } +} + +void Response::printP(const char *string) { printP((unsigned char *)string); } + +void Response::sendStatus(int code) { + status(code); + + m_printHeaders(); + + if (code != 204 && code != 304) { + m_printStatus(code); + } +} + +void Response::set(const char *name, const char *value) { + if (m_headersCount >= SERVER_MAX_HEADERS) { + return; + } + + m_headers[m_headersCount].name = name; + m_headers[m_headersCount].value = value; + m_headersCount++; + + P(contentType) = "Content-Type"; + if (Application::strcmpiP(name, contentType) == 0) { + m_contentTypeSet = true; + } + + P(contentLength) = "Content-Length"; + if (Application::strcmpiP(name, contentLength) == 0) { + m_contentLenghtSet = true; + } + + P(connection) = "Connection"; + if (Application::strcmpiP(name, connection) == 0) { + P(keepAlive) = "keep-alive"; + m_keepAlive = Application::strcmpiP(value, keepAlive) == 0; + } +} + +void Response::setDefaults() { + if (!m_contentTypeSet) { + set("Content-Type", "text/plain"); + } + + if (m_keepAlive && !m_contentLenghtSet) { + set("Transfer-Encoding", "chunked"); + } + + if (!m_keepAlive) { + m_contentLenghtSet = true; + set("Connection", "close"); + } +} + +void Response::status(int code) { + if (m_statusSent) { + return; + } + + m_statusSent = code; + + m_sendingStatus = true; + P(httpVersion) = "HTTP/1.1 "; + printP(httpVersion); + print(code); + P(space) = " "; + printP(space); + m_printStatus(code); + + m_printCRLF(); + + if (code < 200) { + beginHeaders(); + endHeaders(); + m_statusSent = 0; + } else if (code == 204 || code == 304) { + m_contentLenghtSet = true; + m_contentTypeSet = true; + } + + m_sendingStatus = false; +} + +int Response::statusSent() { return m_statusSent; } + +size_t Response::write(uint8_t data) { + if (m_shouldPrintHeaders()) { + m_printHeaders(); + } + + m_buffer[m_bufFill++] = data; + + if (m_bufFill == SERVER_OUTPUT_BUFFER_SIZE) { + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(m_bufFill, HEX); + m_stream->print(CRLF); + } + + m_stream->write(m_buffer, SERVER_OUTPUT_BUFFER_SIZE); + + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(CRLF); + } + + m_bufFill = 0; + } + + size_t bytesSent = sizeof(data); + m_bytesSent += bytesSent; + return bytesSent; +} + +size_t Response::write(uint8_t *buffer, size_t bufferLength) { + if (m_shouldPrintHeaders()) { + m_printHeaders(); + } + + m_flushBuf(); + + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(bufferLength, HEX); + m_stream->print(CRLF); + } + + m_stream->write(buffer, bufferLength); + + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(CRLF); + } + + m_bytesSent += bufferLength; + return bufferLength; +} + +void Response::writeP(const unsigned char *data, size_t length) { + if (m_shouldPrintHeaders()) { + m_printHeaders(); + } + + while (length--) { + write(pgm_read_byte(data++)); + } +} + +void Response::m_printStatus(int code) { + switch (code) { +#ifndef LOW_MEMORY_MCU + case 100: { + P(Continue) = "Continue"; + printP(Continue); + break; + } + case 101: { + P(SwitchingProtocols) = "Switching Protocols"; + printP(SwitchingProtocols); + break; + } + case 102: { + P(Processing) = "Processing"; + printP(Processing); + break; + } + case 103: { + P(EarlyHints) = "Early Hints"; + printP(EarlyHints); + break; + } + case 200: { + P(OK) = "OK"; + printP(OK); + break; + } + case 201: { + P(Created) = "Created"; + printP(Created); + break; + } + case 202: { + P(Accepted) = "Accepted"; + printP(Accepted); + break; + } + case 203: { + P(NonAuthoritativeInformation) = "Non-Authoritative Information"; + printP(NonAuthoritativeInformation); + break; + } + case 204: { + P(NoContent) = "No Content"; + printP(NoContent); + break; + } + case 205: { + P(ResetContent) = "Reset Content"; + printP(ResetContent); + break; + } + case 206: { + P(PartialContent) = "Partial Content"; + printP(PartialContent); + break; + } + case 207: { + P(MultiStatus) = "Multi-Status"; + printP(MultiStatus); + break; + } + case 208: { + P(AlreadyReported) = "Already Reported"; + printP(AlreadyReported); + break; + } + case 226: { + P(IMUsed) = "IM Used"; + printP(IMUsed); + break; + } + case 300: { + P(MultipleChoices) = "Multiple Choices"; + printP(MultipleChoices); + break; + } + case 301: { + P(MovedPermanently) = "Moved Permanently"; + printP(MovedPermanently); + break; + } + case 302: { + P(Found) = "Found"; + printP(Found); + break; + } + case 303: { + P(SeeOther) = "See Other"; + printP(SeeOther); + break; + } + case 304: { + P(NotModified) = "Not Modified"; + printP(NotModified); + break; + } + case 305: { + P(UseProxy) = "Use Proxy"; + printP(UseProxy); + break; + } + case 306: { + P(Unused) = "(Unused)"; + printP(Unused); + break; + } + case 307: { + P(TemporaryRedirect) = "Temporary Redirect"; + printP(TemporaryRedirect); + break; + } + case 308: { + P(PermanentRedirect) = "Permanent Redirect"; + printP(PermanentRedirect); + break; + } + case 400: { + P(BadRequest) = "Bad Request"; + printP(BadRequest); + break; + } + case 401: { + P(Unauthorized) = "Unauthorized"; + printP(Unauthorized); + break; + } + case 402: { + P(PaymentRequired) = "Payment Required"; + printP(PaymentRequired); + break; + } + case 403: { + P(Forbidden) = "Forbidden"; + printP(Forbidden); + break; + } + case 404: { + P(NotFound) = "Not Found"; + printP(NotFound); + break; + } + case 405: { + P(MethodNotAllowed) = "Method Not Allowed"; + printP(MethodNotAllowed); + break; + } + case 406: { + P(NotAcceptable) = "Not Acceptable"; + printP(NotAcceptable); + break; + } + case 407: { + P(ProxyAuthenticationRequired) = "Proxy Authentication Required"; + printP(ProxyAuthenticationRequired); + break; + } + case 408: { + P(RequestTimeout) = "Request Timeout"; + printP(RequestTimeout); + break; + } + case 409: { + P(Conflict) = "Conflict"; + printP(Conflict); + break; + } + case 410: { + P(Gone) = "Gone"; + printP(Gone); + break; + } + case 411: { + P(LengthRequired) = "Length Required"; + printP(LengthRequired); + break; + } + case 412: { + P(PreconditionFailed) = "Precondition Failed"; + printP(PreconditionFailed); + break; + } + case 413: { + P(PayloadTooLarge) = "Payload Too Large"; + printP(PayloadTooLarge); + break; + } + case 414: { + P(URITooLong) = "URI Too Long"; + printP(URITooLong); + break; + } + case 415: { + P(UnsupportedMediaType) = "Unsupported Media Type"; + printP(UnsupportedMediaType); + break; + } + case 416: { + P(RangeNotSatisfiable) = "Range Not Satisfiable"; + printP(RangeNotSatisfiable); + break; + } + case 417: { + P(ExpectationFailed) = "Expectation Failed"; + printP(ExpectationFailed); + break; + } + case 421: { + P(MisdirectedRequest) = "Misdirected Request"; + printP(MisdirectedRequest); + break; + } + case 422: { + P(UnprocessableEntity) = "Unprocessable Entity"; + printP(UnprocessableEntity); + break; + } + case 423: { + P(Locked) = "Locked"; + printP(Locked); + break; + } + case 424: { + P(FailedDependency) = "Failed Dependency"; + printP(FailedDependency); + break; + } + case 425: { + P(TooEarly) = "Too Early"; + printP(TooEarly); + break; + } + case 426: { + P(UpgradeRequired) = "Upgrade Required"; + printP(UpgradeRequired); + break; + } + case 428: { + P(PreconditionRequired) = "Precondition Required"; + printP(PreconditionRequired); + break; + } + case 429: { + P(TooManyRequests) = "Too Many Requests"; + printP(TooManyRequests); + break; + } + case 431: { + P(RequestHeaderFieldsTooLarge) = "Request Header Fields Too Large"; + printP(RequestHeaderFieldsTooLarge); + break; + } + case 451: { + P(UnavailableForLegalReasons) = "Unavailable For Legal Reasons"; + printP(UnavailableForLegalReasons); + break; + } + case 500: { + P(InternalServerError) = "Internal Server Error"; + printP(InternalServerError); + break; + } + case 501: { + P(NotImplemented) = "Not Implemented"; + printP(NotImplemented); + break; + } + case 502: { + P(BadGateway) = "Bad Gateway"; + printP(BadGateway); + break; + } + case 503: { + P(ServiceUnavailable) = "Service Unavailable"; + printP(ServiceUnavailable); + break; + } + case 504: { + P(GatewayTimeout) = "Gateway Timeout"; + printP(GatewayTimeout); + break; + } + case 505: { + P(HTTPVersionNotSupported) = "HTTP Version Not Supported"; + printP(HTTPVersionNotSupported); + break; + } + case 506: { + P(VariantAlsoNegotiates) = "Variant Also Negotiates"; + printP(VariantAlsoNegotiates); + break; + } + case 507: { + P(InsufficientStorage) = "Insufficient Storage"; + printP(InsufficientStorage); + break; + } + case 508: { + P(LoopDetected) = "Loop Detected"; + printP(LoopDetected); + break; + } + case 510: { + P(NotExtended) = "Not Extended"; + printP(NotExtended); + break; + } + case 511: { + P(NetworkAuthenticationRequired) = "Network Authentication Required"; + printP(NetworkAuthenticationRequired); + break; + } +#else + case 200: { + P(OK) = "OK"; + printP(OK); + break; + } + case 201: { + P(Created) = "Created"; + printP(Created); + break; + } + case 202: { + P(Accepted) = "Accepted"; + printP(Accepted); + break; + } + case 204: { + P(NoContent) = "No Content"; + printP(NoContent); + break; + } + case 303: { + P(SeeOther) = "See Other"; + printP(SeeOther); + break; + } + case 304: { + P(NotModified) = "Not Modified"; + printP(NotModified); + break; + } + case 400: { + P(BadRequest) = "Bad Request"; + printP(BadRequest); + break; + } + case 401: { + P(Unauthorized) = "Unauthorized"; + printP(Unauthorized); + break; + } + case 402: { + P(PaymentRequired) = "Payment Required"; + printP(PaymentRequired); + break; + } + case 403: { + P(Forbidden) = "Forbidden"; + printP(Forbidden); + break; + } + case 404: { + P(NotFound) = "Not Found"; + printP(NotFound); + break; + } + case 405: { + P(MethodNotAllowed) = "Method Not Allowed"; + printP(MethodNotAllowed); + break; + } + case 406: { + P(NotAcceptable) = "Not Acceptable"; + printP(NotAcceptable); + break; + } + case 407: { + P(ProxyAuthenticationRequired) = "Proxy Authentication Required"; + printP(ProxyAuthenticationRequired); + break; + } + case 408: { + P(RequestTimeout) = "Request Timeout"; + printP(RequestTimeout); + break; + } + + case 431: { + P(RequestHeaderFieldsTooLarge) = "Request Header Fields Too Large"; + printP(RequestHeaderFieldsTooLarge); + break; + } + case 500: { + P(InternalServerError) = "Internal Server Error"; + printP(InternalServerError); + break; + } + case 505: { + P(HTTPVersionNotSupported) = "HTTP Version Not Supported"; + printP(HTTPVersionNotSupported); + break; + } +#endif + default: { + print(code); + break; + } + } +} + +bool Response::m_shouldPrintHeaders() { + return (!m_headersSent && !m_sendingHeaders && !m_sendingStatus); +} + +void Response::m_printHeaders() { + setDefaults(); + beginHeaders(); + endHeaders(); +} + +void Response::m_printCRLF() { print(CRLF); } + +void Response::m_flushBuf() { + if (m_bufFill > 0) { + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(m_bufFill, HEX); + m_stream->print(CRLF); + } + + m_stream->write(m_buffer, m_bufFill); + + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(CRLF); + } + + m_bufFill = 0; + }; +} + +void Response::m_finalize() { + m_flushBuf(); + + if (m_headersSent && !m_contentLenghtSet) { + m_stream->print(0, HEX); + m_stream->print(CRLF); + m_stream->print(CRLF); + } +} + +Request::Request(Client* client, Response* m_response, HeaderNode* headerTail, + char* urlBuffer, int urlBufferLength, unsigned long timeout, + void* context) + : context(context), + m_stream(client), + m_response(m_response), + m_method(UNKNOWN), + m_minorVersion(-1), + m_pushback(), + m_pushbackDepth(0), + m_readingContent(false), + m_left(0), + m_bytesRead(0), + m_headerTail(headerTail), + m_query(NULL), + m_queryLength(0), + m_readTimedout(false), + m_path(urlBuffer), + m_pathLength(urlBufferLength - 1), + m_pattern(NULL), + m_route(NULL){ + _timeout = timeout; + } + +int Request::availableForWrite() { + return m_response->availableForWrite(); +} + +int Request::available() { + return min(m_stream->available(), m_left + m_pushbackDepth); +} + +int Request::bytesRead() { return m_bytesRead; } + +Stream *Request::stream() { return m_stream; } + +char *Request::get(const char *name) { + HeaderNode *headerNode = m_headerTail; + + while (headerNode != NULL) { + if (Application::strcmpi(headerNode->name, name) == 0) { + return headerNode->buffer; + } + + headerNode = headerNode->next; + } + + return NULL; +} + +void Request::flush() { + return m_response->flush(); +} + +bool Request::form(char *name, int nameLength, char *value, int valueLength) { + int ch; + bool foundSomething = false; + bool readingName = true; + + memset(name, 0, nameLength); + memset(value, 0, valueLength); + + while ((ch = m_timedRead()) != -1) { + foundSomething = true; + if (ch == '+') { + ch = ' '; + } else if (ch == '=') { + readingName = false; + continue; + } else if (ch == '&') { + return nameLength > 0 && valueLength > 0; + } else if (ch == '%') { + int high = m_timedRead(); + if (high == -1) { + return false; + } + + int low = m_timedRead(); + if (low == -1) { + return false; + } + + if (high > 0x39) { + high -= 7; + } + + high &= 0x0f; + + if (low > 0x39) { + low -= 7; + } + + low &= 0x0f; + + ch = (high << 4) | low; + } + + if (readingName && --nameLength) { + *name++ = ch; + } else if (!readingName && --valueLength) { + *value++ = ch; + } + } + + return foundSomething && nameLength > 0 && valueLength > 0; +} + +int Request::left() { return m_left + m_pushbackDepth; } + +Request::MethodType Request::method() { return m_method; } + +char *Request::path() { return m_path; } + +int Request::peek() { + int ch = read(); + + if (ch != -1) { + push(ch); + } + + return ch; +} + +void Request::push(uint8_t ch) { + m_pushback[m_pushbackDepth++] = ch; + + // can't raise error here, so just replace last char over and over + if (m_pushbackDepth == SERVER_PUSHBACK_BUFFER_SIZE) { + m_pushbackDepth = SERVER_PUSHBACK_BUFFER_SIZE - 1; + } +} + +char *Request::query() { return m_query; } + +bool Request::query(const char *name, char *buffer, int bufferLength) { + memset(buffer, 0, bufferLength); + + char *position = m_query; + int nameLength = strlen(name); + + while ((position = strstr(position, name))) { + char previous = *(position - 1); + + if ((previous == '\0' || previous == '&') && + *(position + nameLength) == '=') { + position = position + nameLength + 1; + while (*position && *position != '&' && --bufferLength) { + *buffer++ = *position++; + } + + return bufferLength > 0; + } + + position++; + } + + return false; +} + +int Request::read() { + if (m_pushbackDepth > 0) { + return m_pushback[--m_pushbackDepth]; + } + + if (m_readingContent && !m_left) { + _timeout = 0; + return -1; + } + + int ch = m_stream->read(); + if (ch == -1) { + return -1; + } + + if (m_readingContent) { + m_left--; + } + + m_bytesRead++; + return ch; +} + +int Request::read(uint8_t* buf, size_t size) { + int ret = 0; + + while (m_pushbackDepth > 0) { + *buf++ = m_pushback[--m_pushbackDepth]; + size--; + ret++; + } + + int read = m_stream->read(buf, (size < (unsigned)m_left ? size : m_left)); + if (read == -1) { + if (ret > 0) { + return ret; + } + + return -1; + } + + ret += read; + m_bytesRead += read; + m_left -= read; + + return ret; +} + +bool Request::route(const char *name, char *buffer, int bufferLength) { + int part = 0; + int i = 1; + + while (m_pattern[i]) { + if (m_pattern[i] == '/') { + part++; + } + + if (m_pattern[i++] == ':') { + int j = 0; + + while ((m_pattern[i] && name[j]) && m_pattern[i] == name[j]) { + i++; + j++; + } + + if (!name[j] && (m_pattern[i] == '/' || !m_pattern[i])) { + return route(part, buffer, bufferLength); + } + } + } + + return false; +} + +bool Request::route(int number, char *buffer, int bufferLength) { + memset(buffer, 0, bufferLength); + int part = -1; + const char *routeStart = m_route; + + while (*routeStart) { + if (*routeStart++ == '/') { + part++; + + if (part == number) { + while (*routeStart && *routeStart != '/' && --bufferLength) { + *buffer++ = *routeStart++; + } + + return bufferLength > 0; + } + } + } + + return false; +} + +int Request::minorVersion() { return m_minorVersion; } + +size_t Request::write(uint8_t data) { + return m_response->write(data); +} + +size_t Request::write(uint8_t* buffer, size_t bufferLength) { + return m_response->write(buffer, bufferLength); +} + +bool Request::m_processMethod() { + P(GET_VERB) = "GET "; + P(HEAD_VERB) = "HEAD "; + P(POST_VERB) = "POST "; + P(PUT_VERB) = "PUT "; + P(DELETE_VERB) = "DELETE "; + P(PATCH_VERB) = "PATCH "; + P(OPTIONS_VERB) = "OPTIONS "; + + if (m_expectP(GET_VERB)) { + m_method = GET; + } else if (m_expectP(HEAD_VERB)) { + m_method = HEAD; + } else if (m_expectP(POST_VERB)) { + m_method = POST; + } else if (m_expectP(PUT_VERB)) { + m_method = PUT; + } else if (m_expectP(DELETE_VERB)) { + m_method = DELETE; + } else if (m_expectP(PATCH_VERB)) { + m_method = PATCH; + } else if (m_expectP(OPTIONS_VERB)) { + m_method = OPTIONS; + } else { + return false; + } + + return true; +} + +bool Request::m_readURL() { + char *request = m_path; + int bufferLeft = m_pathLength; + int ch; + + while ((ch = m_timedRead()) != -1 && ch != ' ' && ch != '\n' && ch != '\r' && + --bufferLeft) { + if (ch == '%') { + int high = m_timedRead(); + if (high == -1) { + return false; + } + + int low = m_timedRead(); + if (low == -1) { + return false; + } + + if (high > 0x39) { + high -= 7; + } + + high &= 0x0f; + + if (low > 0x39) { + low -= 7; + } + + low &= 0x0f; + + ch = (high << 4) | low; + } + + *request++ = ch; + } + + *request = 0; + + return bufferLeft > 0; +} + +bool Request::m_readVersion() { + while (!m_expect(CRLF)) { + P(HTTP_10) = "1.0"; + P(HTTP_11) = "1.1"; + + if (m_expectP(HTTP_10)) { + m_minorVersion = 0; + } else if (m_expectP(HTTP_11)) { + m_minorVersion = 1; + } else if (m_timedRead() == -1) { + return false; + } + } + + return true; +} + +void Request::m_processURL() { + char *qmLocation = strchr(m_path, '?'); + int qmOffset = (qmLocation == NULL) ? 0 : 1; + + m_pathLength = (qmLocation == NULL) ? strlen(m_path) : (qmLocation - m_path); + m_query = m_path + m_pathLength + qmOffset; + m_queryLength = strlen(m_query); + + if (qmOffset) { + *qmLocation = 0; + } +} + +bool Request::m_processHeaders() { + bool canEnd = true; + + while (!(canEnd && m_expect(CRLF))) { + canEnd = false; + P(ContentLength) = "Content-Length:"; + if (m_expectP(ContentLength)) { + if (!m_readInt(m_left) || !m_expect(CRLF)) { + return false; + } + + canEnd = true; + } else { + HeaderNode *headerNode = m_headerTail; + + while (headerNode != NULL) { + P(headerSeparator) = ":"; + if (m_expect(headerNode->name) && m_expectP(headerSeparator)) { + if (!m_headerValue(headerNode->buffer, headerNode->bufferLength)) { + return false; + } + + canEnd = true; + break; + } + + headerNode = headerNode->next; + } + } + + if (!canEnd) { + while (!m_expect(CRLF)) { + if (m_timedRead() == -1) { + return false; + } + } + + canEnd = true; + } + } + + m_readingContent = true; + + return true; +} + +bool Request::m_headerValue(char *buffer, int bufferLength) { + int ch; + + if (buffer[0] != '\0') { + int length = strlen(buffer); + buffer[length] = ','; + buffer = buffer + length + 1; + bufferLength = bufferLength - (length + 1); + } + + if (!m_skipSpace()) { + return false; + } + + while ((ch = m_timedRead()) != -1) { + if (--bufferLength > 0) { + *buffer++ = ch; + } + + if (m_expect(CRLF)) { + *buffer = '\0'; + return bufferLength > 0; + } + } + + return false; +} + +bool Request::m_readInt(int &number) { + bool negate = false; + bool gotNumber = false; + + if (!m_skipSpace()) { + return false; + } + + int ch = m_timedRead(); + if (ch == -1) { + return false; + } + + if (ch == '-') { + negate = true; + ch = m_timedRead(); + if (ch == -1) { + return false; + } + } + + number = 0; + + while (ch >= '0' && ch <= '9') { + gotNumber = true; + number = number * 10 + ch - '0'; + ch = m_timedRead(); + if (ch == -1) { + return false; + } + } + + push(ch); + + if (negate) { + number = -number; + } + + return gotNumber; +} + +void Request::m_setRoute(const char *route, const char *pattern) { + m_route = route; + m_pattern = pattern; +} + +int Request::m_getUrlPathLength() { return m_pathLength; } + +bool Request::m_expect(const char *expected) { + const char *candidate = expected; + + while (*candidate != 0) { + int ch = m_timedRead(); + if (ch == -1) { + return false; + } + + if (tolower(ch) != tolower(*candidate++)) { + push(ch); + + while (--candidate != expected) { + push(candidate[-1]); + } + + return false; + } + } + + return true; +} + +bool Request::m_expectP(const unsigned char *expected) { + const unsigned char *candidate = expected; + + while (pgm_read_byte(candidate) != 0) { + int ch = m_timedRead(); + if (ch == -1) { + return false; + } + + if (tolower(ch) != tolower(pgm_read_byte(candidate++))) { + push(ch); + + while (--candidate != expected) { + push(pgm_read_byte(candidate-1)); + } + + return false; + } + } + + return true; +} + +bool Request::m_skipSpace() { + int ch; + + while ((ch = m_timedRead()) != -1 && (ch == ' ' || ch == '\t')) + ; + + if (ch == -1) { + return false; + } + + push(ch); + + return true; +} + +void Request::m_reset() { + HeaderNode *headerNode = m_headerTail; + while (headerNode != NULL) { + headerNode->buffer[0] = '\0'; + headerNode = headerNode->next; + } +} + +bool Request::m_timedout() { return m_readTimedout; } + +int Request::m_timedRead() { + int ch = timedRead(); + if (ch == -1) { + m_readTimedout = true; + } + + return ch; +} + +Router::Router() + : m_head(NULL) {} + +Router::~Router() { + MiddlewareNode *current = m_head; + MiddlewareNode *next; + + while (current != NULL) { + next = current->next; + delete current; + + current = next; + } + + m_head = NULL; +} + +void Router::del(const char *path, Middleware *middleware) { + m_addMiddleware(Request::DELETE, path, middleware); +} + +void Router::del(Middleware *middleware) { + del(NULL, middleware); +} + +void Router::get(const char *path, Middleware *middleware) { + m_addMiddleware(Request::GET, path, middleware); +} + +void Router::get(Middleware *middleware) { + get(NULL, middleware); +} + +void Router::head(const char *path, Middleware *middleware) { + m_addMiddleware(Request::HEAD, path, middleware); +} + +void Router::head(Middleware *middleware) { + head(NULL, middleware); +} + +void Router::options(const char *path, Middleware *middleware) { + m_addMiddleware(Request::OPTIONS, path, middleware); +} + +void Router::options(Middleware *middleware) { + options(NULL, middleware); +} + +void Router::post(const char *path, Middleware *middleware) { + m_addMiddleware(Request::POST, path, middleware); +} + +void Router::post(Middleware *middleware) { + post(NULL, middleware); +} + +void Router::put(const char *path, Middleware *middleware) { + m_addMiddleware(Request::PUT, path, middleware); +} + +void Router::put(Middleware *middleware) { + put(NULL, middleware); +} + +void Router::patch(const char *path, Middleware *middleware) { + m_addMiddleware(Request::PATCH, path, middleware); +} + +void Router::patch(Middleware *middleware) { + patch(NULL, middleware); +} + +void Router::use(const char *path, Middleware *middleware) { + m_addMiddleware(Request::ALL, path, middleware); +} + +void Router::use(Middleware *middleware) { + use(NULL, middleware); +} + +void Router::use(const char *path, Router *router) { + MiddlewareNode *tail = new MiddlewareNode(); + tail->path = path; + tail->middleware = NULL; + tail->router = router; + tail->next = NULL; + m_mountMiddleware(tail); +} + +void Router::use(Router *router) { + use(NULL, router); +} + +void Router::m_addMiddleware(Request::MethodType type, const char *path, + Middleware *middleware) { + MiddlewareNode *tail = new MiddlewareNode(); + tail->path = path; + tail->middleware = middleware; + tail->router = NULL; + tail->type = type; + tail->next = NULL; + + m_mountMiddleware(tail); +} + +void Router::m_mountMiddleware(MiddlewareNode *tail) { + if (m_head == NULL) { + m_head = tail; + } else { + MiddlewareNode *current = m_head; + + while (current->next != NULL) { + current = current->next; + } + + current->next = tail; + } +} + +void Router::m_dispatchMiddleware(Request &request, Response &response, int urlShift) { + MiddlewareNode *middleware = m_head; + + while (middleware != NULL && !response.ended()) { + if (middleware->router != NULL) { + int prefixLength = middleware->path ? strlen(middleware->path) : 0; + int shift = urlShift + prefixLength; + + if (middleware->path == NULL || strncmp(middleware->path, request.path() + urlShift, prefixLength) == 0) { + middleware->router->m_dispatchMiddleware(request, response, shift); + } + } else if (middleware->type == request.method() || middleware->type == Request::ALL) { + if (middleware->path == NULL || m_routeMatch(request.path() + urlShift, middleware->path)) { + request.m_setRoute(request.path() + urlShift, middleware->path); + middleware->middleware(request, response); + } + } + + middleware = middleware->next; + } +} + +bool Router::m_routeMatch(const char* route, const char* pattern) { + if (pattern[0] == '\0' && route[0] == '\0') { + return true; + } + + bool match = false; + int i = 0; + int j = 0; + + while (pattern[i] && route[j]) { + if (pattern[i] == ':') { + while (pattern[i] && pattern[i] != '/') { + i++; + } + + while (route[j] && route[j] != '/') { + j++; + } + + match = true; + } else if (pattern[i] == route[j]) { + j++; + i++; + match = true; + } else { + match = false; + break; + } + } + + if (match && !pattern[i] && route[j] == '/' && !route[i]) { + match = true; + } else if (pattern[i] || route[j]) { + match = false; + } + + return match; +} + +Application::Application() + : m_final(NULL), m_notFound(NULL), m_headerTail(NULL), m_timeout(1000) {} + +int Application::strcmpi(const char *s1, const char *s2) { + int i; + + for (i = 0; s1[i] && s2[i]; ++i) { + if (s1[i] == s2[i] || (s1[i] ^ 32) == s2[i]) { + continue; + } else { + break; + } + } + + if (s1[i] == s2[i]) { + return 0; + } + + if ((s1[i] | 32) < (s2[i] | 32)) { + return -1; + } + + return 1; +} + +int Application::strcmpiP(const char *s1, const unsigned char *s2) { + int i = 0; + + for (i = 0; s1[i] && pgm_read_byte(s2 + i); ++i) { + if (s1[i] == pgm_read_byte(s2 + i) || (s1[i] ^ 32) == pgm_read_byte(s2 + i)) { + continue; + } else { + break; + } + } + + if (s1[i] == pgm_read_byte(s2 + i)) { + return 0; + } + + if ((s1[i] | 32) < (pgm_read_byte(s2 + i) | 32)) { + return -1; + } + + return 1; +} + +Application::~Application() { + Request::HeaderNode *current = m_headerTail; + Request::HeaderNode *next; + + while (current != NULL) { + next = current->next; + delete current; + current = next; + } + + m_headerTail = NULL; +} + +void Application::del(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::DELETE, path, middleware); +} + +void Application::del(Router::Middleware *middleware) { + del(NULL, middleware); +} + +void Application::finally(Router::Middleware *final) { + m_final = final; +} + +void Application::get(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::GET, path, middleware); +} + +void Application::get(Router::Middleware *middleware) { + get(NULL, middleware); +} + +void Application::head(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::HEAD, path, middleware); +} + +void Application::head(Router::Middleware *middleware) { + head(NULL, middleware); +} + +void Application::notFound(Router::Middleware *notFound) { + m_notFound = notFound; +} + +void Application::options(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::OPTIONS, path, middleware); +} + +void Application::options(Router::Middleware *middleware) { + options(NULL, middleware); +} + +void Application::patch(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::PATCH, path, middleware); +} + +void Application::patch(Router::Middleware *middleware) { + patch(NULL, middleware); +} + +void Application::post(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::POST, path, middleware); +} + +void Application::post(Router::Middleware *middleware) { + post(NULL, middleware); +} + +void Application::put(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::PUT, path, middleware); +} + +void Application::put(Router::Middleware *middleware) { + put(NULL, middleware); +} + +void Application::process(Client *client, void *context) { + if (!client) { + return; + } + + char urlBuffer[SERVER_URL_BUFFER_SIZE]; + process(client, urlBuffer, SERVER_URL_BUFFER_SIZE, context); +} + + +void Application::process(Client *client, char *urlBuffer, int urlBufferLength, void *context) { + if (!client) { + return; + } + + uint8_t writeBuffer[SERVER_OUTPUT_BUFFER_SIZE]; + process(client, urlBuffer, urlBufferLength, writeBuffer, SERVER_OUTPUT_BUFFER_SIZE, context); +} + +void Application::process(Client *client, char *urlBuffer, int urlBufferLength, uint8_t * writeBuffer, int writeBufferLength, void* context) { + if (!client) { + return; + } + + Response response(client, writeBuffer, writeBufferLength); + Request request(client, &response, m_headerTail, urlBuffer, urlBufferLength, + m_timeout, context); + + m_process(request, response); + + if (m_final != NULL) { + m_final(request, response); + } + + response.m_finalize(); + + Request::HeaderNode *headerNode = m_headerTail; + while (headerNode != NULL) { + headerNode->buffer[0] = '\0'; + headerNode = headerNode->next; + } +} + +void Application::process(Stream *stream, void* context) { + if (!stream) { + return; + } + + StreamClient client(stream); + process(&client, context); +} + +void Application::process(Stream *stream, char *buffer, int bufferLength, void* context) { + if (!stream) { + return; + } + + StreamClient client(stream); + process(&client, buffer, bufferLength, context); +} + +void Application::process(Stream *stream, char *urlBuffer, int urlBufferLength, uint8_t * writeBuffer, int writeBufferLength, void* context) { + if (!stream) { + return; + } + + StreamClient client(stream); + process(&client, urlBuffer, urlBufferLength, writeBuffer, writeBufferLength, context); +} + +void Application::use(const char *path, Router::Middleware *middleware) { + m_defaultRouter.m_addMiddleware(Request::ALL, path, middleware); +} + +void Application::use(Router::Middleware *middleware) { + use(NULL, middleware); +} + +void Application::setTimeout(unsigned long timeoutMillis) { + m_timeout = timeoutMillis; +} + +void Application::use(const char *path, Router *router) { + m_defaultRouter.use(path, router); +} + +void Application::use(Router *router) { + use(NULL, router); +} + +void Application::m_process(Request &request, Response &response) { + if (!request.m_processMethod()) { + if (request.m_timedout()) { + return response.sendStatus(408); + } + + return response.sendStatus(400); + } + + if (!request.m_readURL()) { + if (request.m_timedout()) { + return response.sendStatus(408); + } + + return response.sendStatus(414); + } + + request.m_processURL(); + + if (!request.m_readVersion()) { + if (request.m_timedout()) { + return response.sendStatus(408); + } + + return response.sendStatus(505); + } + + if (!request.m_processHeaders()) { + if (request.m_timedout()) { + return response.sendStatus(408); + } + + return response.sendStatus(431); + } + + m_defaultRouter.m_dispatchMiddleware(request, response); + + if (!response.statusSent() && !response.ended()) { + if(m_notFound != NULL) { + response.status(404); + return m_notFound(request, response); + } + + return response.sendStatus(404); + } + + if (!response.headersSent()) { + response.m_printHeaders(); + } +} + +void Application::header(const char *name, char *buffer, int bufferLength) { + Request::HeaderNode *newNode = new Request::HeaderNode(); + + buffer[0] = '\0'; + + newNode->name = name; + newNode->buffer = buffer; + newNode->bufferLength = bufferLength; + newNode->next = NULL; + + if (m_headerTail == NULL) { + m_headerTail = newNode; + } else { + Request::HeaderNode *headerNode = m_headerTail; + + while (headerNode->next != NULL) { + headerNode = headerNode->next; + } + + headerNode->next = newNode; + } +} diff --git a/aWOT.h b/aWOT.h new file mode 100755 index 0000000..824c0c9 --- /dev/null +++ b/aWOT.h @@ -0,0 +1,343 @@ +/* + aWOT, Express.js inspired microcontreller web framework for the Web of Things + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef AWOT_H_ +#define AWOT_H_ + +#include +#include +#include + +#include "Client.h" + +#define CRLF "\r\n" + +#if defined(__AVR_ATmega328P__) || defined(__AVR_Atmega32U4__) || \ + defined(__AVR_ATmega16U4__) || defined(_AVR_ATmega328__) +#define LOW_MEMORY_MCU +#endif + +#ifndef SERVER_URL_BUFFER_SIZE +#if defined(LOW_MEMORY_MCU) +#define SERVER_URL_BUFFER_SIZE 64 +#else +#define SERVER_URL_BUFFER_SIZE 512 +#endif +#endif + +#ifndef SERVER_PUSHBACK_BUFFER_SIZE +#if defined(LOW_MEMORY_MCU) +#define SERVER_PUSHBACK_BUFFER_SIZE 32 +#else +#define SERVER_PUSHBACK_BUFFER_SIZE 128 +#endif +#endif + +#ifndef SERVER_OUTPUT_BUFFER_SIZE +#if defined(LOW_MEMORY_MCU) +#define SERVER_OUTPUT_BUFFER_SIZE 32 +#else +#define SERVER_OUTPUT_BUFFER_SIZE 1024 +#endif +#endif + +#ifndef SERVER_MAX_HEADERS +#define SERVER_MAX_HEADERS 10 +#endif + +#ifdef __AVR__ +#define P(name) static const unsigned char name[] __attribute__(( section(".progmem." #name) )) +#else +#define P(name) static const unsigned char name[] PROGMEM +#endif + +namespace awot { + +class StreamClient : public Client { + private: + Stream* s; + + public: + StreamClient(Stream* stream) : s(stream){}; + int connect(IPAddress, uint16_t){return 1;}; + int connect(const char*, uint16_t){return 1;}; + size_t write(uint8_t byte){return s->write(byte);}; + size_t write(const uint8_t* buffer, size_t length){return s->write(buffer, length);}; + int available(){return s->available();}; + int read() {return s->read();}; + int read(uint8_t* buffer, size_t length) { + size_t count = 0; + + while (count < length) { + int c = read(); + if (c < 0) { + break; + } + + *buffer++ = (uint8_t)c; + count++; + } + + return count; + } + int peek(){return s->peek();}; + void flush(){return s->flush();}; + void stop(){}; + uint8_t connected(){return 1;}; + operator bool(){return true;}; +}; + +class Response : public Print { + friend class Application; + friend class Router; + + public: + int availableForWrite(); + int bytesSent(); + void beginHeaders(); + void end(); + void endHeaders(); + bool ended(); + void flush(); + const char* get(const char* name); + bool headersSent(); + void printP(const unsigned char* string); + void printP(const char* string); + void sendStatus(int code); + void set(const char* name, const char* value); + void setDefaults(); + void status(int code); + int statusSent(); + size_t write(uint8_t data); + size_t write(uint8_t* buffer, size_t bufferLength); + void writeP(const unsigned char* data, size_t length); + + private: + Response(Client* client, uint8_t * writeBuffer, int writeBufferLength); + + void m_printStatus(int code); + bool m_shouldPrintHeaders(); + void m_printHeaders(); + void m_printCRLF(); + void m_flushBuf(); + void m_finalize(); + + Client* m_stream; + struct Headers { + const char* name; + const char* value; + } m_headers[SERVER_MAX_HEADERS]; + bool m_contentLenghtSet; + bool m_contentTypeSet; + bool m_keepAlive; + int m_statusSent; + bool m_headersSent; + bool m_sendingStatus; + bool m_sendingHeaders; + int m_headersCount; + char* m_mime; + int m_bytesSent; + bool m_ended; + uint8_t * m_buffer; + int m_bufferLength; + int m_bufFill; +}; + +class Request : public Stream { + friend class Application; + friend class Router; + + public: + enum MethodType { UNKNOWN, GET, HEAD, POST, PUT, DELETE, PATCH, OPTIONS, ALL }; + void* context; + + int available(); + int availableForWrite(); + int bytesRead(); + Stream* stream(); + void flush(); + bool form(char* name, int nameLength, char* value, int valueLength); + char* get(const char* name); + int left(); + MethodType method(); + char* path(); + int peek(); + void push(uint8_t ch); + char* query(); + bool query(const char* name, char* buffer, int bufferLength); + int read(); + int read(uint8_t* buf, size_t size); + bool route(const char* name, char* buffer, int bufferLength); + bool route(int number, char* buffer, int bufferLength); + int minorVersion(); + size_t write(uint8_t data); + size_t write(uint8_t* buffer, size_t bufferLength); + + private: + struct HeaderNode { + const char* name; + char* buffer; + int bufferLength; + HeaderNode* next; + }; + + Request(Client* client, Response* m_response, HeaderNode* headerTail, + char* urlBuffer, int urlBufferLength, unsigned long timeout, + void* context); + bool m_processMethod(); + bool m_readURL(); + bool m_readVersion(); + void m_processURL(); + bool m_processHeaders(); + bool m_headerValue(char* buffer, int bufferLength); + bool m_readInt(int& number); + void m_setRoute(const char* route, const char* pattern); + int m_getUrlPathLength(); + bool m_expect(const char* expected); + bool m_expectP(const unsigned char* expected); + bool m_skipSpace(); + void m_reset(); + int m_timedRead(); + bool m_timedout(); + + Client* m_stream; + Response* m_response; + MethodType m_method; + int m_minorVersion; + unsigned char m_pushback[SERVER_PUSHBACK_BUFFER_SIZE]; + int m_pushbackDepth; + bool m_readingContent; + int m_left; + int m_bytesRead; + HeaderNode* m_headerTail; + char* m_query; + int m_queryLength; + bool m_readTimedout; + char* m_path; + int m_pathLength; + const char* m_pattern; + const char* m_route; +}; + +class Router { + friend class Application; + + public: + typedef void Middleware(Request& request, Response& response); + + Router(); + ~Router(); + + void del(const char* path, Middleware* middleware); + void del(Middleware* middleware); + void get(const char* path, Middleware* middleware); + void get(Middleware* middleware); + void head(const char* path, Middleware* middleware); + void head(Middleware* middleware); + void options(const char* path, Middleware* middleware); + void options(Middleware* middleware); + void patch(const char* path, Middleware* middleware); + void patch(Middleware* middleware); + void post(const char* path, Middleware* middleware); + void post(Middleware* middleware); + void put(const char* path, Middleware* middleware); + void put(Middleware* middleware); + void use(const char* path, Router* router); + void use(Router* router); + void use(const char* path, Middleware* middleware); + void use(Middleware* middleware); + + private: + struct MiddlewareNode { + const char* path; + Middleware* middleware; + Router* router; + Request::MethodType type; + MiddlewareNode* next; + }; + + void m_addMiddleware(Request::MethodType type, const char* path, + Middleware* middleware); + void m_mountMiddleware(MiddlewareNode *tail); + void m_setNext(Router* next); + Router* m_getNext(); + void m_dispatchMiddleware(Request& request, Response& response, int urlShift = 0); + bool m_routeMatch(const char* route, const char* pattern); + + MiddlewareNode* m_head; +}; + +class Application { + public: + Application(); + ~Application(); + + static int strcmpi(const char* s1, const char* s2); + static int strcmpiP(const char* s1, const unsigned char* s2); + + void del(const char* path, Router::Middleware* middleware); + void del(Router::Middleware* middleware); + void finally(Router::Middleware* middleware); + void get(const char* path, Router::Middleware* middleware); + void get(Router::Middleware* middleware); + void head(const char* path, Router::Middleware* middleware); + void head(Router::Middleware* middleware); + void header(const char* name, char* buffer, int bufferLength); + void notFound(Router::Middleware* middleware); + void options(const char* path, Router::Middleware* middleware); + void options(Router::Middleware* middleware); + void patch(const char* path, Router::Middleware* middleware); + void patch(Router::Middleware* middleware); + void post(const char* path, Router::Middleware* middleware); + void post(Router::Middleware* middleware); + void put(const char* path, Router::Middleware* middleware); + void put(Router::Middleware* middleware); + void process(Client* client, void* context = NULL); + void process(Client* client, char* urlbuffer, int urlBufferLength, void* context = NULL); + void process(Client* client, char* urlBuffer, int urlBufferLength, uint8_t * writeBuffer, int writeBufferLength, void* context = NULL); + void process(Stream* stream, void* context = NULL); + void process(Stream* stream, char* urlbuffer, int urlBufferLength, void* context = NULL); + void process(Stream* stream, char* urlBuffer, int urlBufferLength, uint8_t * writeBuffer, int writeBufferLength, void* context = NULL); + + void setTimeout(unsigned long timeoutMillis); + void use(const char* path, Router* router); + void use(Router* router); + void use(const char* path, Router::Middleware* middleware); + void use(Router::Middleware* middleware); + + private: + void m_process(Request &req, Response &res); + + Router::Middleware* m_final; + Router::Middleware* m_notFound; + Router m_defaultRouter; + Request::HeaderNode* m_headerTail; + unsigned long m_timeout; +}; + +} + +#ifndef ENABLE_AWOT_NAMESPACE +using namespace awot; +#endif + +#endif diff --git a/olStatic.txt b/olStatic.txt new file mode 100755 index 0000000..331e71a --- /dev/null +++ b/olStatic.txt @@ -0,0 +1,21 @@ +void static_index(Request &req, Response &res) { + P(index) = + "\n" + "\n" + "Hello World!\n" + "\n" + "\n" + "

Greetings middle earth!

\n" + "\n" + ""; + + res.set("Content-Type", "text/html"); + res.printP(index); +} + +Router staticFileRouter; + +Router * staticFiles() { + staticFileRouter.get("/", &static_index); + return &staticFileRouter; +}