SapFlow Probe
A low-cost HRM probe for measuring a tree's water consumption
measure.cpp File Reference
#include "measure.h"
#include "lora.h"

Go to the source code of this file.

Functions

int sample_timer (struct pt *pt)
 Controls timing of the measurements. More...
 
int measure (struct pt *pt)
 Captures a measurement from the three probes. More...
 
int baseline (struct pt *pt)
 Calculates baseline temperature. More...
 
int delta (struct pt *pt)
 Calculates temperature delta and sapflow. More...
 

Variables

float maxtemp
 

Function Documentation

◆ baseline()

int baseline ( struct pt *  pt = &baseline_thd)

Calculates baseline temperature.

This is a protothread that averages 10 samples of data to determine the "initial" or "baseline" temperature of the tree. It should be used before the heater is turned on.

Parameters
ptA pointer to the protothread control structure. The default parameter is correct. Don't forget to initialize the control structure in setup().
Returns
the status of the protothread (Waiting, yeilded, exited, or ended)

Definition at line 65 of file measure.cpp.

66 {
67  PT_BEGIN(pt);MARK();
68  Serial.print("Initializing baseline thread... ");
69  // Declare persistant variable for this thread
70  static int i;
71  // Initialize the baseline (reference) temperature
72  reference.upper = 0;
73  reference.lower = 0;
74  maxtemp = -300; //< Any temperature should be greater than this.
75  Serial.println("Done");
76  // Take an average over the first 10 seconds
77  for(i = 0; i < 10; ++i){ MARK();
78  PT_WAIT_UNTIL(pt, sample_trigger); MARK();
79  PT_WAIT_WHILE(pt, sample_trigger); MARK();
82  }; MARK();
83  reference.upper /= i;
84  reference.lower /= i; MARK();
85  cout<<"Baseline: "<<reference.upper<<", "<<reference.lower<<endl; MARK();
86  PT_END(pt);
87 }

◆ delta()

int delta ( struct pt *  pt = &delta_thd)

Calculates temperature delta and sapflow.

This is a protothread that calculates the sap flow by averaging measurements over 40 seconds. It also calls other functions to get the weight, package the data, log to an SD card, and send the information over LoRa.

Parameters
ptA pointer to the protothread control structure. The default parameter is correct. Don't forget to initialize the control structure in setup().
Returns
the status of the protothread (Waiting, yeilded, exited, or ended)

We compute sapflow using the following formula:: sapflow = k / x * log(v1 / v2) / 3600

  • k is an empirical constant
  • x is the distance between the probes
  • v1 is temperature increase of the upper probe from its baseline temperature
  • v2 is the temperature increase of the lower probe from its baseline temperature
  • 3600 is the number of seconds in an hour

In order to get a smoother result, we are takng the average of this calculation over a period of 40 seconds. Burges et. al. (2001) suggests that this value should converge.

Definition at line 91 of file measure.cpp.

92 {
93  PT_BEGIN(pt);MARK();
94  Serial.print("Initializing delta thread... ");
95  // Declare persistent variables for this thread
96  static int i;
97  static float flow;
98  // Initialize the flow value
99  flow = 0;
100  Serial.println("Done");
112  for(i = 0; i < 40; ++i ){ MARK();
113  PT_WAIT_UNTIL(pt, sample_trigger); MARK();
114  PT_WAIT_WHILE(pt, sample_trigger); MARK();
115  // Ratio of upper delta over lower delta
116  float udelt = latest.upper - reference.upper;
117  float ldelt = latest.lower - reference.lower;
118  cout << "Delta: " << udelt <<", " << ldelt << endl; MARK();
119  // Take the average before the log, since this ratio should converge
120  flow += udelt / ldelt;
121  }; MARK();
122  cout<<"Finished measurements."<<endl; MARK();
123  flow /= i; MARK();
124  // Complete the rest of the equation
125  flow = log(flow) * (3600.*2e-6/7e-3); MARK();
126  // Print the result
127  cout<<"Flow is "<<flow<<endl; MARK();
128  // Write the sapflow to the file.
129  ofstream sapfile = ofstream("demo.csv", ios::out | ios::app); MARK();
130  char * time = rtc_ds.now().text(); MARK();
131  cout << time << ", "; MARK();
132  cout << reference.upper << ", "; MARK();
133  cout << reference.lower << ", "; MARK();
134  cout << flow << ", " << endl; MARK();
135  sapfile << time << ", "<< reference.upper << ", "; MARK();
136  sapfile << reference.lower << ", "<< flow << ", "<< endl; MARK();
137  sapfile.close(); MARK();
138  // Send the data over LoRa
139  lora_init(); MARK();
140  build_msg(flow, "0", reference.upper, maxtemp); MARK();
141  send_msg(); MARK();
142 
143  PT_END(pt);
144 }

◆ measure()

int measure ( struct pt *  pt = &measure_thd)

Captures a measurement from the three probes.

This is a protothread that reads the temperature from three RTD amplifiers connected to the probes in the tree. It stores the result in the global variable "latest", and logs to the SD card.

Parameters
ptA pointer to the protothread control structure. The default parameter is correct. Don't forget to initialize the control structure in setup().
Returns
the status of the protothread (Waiting, yeilded, exited, or ended)

