From c3121c907ae16c4f40cc8069f08668c875a7eb28 Mon Sep 17 00:00:00 2001 From: 2ManyProjects Date: Mon, 10 Mar 2025 19:31:28 -0500 Subject: [PATCH] BioController --- AutoHumidity.ino | 371 ++++++++++ DataLogger.ino | 93 +++ Incubator.ino | 181 +++++ MainController.ino | 462 ++++++++++++ StaticFiles.h | 21 + aWOT.cpp | 1736 ++++++++++++++++++++++++++++++++++++++++++++ aWOT.h | 343 +++++++++ olStatic.txt | 21 + 8 files changed, 3228 insertions(+) create mode 100755 AutoHumidity.ino create mode 100644 DataLogger.ino create mode 100755 Incubator.ino create mode 100755 MainController.ino create mode 100755 StaticFiles.h create mode 100755 aWOT.cpp create mode 100755 aWOT.h create mode 100755 olStatic.txt 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; +}