Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
Indoor growing systems can be fiddly to set up and require a large amount of monitoring and expertise on the growers part to get good results. A such there is a need for simpler indoor growth systems that can be used simply by anyone and take little time to set up and maintain.
This project aims to deliver a low-cost and low-input alternative to small-scale commercial autonomous plant growing systems, such as hydroponic growth systems.
Our project intends to create a small enclosed container which automatically senses and controls environmental factors important for growth in which small edible plants and fungi can be grown within the home.
The intent is to have the system entirely autonomous with no intervention needed by the grower until it is time to harvest.
How it worksWithin this system temperature, soil moisture content, humidity and CO2 can be monitored, and water pumps, heat strips, air pumps and water sprays can then be turned on or off automatically to maintain optimal conditions.
The container needs to be almost completely enclosed to allow for a controllable environment in which conditions can be optimised.
Conditions which can be monitored include: temperature, soil moisture content, humidity, CO2 levels.
The Arduino board will also be connected to a water pump, fan, heat strips and water atomiser which shall then be automatically turned on or off as needed to maintain optimal conditions.
We hope to configure a touchscreen to allow conditions to be viewable at any time. All this shall be programmed into the system using XOD, with the option to programme in optimal values for the species being grown.
// The sketch is auto-generated with XOD (https://xod.io).
//
// You can compile and upload it to an Arduino-compatible board with
// Arduino IDE.
//
// Rough code overview:
//
// - Configuration section
// - STL shim
// - Immutable list classes and functions
// - XOD runtime environment
// - Native node implementation
// - Program graph definition
//
// Search for comments fenced with '====' and '----' to navigate through
// the major code blocks.
#include <Arduino.h>
#include <inttypes.h>
/*=============================================================================
*
*
* Configuration
*
*
=============================================================================*/
// Uncomment to turn on debug of the program
//#define XOD_DEBUG
// Uncomment to trace the program runtime in the Serial Monitor
//#define XOD_DEBUG_ENABLE_TRACE
/*=============================================================================
*
*
* STL shim. Provides implementation for vital std::* constructs
*
*
=============================================================================*/
namespace xod {
namespace std {
template< class T > struct remove_reference {typedef T type;};
template< class T > struct remove_reference<T&> {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};
template <class T>
typename remove_reference<T>::type&& move(T&& a) {
return static_cast<typename remove_reference<T>::type&&>(a);
}
} // namespace std
} // namespace xod
/*=============================================================================
*
*
* XOD-specific list/array implementations
*
*
=============================================================================*/
#ifndef XOD_LIST_H
#define XOD_LIST_H
namespace xod {
namespace detail {
/*
* Cursors are used internaly by iterators and list views. They are not exposed
* directly to a list consumer.
*
* The base `Cursor` is an interface which provides the bare minimum of methods
* to facilitate a single iteration pass.
*/
template<typename T> class Cursor {
public:
virtual ~Cursor() { }
virtual bool isValid() const = 0;
virtual bool value(T* out) const = 0;
virtual void next() = 0;
};
template<typename T> class NilCursor : public Cursor<T> {
public:
virtual bool isValid() const { return false; }
virtual bool value(T*) const { return false; }
virtual void next() { }
};
} // namespace detail
/*
* Iterator is an object used to iterate a list once.
*
* Users create new iterators by calling `someList.iterate()`.
* Iterators are created on stack and are supposed to have a
* short live, e.g. for a duration of `for` loop or node’s
* `evaluate` function. Iterators can’t be copied.
*
* Implemented as a pimpl pattern wrapper over the cursor.
* Once created for a cursor, an iterator owns that cursor
* and will delete the cursor object once destroyed itself.
*/
template<typename T>
class Iterator {
public:
static Iterator<T> nil() {
return Iterator<T>(new detail::NilCursor<T>());
}
Iterator(detail::Cursor<T>* cursor)
: _cursor(cursor)
{ }
~Iterator() {
if (_cursor)
delete _cursor;
}
Iterator(const Iterator& that) = delete;
Iterator& operator=(const Iterator& that) = delete;
Iterator(Iterator&& it)
: _cursor(it._cursor)
{
it._cursor = nullptr;
}
Iterator& operator=(Iterator&& it) {
auto tmp = it._cursor;
it._cursor = _cursor;
_cursor = tmp;
return *this;
}
operator bool() const { return _cursor->isValid(); }
bool value(T* out) const {
return _cursor->value(out);
}
T operator*() const {
T out;
_cursor->value(&out);
return out;
}
Iterator& operator++() {
_cursor->next();
return *this;
}
private:
detail::Cursor<T>* _cursor;
};
/*
* An interface for a list view. A particular list view provides a new
* kind of iteration over existing data. This way we can use list slices,
* list concatenations, list rotations, etc without introducing new data
* buffers. We just change the way already existing data is iterated.
*
* ListView is not exposed to a list user directly, it is used internally
* by the List class. However, deriving a new ListView is necessary if you
* make a new list/string processing node.
*/
template<typename T> class ListView {
public:
virtual Iterator<T> iterate() const = 0;
};
/*
* The list as it seen by data consumers. Have a single method `iterate`
* to create a new iterator.
*
* Implemented as pimpl pattern wrapper over a list view. Takes pointer
* to a list view in constructor and expects the view will be alive for
* the whole life time of the list.
*/
template<typename T> class List {
public:
constexpr List()
: _view(nullptr)
{ }
List(const ListView<T>* view)
: _view(view)
{ }
Iterator<T> iterate() const {
return _view ? _view->iterate() : Iterator<T>::nil();
}
// pre 0.15.0 backward compatibility
List* operator->() __attribute__ ((deprecated)) { return this; }
const List* operator->() const __attribute__ ((deprecated)) { return this; }
private:
const ListView<T>* _view;
};
/*
* A list view over an old good plain C array.
*
* Expects the array will be alive for the whole life time of the
* view.
*/
template<typename T> class PlainListView : public ListView<T> {
public:
class Cursor : public detail::Cursor<T> {
public:
Cursor(const PlainListView* owner)
: _owner(owner)
, _idx(0)
{ }
bool isValid() const override {
return _idx < _owner->_len;
}
bool value(T* out) const override {
if (!isValid())
return false;
*out = _owner->_data[_idx];
return true;
}
void next() override { ++_idx; }
private:
const PlainListView* _owner;
size_t _idx;
};
public:
PlainListView(const T* data, size_t len)
: _data(data)
, _len(len)
{ }
virtual Iterator<T> iterate() const override {
return Iterator<T>(new Cursor(this));
}
private:
friend class Cursor;
const T* _data;
size_t _len;
};
/*
* A list view over a null-terminated C-String.
*
* Expects the char buffer will be alive for the whole life time of the view.
* You can use string literals as a buffer, since they are persistent for
* the program execution time.
*/
class CStringView : public ListView<char> {
public:
class Cursor : public detail::Cursor<char> {
public:
Cursor(const char* str)
: _ptr(str)
{ }
bool isValid() const override {
return (bool)*_ptr;
}
bool value(char* out) const override {
*out = *_ptr;
return (bool)*_ptr;
}
void next() override { ++_ptr; }
private:
const char* _ptr;
};
public:
CStringView(const char* str = nullptr)
: _str(str)
{ }
CStringView& operator=(const CStringView& rhs) {
_str = rhs._str;
return *this;
}
virtual Iterator<char> iterate() const override {
return _str ? Iterator<char>(new Cursor(_str)) : Iterator<char>::nil();
}
private:
friend class Cursor;
const char* _str;
};
/*
* A list view over two other lists (Left and Right) which first iterates the
* left one, and when exhausted, iterates the right one.
*
* Expects both Left and Right to be alive for the whole view life time.
*/
template<typename T> class ConcatListView : public ListView<T> {
public:
class Cursor : public detail::Cursor<T> {
public:
Cursor(Iterator<T>&& left, Iterator<T>&& right)
: _left(std::move(left))
, _right(std::move(right))
{ }
bool isValid() const override {
return _left || _right;
}
bool value(T* out) const override {
return _left.value(out) || _right.value(out);
}
void next() override {
_left ? ++_left : ++_right;
}
private:
Iterator<T> _left;
Iterator<T> _right;
};
public:
ConcatListView() { }
ConcatListView(List<T> left, List<T> right)
: _left(left)
, _right(right)
{ }
ConcatListView& operator=(const ConcatListView& rhs) {
_left = rhs._left;
_right = rhs._right;
return *this;
}
virtual Iterator<T> iterate() const override {
return Iterator<T>(new Cursor(_left.iterate(), _right.iterate()));
}
private:
friend class Cursor;
List<T> _left;
List<T> _right;
};
//----------------------------------------------------------------------------
// Text string helpers
//----------------------------------------------------------------------------
using XString = List<char>;
/*
* List and list view in a single pack. An utility used to define constant
* string literals in XOD.
*/
class XStringCString : public XString {
public:
XStringCString(const char* str)
: XString(&_view)
, _view(str)
{ }
private:
CStringView _view;
};
} // namespace xod
#endif
/*=============================================================================
*
*
* Functions to work with memory
*
*
=============================================================================*/
#ifdef __AVR__
// Placement `new` for Arduino
void* operator new(size_t, void* ptr) {
return ptr;
}
#endif
/*=============================================================================
*
*
* UART Classes, that wraps Serials
*
*
=============================================================================*/
class HardwareSerial;
class SoftwareSerial;
namespace xod {
class Uart {
private:
long _baud;
protected:
bool _started = false;
public:
Uart(long baud) {
_baud = baud;
}
virtual void begin() = 0;
virtual void end() = 0;
virtual void flush() = 0;
virtual bool available() = 0;
virtual bool writeByte(uint8_t) = 0;
virtual bool readByte(uint8_t*) = 0;
virtual SoftwareSerial* toSoftwareSerial() {
return nullptr;
}
virtual HardwareSerial* toHardwareSerial() {
return nullptr;
}
void changeBaudRate(long baud) {
_baud = baud;
if (_started) {
end();
begin();
}
}
long getBaudRate() const {
return _baud;
}
Stream* toStream() {
Stream* stream = (Stream*) toHardwareSerial();
if (stream) return stream;
return (Stream*) toSoftwareSerial();
}
};
class HardwareUart : public Uart {
private:
HardwareSerial* _serial;
public:
HardwareUart(HardwareSerial& hserial, uint32_t baud = 115200) : Uart(baud) {
_serial = &hserial;
}
void begin();
void end();
void flush();
bool available() {
return (bool) _serial->available();
}
bool writeByte(uint8_t byte) {
return (bool) _serial->write(byte);
}
bool readByte(uint8_t* out) {
int data = _serial->read();
if (data == -1) return false;
*out = data;
return true;
}
HardwareSerial* toHardwareSerial() {
return _serial;
}
};
void HardwareUart::begin() {
_started = true;
_serial->begin(getBaudRate());
};
void HardwareUart::end() {
_started = false;
_serial->end();
};
void HardwareUart::flush() {
_serial->flush();
};
} // namespace xod
/*=============================================================================
*
*
* Basic algorithms for XOD lists
*
*
=============================================================================*/
#ifndef XOD_LIST_FUNCS_H
#define XOD_LIST_FUNCS_H
namespace xod {
/*
* Folds a list from left. Also known as "reduce".
*/
template<typename T, typename TR>
TR foldl(List<T> xs, TR (*func)(TR, T), TR acc) {
for (auto it = xs.iterate(); it; ++it)
acc = func(acc, *it);
return acc;
}
template<typename T> size_t lengthReducer(size_t len, T) {
return len + 1;
}
/*
* Computes length of a list.
*/
template<typename T> size_t length(List<T> xs) {
return foldl(xs, lengthReducer<T>, (size_t)0);
}
template<typename T> T* dumpReducer(T* buff, T x) {
*buff = x;
return buff + 1;
}
/*
* Copies a list content into a memory buffer.
*
* It is expected that `outBuff` has enough size to fit all the data.
*/
template<typename T> size_t dump(List<T> xs, T* outBuff) {
T* buffEnd = foldl(xs, dumpReducer, outBuff);
return buffEnd - outBuff;
}
/*
* Compares two lists.
*/
template<typename T> bool equal(List<T> lhs, List<T> rhs) {
auto lhsIt = lhs.iterate();
auto rhsIt = rhs.iterate();
for (; lhsIt && rhsIt; ++lhsIt, ++rhsIt) {
if (*lhsIt != *rhsIt) return false;
}
return !lhsIt && !rhsIt;
}
} // namespace xod
#endif
/*=============================================================================
*
*
* Runtime
*
*
=============================================================================*/
//----------------------------------------------------------------------------
// Debug routines
//----------------------------------------------------------------------------
#ifndef DEBUG_SERIAL
# define DEBUG_SERIAL Serial
#endif
#if defined(XOD_DEBUG) && defined(XOD_DEBUG_ENABLE_TRACE)
# define XOD_TRACE(x) { DEBUG_SERIAL.print(x); DEBUG_SERIAL.flush(); }
# define XOD_TRACE_LN(x) { DEBUG_SERIAL.println(x); DEBUG_SERIAL.flush(); }
# define XOD_TRACE_F(x) XOD_TRACE(F(x))
# define XOD_TRACE_FLN(x) XOD_TRACE_LN(F(x))
#else
# define XOD_TRACE(x)
# define XOD_TRACE_LN(x)
# define XOD_TRACE_F(x)
# define XOD_TRACE_FLN(x)
#endif
//----------------------------------------------------------------------------
// PGM space utilities
//----------------------------------------------------------------------------
#define pgm_read_nodeid(address) (pgm_read_word(address))
/*
* Workaround for bugs:
* https://github.com/arduino/ArduinoCore-sam/pull/43
* https://github.com/arduino/ArduinoCore-samd/pull/253
* Remove after the PRs merge
*/
#if !defined(ARDUINO_ARCH_AVR) && defined(pgm_read_ptr)
# undef pgm_read_ptr
# define pgm_read_ptr(addr) (*(const void **)(addr))
#endif
//----------------------------------------------------------------------------
// Compatibilities
//----------------------------------------------------------------------------
#if !defined(ARDUINO_ARCH_AVR) && !defined(__DTOSTRF_H_)
/*
* Provide dtostrf function for non-AVR platforms. Although many platforms
* provide a stub many others do not. And the stub is based on `sprintf`
* which doesn’t work with floating point formatters on some platforms
* (e.g. Arduino M0).
*
* This is an implementation based on `fcvt` standard function. Taken here:
* https://forum.arduino.cc/index.php?topic=368720.msg2542614#msg2542614
*/
char *dtostrf(double val, int width, unsigned int prec, char *sout) {
int decpt, sign, reqd, pad;
const char *s, *e;
char *p;
s = fcvt(val, prec, &decpt, &sign);
if (prec == 0 && decpt == 0) {
s = (*s < '5') ? "0" : "1";
reqd = 1;
} else {
reqd = strlen(s);
if (reqd > decpt) reqd++;
if (decpt == 0) reqd++;
}
if (sign) reqd++;
p = sout;
e = p + reqd;
pad = width - reqd;
if (pad > 0) {
e += pad;
while (pad-- > 0) *p++ = ' ';
}
if (sign) *p++ = '-';
if (decpt <= 0 && prec > 0) {
*p++ = '0';
*p++ = '.';
e++;
while ( decpt < 0 ) {
decpt++;
*p++ = '0';
}
}
while (p < e) {
*p++ = *s++;
if (p == e) break;
if (--decpt == 0) *p++ = '.';
}
if (width < 0) {
pad = (reqd + width) * -1;
while (pad-- > 0) *p++ = ' ';
}
*p = 0;
return sout;
}
#endif
namespace xod {
//----------------------------------------------------------------------------
// Type definitions
//----------------------------------------------------------------------------
#if __SIZEOF_FLOAT__ == 4
typedef float Number;
#else
typedef double Number;
#endif
typedef bool Logic;
typedef unsigned long TimeMs;
typedef uint8_t DirtyFlags;
//----------------------------------------------------------------------------
// Global variables
//----------------------------------------------------------------------------
TimeMs g_transactionTime;
bool g_isSettingUp;
//----------------------------------------------------------------------------
// Metaprogramming utilities
//----------------------------------------------------------------------------
template<typename T> struct always_false {
enum { value = 0 };
};
//----------------------------------------------------------------------------
// Forward declarations
//----------------------------------------------------------------------------
TimeMs transactionTime();
void runTransaction();
//----------------------------------------------------------------------------
// Engine (private API)
//----------------------------------------------------------------------------
namespace detail {
template<typename NodeT>
bool isTimedOut(const NodeT* node) {
TimeMs t = node->timeoutAt;
// TODO: deal with uint32 overflow
return t && t < transactionTime();
}
// Marks timed out node dirty. Do not reset timeoutAt here to give
// a chance for a node to get a reasonable result from `isTimedOut`
// later during its `evaluate`
template<typename NodeT>
void checkTriggerTimeout(NodeT* node) {
node->isNodeDirty |= isTimedOut(node);
}
template<typename NodeT>
void clearTimeout(NodeT* node) {
node->timeoutAt = 0;
}
template<typename NodeT>
void clearStaleTimeout(NodeT* node) {
if (isTimedOut(node))
clearTimeout(node);
}
} // namespace detail
//----------------------------------------------------------------------------
// Public API (can be used by native nodes’ `evaluate` functions)
//----------------------------------------------------------------------------
TimeMs transactionTime() {
return g_transactionTime;
}
bool isSettingUp() {
return g_isSettingUp;
}
template<typename ContextT>
void setTimeout(ContextT* ctx, TimeMs timeout) {
ctx->_node->timeoutAt = transactionTime() + timeout;
}
template<typename ContextT>
void clearTimeout(ContextT* ctx) {
detail::clearTimeout(ctx->_node);
}
template<typename ContextT>
bool isTimedOut(const ContextT* ctx) {
return detail::isTimedOut(ctx->_node);
}
bool isValidDigitalPort(uint8_t port) {
#if defined(__AVR__) && defined(NUM_DIGITAL_PINS)
return port < NUM_DIGITAL_PINS;
#else
return true;
#endif
}
bool isValidAnalogPort(uint8_t port) {
#if defined(__AVR__) && defined(NUM_ANALOG_INPUTS)
return port >= A0 && port < A0 + NUM_ANALOG_INPUTS;
#else
return true;
#endif
}
} // namespace xod
//----------------------------------------------------------------------------
// Entry point
//----------------------------------------------------------------------------
void setup() {
// FIXME: looks like there is a rounding bug. Waiting for 100ms fights it
delay(100);
#ifdef XOD_DEBUG
DEBUG_SERIAL.begin(115200);
#endif
XOD_TRACE_FLN("\n\nProgram started");
xod::g_isSettingUp = true;
xod::runTransaction();
xod::g_isSettingUp = false;
}
void loop() {
xod::runTransaction();
}
/*=============================================================================
*
*
* Native node implementations
*
*
=============================================================================*/
namespace xod {
//-----------------------------------------------------------------------------
// xod/core/continuously implementation
//-----------------------------------------------------------------------------
namespace xod__core__continuously {
struct State {
};
struct Node {
State state;
TimeMs timeoutAt;
Logic output_TICK;
union {
struct {
bool isOutputDirty_TICK : 1;
bool isNodeDirty : 1;
};
DirtyFlags dirtyFlags;
};
};
struct output_TICK { };
template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<output_TICK> { using T = Logic; };
struct ContextObject {
Node* _node;
};
using Context = ContextObject*;
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
static_assert(always_false<PinT>::value,
"Invalid pin descriptor. Expected one of:" \
"" \
" output_TICK");
}
template<> Logic getValue<output_TICK>(Context ctx) {
return ctx->_node->output_TICK;
}
template<typename InputT> bool isInputDirty(Context ctx) {
static_assert(always_false<InputT>::value,
"Invalid input descriptor. Expected one of:" \
"");
return false;
}
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
static_assert(always_false<OutputT>::value,
"Invalid output descriptor. Expected one of:" \
" output_TICK");
}
template<> void emitValue<output_TICK>(Context ctx, Logic val) {
ctx->_node->output_TICK = val;
ctx->_node->isOutputDirty_TICK = true;
}
State* getState(Context ctx) {
return &ctx->_node->state;
}
void evaluate(Context ctx) {
emitValue<output_TICK>(ctx, 1);
setTimeout(ctx, 0);
}
} // namespace xod__core__continuously
//-----------------------------------------------------------------------------
// xod/gpio/analog-read implementation
//-----------------------------------------------------------------------------
namespace xod__gpio__analog_read {
struct State {
};
struct Node {
State state;
Number output_VAL;
Logic output_DONE;
Logic output_ERR;
union {
struct {
bool isOutputDirty_VAL : 1;
bool isOutputDirty_DONE : 1;
bool isOutputDirty_ERR : 1;
bool isNodeDirty : 1;
};
DirtyFlags dirtyFlags;
};
};
struct input_PORT { };
struct input_UPD { };
struct output_VAL { };
struct output_DONE { };
struct output_ERR { };
template<typename PinT> struct ValueType { using T = void; };
template<> struct ValueType<input_PORT> { using T = uint8_t; };
template<> struct ValueType<input_UPD> { using T = Logic; };
template<> struct ValueType<output_VAL> { using T = Number; };
template<> struct ValueType<output_DONE> { using T = Logic; };
template<> struct ValueType<output_ERR> { using T = Logic; };
struct ContextObject {
Node* _node;
uint8_t _input_PORT;
Logic _input_UPD;
bool _isInputDirty_UPD;
};
using Context = ContextObject*;
template<typename PinT> typename ValueType<PinT>::T getValue(Context ctx) {
static_assert(always_false<PinT>::value,
"Invalid pin descriptor. Expected one of:" \
" input_PORT input_UPD" \
" output_VAL output_DONE output_ERR");
}
template<> uint8_t getValue<input_PORT>(Context ctx) {
return ctx->_input_PORT;
}
template<> Logic getValue<input_UPD>(Context ctx) {
return ctx->_input_UPD;
}
template<> Number getValue<output_VAL>(Context ctx) {
return ctx->_node->output_VAL;
}
template<> Logic getValue<output_DONE>(Context ctx) {
return ctx->_node->output_DONE;
}
template<> Logic getValue<output_ERR>(Context ctx) {
return ctx->_node->output_ERR;
}
template<typename InputT> bool isInputDirty(Context ctx) {
static_assert(always_false<InputT>::value,
"Invalid input descriptor. Expected one of:" \
" input_UPD");
return false;
}
template<> bool isInputDirty<input_UPD>(Context ctx) {
return ctx->_isInputDirty_UPD;
}
template<typename OutputT> void emitValue(Context ctx, typename ValueType<OutputT>::T val) {
static_assert(always_false<OutputT>::value,
"Invalid output descriptor. Expected one of:" \
" output_VAL output_DONE output_ERR");
}
template<> void emitValue<output_VAL>(Context ctx, Number val) {
ctx->_node->output_VAL = val;
ctx->_node->isOutputDirty_VAL = true;
}
template<> void emitValue<output_DONE>(Context ctx, Logic val) {
ctx->_node->output_DONE = val;
ctx->_node->isOutputDirty_DONE = true;
}
...
This file has been truncated, please download it to see its full contents.
Comments