Definition at line 22 of file measure.cpp.

23 {
24  PT_BEGIN(pt);
25  Serial.print("Initializing measurement thread... ");
26  static Adafruit_MAX31865 upper_rtd = Adafruit_MAX31865(UPPER_CS);
27  static Adafruit_MAX31865 lower_rtd = Adafruit_MAX31865(LOWER_CS);
28  static Adafruit_MAX31865 heater_rtd = Adafruit_MAX31865(HEATER_CS);
29  upper_rtd.begin(MAX31865_2WIRE);
30  lower_rtd.begin(MAX31865_2WIRE);
31  heater_rtd.begin(MAX31865_2WIRE);
32  Serial.println("Done");
33  PT_YIELD(pt);
34  while(1)
35  {
36  // Wait for next sample trigger
37  PT_WAIT_UNTIL(pt, sample_trigger );
38  PT_WAIT_WHILE(pt, sample_trigger);
39  // Get the latest temperature
40  MARK();
41  latest.upper = upper_rtd.temperature(Rnom, Rref); MARK();
42  latest.lower = lower_rtd.temperature(Rnom, Rref); MARK();
43  latest.heater = heater_rtd.temperature(Rnom, Rref); MARK();
44  maxtemp = max(latest.upper, maxtemp); MARK();
45  maxtemp = max(latest.lower, maxtemp); MARK();
46  maxtemp = max(latest.heater, maxtemp); MARK();
47  DateTime t = rtc_ds.now(); MARK();
48  // Print to Serial terminal
49  cout << "Upper: " << latest.upper << " Lower: "; MARK();
50  cout << latest.lower << " Heater: " <<latest.heater; MARK();
51  cout << " Time: " << t.text() << endl; MARK();
52  // Save calculated sapflow
53  ofstream logfile = ofstream("demo_log.csv",
54  ios::out | ios::app ); MARK();
55  logfile << t.text() << ", "; MARK();
56  logfile << setw(6) << latest.upper << ", "; MARK();
57  logfile << setw(6) << latest.lower << ", "; MARK();
58  logfile << setw(6) << latest.heater << endl; MARK();
59  logfile.close(); MARK();//< Ensure the file is closed
60  }
61  PT_END(pt);
62 }

◆ sample_timer()

int sample_timer ( struct pt *  pt = &sample_timer_thd)

Controls timing of the measurements.

This is a protothread that creates a pulse every second on the global variable sample_trigger in order to sychronize sample-based processing. Protothreads that sample or process sampled data can latch this signal using PT_WAIT_UNTIL(pt, sample_trigger); PT_WAIT_WHILE(pt, sample_trigger); to synchronize execution without impacting the sample frequency.

Parameters
ptA pointer to the protothread control structure. The default parameter is correct. Don't forget to initialize the control structure in setup().
Returns
the status of the protothread (Waiting, yeilded, exited, or ended)

Definition at line 8 of file measure.cpp.

9 {
10  PT_BEGIN(pt);
11  Serial.println("Initialized timer thread");
12  while(1)
13  {
14  sample_trigger = true;
15  PT_YIELD(pt); //< Give everyone a chance to see the trigger
16  sample_trigger = false; //< Clear the trigger
17  PT_TIMER_DELAY(pt,1000); //< Sample every 1000ms
18  }
19  PT_END(pt);
20 }
temperature::heater
float heater
Temperature (Celcius) at the heater probe.
Definition: measure.h:23
send_msg
void send_msg(void)
Sends a LoRa packet to the base station.
Definition: lora.cpp:83
rtc_ds
static RTC_DS3231 rtc_ds
Instance of our real-time clock.
Definition: schedule.h:11
temperature::upper
float upper
Temperature (Celcius) at the upper probe.
Definition: measure.h:21
sample_trigger
static bool sample_trigger
global flag for synchronizing live data processing. Set by sample_timer()
Definition: measure.h:10
lora_init
void lora_init(void)
Initialize the LoRa radio.
Definition: lora.cpp:17
HEATER_CS
@ HEATER_CS
Heater RTD amplifier chip select. Output.
Definition: pinout.h:27
reference
static struct temperature reference
The baseline temperature reading, computed by the baseline() protothread.
Definition: measure.h:27
temperature::lower
float lower
Temperature (Celcius) at the lower probe.
Definition: measure.h:22
cout
static ArduinoOutStream cout(Serial)
Allows use of streams to print to Serial via cout.
Rref
#define Rref
Nominal reference resistor in ohms.
Definition: measure.h:13
MARK
#define MARK()
This macro records the line number and function name.
Definition: debug.h:72
UPPER_CS
@ UPPER_CS
Upper RTD amplifier chip select. Output.
Definition: pinout.h:25
Rnom
#define Rnom
Nominal RTD resistance in ohms.
Definition: measure.h:12
latest
static struct temperature latest
The most recent temperature reading, measured by the measure() protothread.
Definition: measure.h:26
LOWER_CS
@ LOWER_CS
Lower RTD amplifier chip select. Output.
Definition: pinout.h:26
build_msg
void build_msg(float flow, char *weight, float temp, float maxtemp)
Builds a JSON string to send over LoRa.
Definition: lora.cpp:58