In order to control plants via Dropbox we built the following control circuit / Cyber Physical System (CPS) / Internet of Things application. To support sustainability, the water used is taken out of a natural pond. The required energy is supplied by a solar panel that charges an old automotive accumulator (battery). Images of the plant growth progress and data is being sent to dropbox constantly. This project was part of a thesis and the advanced development / recursive modelling has been performed with SysML to verify applicability of Model Based Systems Engineering (MBSE) in the context of IoT and CPS.
| {gallery} Layout |
|---|
Layout: |
Schema: |
SysML CPS Profile & abstract reference architecture (1/3): |
{gallery} Dropbox GUI and Hardware |
|---|
Measured data: Battery level; Humidity; Temperature; XBee connection; Water level |
Images sent to Dropbox: |
Arduino: |
Battery: |
Arduino code:
#include <MemoryFree.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include "pb_encode.h"
#include "pb_decode.h"
#include "messages_pb.h"
#include <SoilHumidity2.h>
#include <LM35.h>
//#include <AltSoftSerial.h>
#include <SoftwareSerial.h>
#include <JPEGCamera.h>
// sensors
SoilHumidity2 hsensor(3, 1); // soil resistance sensor: digital out pin, analog in pin
LM35 tsensor(0); // temperature sensor: analog in pin
const int batVPin = 2; // bat level analog pin
const int waterVPin = 3; // water level in pin
const int waterOPin = 5; // water level sensor digital out pin
const int pumpOPin = 6; // pump digital out pin
long pumpEndT = -1; // time to turn pump off, -1 if pump is off
long lastCommand = millis(); // time of last command. if we do not receive a command within 2 min. we go to sleep for 56 secs -> save power
int pumpBattLevel = 0; // level of bat while pumping
const int WATER_LEVEL = 400;
// communication
SoftwareSerial s(8,9); // cam serial
SoftwareSerial s2(10,11); // xbee serial
JPEGCamera cam(s);
// xbee comm protocol
const unsigned int MAGIC = 7557; // frame prefix
messages_SensorData sensorData;
messages_Command commandData;
messages_picData picData;
messages_Ok okData;
char rBuffer[16]; // read buffer
uint8_t wBuffer[32]; // write buffer
pb_ostream_t stream;
// xbee comm protocol parsing
int readState = 0; // 0: magic_h, 1: magic_l, 2: len_h, 3: len_l, 4: buffer
int readExpectedLen = 0;
int m;
uint8_t h;
uint8_t l;
uint8_t command = 255;
int command_arg;
// control led
int led = 13;
int value = HIGH;
// power control
volatile long sleepOn = 0;
int powerPin = 12;
void softReset() // Restarts program from beginning but does not reset the peripherals and registers
{
asm volatile (" jmp 0");
}
ISR(WDT_vect)
{
//Serial.println("ISR");
if (sleepOn > -1)
{
}
else
{
softReset();
}
}
void setupWDT()
{
/*** Setup the WDT ***/
/* Clear the reset flag. */
MCUSR &= ~(1<<WDRF);
/* In order to change WDE or the prescaler, we need to
* set WDCE (This will allow updates for 4 clock cycles).
*/
WDTCSR |= (1<<WDCE) | (1<<WDE);
/* set new watchdog timeout prescaler value */
WDTCSR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */
/* Enable the WD interrupt (note no reset). */
WDTCSR |= _BV(WDIE);
}
void enterSleep()
{
// set_sleep_mode(SLEEP_MODE_PWR_SAVE); /* EDIT: could also use SLEEP_MODE_PWR_DOWN for lowest power consumption. */
set_sleep_mode(SLEEP_MODE_PWR_DOWN); /* EDIT: could also use SLEEP_MODE_PWR_DOWN for lowest power consumption. */
/* Now enter sleep mode. */
sleep_enable();
sleep_mode();
}
void camSave()
{
/*
s.begin(38400);
s.listen();
wdt_reset();
cam.reset();
delay(6000);
wdt_reset();
cam.powerSaving(true);
*/
}
void sleepIt(long duration)
{
LEDLow();
sleepOn = duration/8000;
if (sleepOn == 0)
sleepOn = 1;
digitalWrite(powerPin, LOW);
while (sleepOn)
{
enterSleep();
/* The program will continue from here after the WDT timeout*/
sleepOn--;
}
sleep_disable(); /* First thing to do is disable sleep. */
/* Re-enable the peripherals. */
power_all_enable();
delay(100);
digitalWrite(powerPin, HIGH);
// camSave();
}
// parse xbee comm
void doSerial()
{
s2.listen();
//Serial.println("event");
while (s2.available())
{
invertLED();
switch (readState)
{
case 0:
h = s2.read();
if (h == highByte(MAGIC))
readState = 1;
break;
case 1:
l = s2.read();
m = word(h,l);
if (m != MAGIC)
readState = 0;
else
{
readState = 2;
}
m = 0;
break;
case 2:
h = s2.read();
readState = 3;
break;
case 3:
l = s2.read();
readExpectedLen = word(h, l);
// Serial.print("len ");
// Serial.println(readExpectedLen, DEC);
if (readExpectedLen > 32)
{
readState = 0;
}
else
{
readState = 4;
}
break;
case 4:
if (s2.readBytes(rBuffer, readExpectedLen) == readExpectedLen)
{
pb_istream_t stream = pb_istream_from_buffer((uint8_t *)rBuffer, sizeof(rBuffer));
pb_decode(&stream, messages_Command_fields, &commandData);
command = commandData.command;
// Serial.print(7,DEC);
// Serial.println(command,DEC);
}
readState = 0;
break;
default:
readState = 0;
break;
}
}
}
void setup()
{
stream = pb_ostream_from_buffer(wBuffer, sizeof(wBuffer));
s2.begin(9600); // set low xbee baudrate to reduce error rate.
s.begin(38400); // default cam baudrate is 38400
// Serial.begin(9600); // use serial for debug, but may block if used together with software serial
// Serial.println("setup");
pinMode(led, OUTPUT);
pinMode(batVPin, INPUT);
pinMode(waterVPin, INPUT);
pinMode(waterOPin, OUTPUT);
pinMode(pumpOPin, OUTPUT);
pinMode(powerPin, OUTPUT);
// turn xbee on
digitalWrite(powerPin, HIGH);
setupWDT();
lastCommand = millis();
// camSave();
// set LED to high
LEDHigh();
// wdt_enable(WDTO_8S); // set watchdog to 8 seconds. may be set lower in future
}
void loop()
{
wdt_reset(); // trigger watchdog
if (pumpEndT > 0 && pumpEndT < millis()) // check if pump is on
offPump();
doSerial(); // parse input
if (command < 255) // if we received a command: execute it
{
lastCommand = millis();
invertLED();
wdt_reset();
doCommand();
command = 255;
// Serial.print(0,DEC);
// Serial.print(command, DEC);
}
else
{
if (pumpEndT <= 0 && (millis() - lastCommand) > 60000)
{
sleepIt(56000);
lastCommand = millis()-55000;
}
}
}
void LEDHigh()
{
value = LOW;
invertLED();
}
void LEDLow()
{
value = HIGH;
invertLED();
}
void invertLED()
{
if (value == HIGH)
value = LOW;
else
value = HIGH;
digitalWrite(led, value);
}
void doPump()
{
pb_ostream_t stream = pb_ostream_from_buffer(wBuffer, sizeof(wBuffer));
if (pb_encode(&stream, messages_Ok_fields, &okData))
{
//Serial.print("b ");
//Serial.println(stream.bytes_written, DEC);
sendMessage(stream.bytes_written);
}
// else
// Serial.println("error encoding ok in doPump");
long duration = commandData.arg;
// Serial.print(millis(), DEC);Serial.print(" doPump ");Serial.println(duration, DEC);
if (duration > 0) // duration 0 == heart beat
{
pumpEndT = millis()+duration;
digitalWrite(pumpOPin, HIGH);
delay(500);
pumpBattLevel = getBatLevel(); // get bat level only when bat is under load
}
}
void doSleep()
{
long duration = commandData.arg;
pb_encode(&stream, messages_Ok_fields, &okData);
sendMessage(stream.bytes_written);
if (duration > 0) // duration 0 == heart beat
{
sleepIt(duration);
}
}
int getBatLevel()
{
return analogRead(batVPin);
}
void getWaterLevel()
{
digitalWrite(waterOPin, HIGH);
delay(500);
int x = analogRead(waterVPin);
digitalWrite(waterOPin, LOW);
bool hasWater = x > WATER_LEVEL;
sensorData.hasWater = hasWater;
}
void offPump()
{
// Serial.print(millis(), DEC);Serial.println(" offPump");
pumpEndT = -1;
digitalWrite(pumpOPin, LOW);
}
void sendPic()
{
// Serial.println();
// Serial.println(9, DEC);
// Serial port connected to the cam
s.listen();
delay(1000);
cam.reset();
delay(4000);
wdt_reset();
// cam.powerSaving(false);
/*
delay(50);
cam.chBaudRate(1);
delay(50);
s.end();
s.begin(19200);
delay(50);
*/
invertLED();
cam.takePicture();
wdt_reset();
delay(25);
picData.size = cam.getSize();
pb_ostream_t stream = pb_ostream_from_buffer(wBuffer, sizeof(wBuffer));
pb_encode(&stream, messages_picData_fields, &picData);
sendMessage(stream.bytes_written);
delay(10);
invertLED();
wdt_reset();
cam.readData(s2);
s2.flush();
cam.stopPictures();
invertLED();
cam.powerSaving(true);
}
void sendSensorData()
{
wdt_reset();
sensorData.temperature = tsensor.temperature();
wdt_reset();
sensorData.humidity = hsensor.humidity();
wdt_reset();
if (pumpBattLevel > 0)
{
sensorData.batLevel = pumpBattLevel;
pumpBattLevel = 0;
}
else
sensorData.batLevel = getBatLevel();
getWaterLevel();
pb_ostream_t stream = pb_ostream_from_buffer(wBuffer, sizeof(wBuffer));
pb_encode(&stream, messages_SensorData_fields, &sensorData);
sendMessage(stream.bytes_written);
}
void sendMessage(size_t len)
{
// Serial.print("send ");Serial.println(len, DEC);
sendStartMessage(len);
s2.write(wBuffer, len);
s2.flush();
delay(100);
//Serial.println("endsend");
}
void sendStartMessage(size_t len)
{
s2.write(highByte(MAGIC));
s2.write(lowByte(MAGIC));
s2.write(highByte(len));
s2.write(lowByte(len));
}
void doCommand()
{
// Serial.print(8,DEC);
// Serial.println(command,DEC);
switch (command)
{
case 0:
sendSensorData();
break;
case 1:
doPump();
break;
case 2:
sendPic();
break;
case 3:
doSleep();
break;
default:
break;
}
}
BoM:







Top Comments