Bill Waggoner
Published © MIT

Modulo Temperature Probe Example

A full-featured thermometer with selectable display, color indicator, high/low recording, and thermal alarms.

BeginnerFull instructions provided1 hour894
Modulo Temperature Probe Example

Things used in this project

Story

Read more

Code

TemperatureProbeExample

Arduino
Main code
#include "Modulo.h"
#include "Wire.h"

/*
   Modulo Temerature Probe test sketch
   Copyright 2016 - William C Waggoner. All Right Reserved.
   Licensed under MIT License, see LICENSE.txt

   This little test exercizes the TemperatureProbeModulo and the DisplayModulo
   and will also use the KnobModulo if it is present.

   The Display will show the high and low temperatures as well as the current temperatures
   all in either Centigrade or Fahrenheit. The Display button are used to change the
   display format among a few options as described below:

   Button 0: Cycle the current temperature display through Both (F and C), F only, C only.

   Button 1: Cycle the High and Low temperature display through None, High, Low. The display
             scale follows the current temerature display or F when both scales are displayed.

   Button 2: Reset the High and Low temps to the current temp.

   Knob: The Knob, if present, displays a color related to the current temperature from BLUE
         for cold to RED for hot temperature based on a range from -18C to 50C.

         Pressing the knob begins a mode where the high and low temperature limits as well
         as the alarm (TBI) state can be changed. Each press moves to the next mode until
         it exits back to "normal" mode.
*/

/*
   A NOTE FROM THE AUTHOR

   This all started as a simple "I wonder how the Temperature Probe works?" and as I discovered
   more I wanted it to do more. What you find below is probably not the most efficient and is
   also probably not the "best" code. But I have learned a lot and I tried to use as many
   programming techniques as was appropriate. I hope you learn something yourself as you
   wander through my maze.

   I have added comments everywhere that I thought you might like a peek into "What was he
   thinking?"  I hope they help.

   Bill Waggoner - March 2016
*/

// Define debugU to display update counts as they increase
//#define debugU

// Define debugT to display the Hue for the Knob color
//#define debugT

// Define debugK to display knob status
//#define debugK

enum DisplayFC {dF = 1, dC = 2, dB = 3};
enum DisplayHL {dH, dL, dN };
enum MenuMode {MenuModeSetHigh, MenuModeSetLow, MenuModeSetAlarm};

// Devices
TemperatureProbeModulo probe;
DisplayModulo display;
KnobModulo knob;
bool haveProbe;
bool haveKnob;

// Used for debugging, meaningless otherwise
unsigned int updCount;

// Keep track of what wer are displaying
DisplayFC dispFC;
DisplayHL dispHL;
bool inMenu;
MenuMode menuMode;

// Knob related things
float setHue;
ModuloStatus knobStatus = ModuloStatusOff;

// [0] = F, [1] = C
// Low and High temps for knob colors, in C
int tempLowLimit = -18;
int tempHighLimit = 50;
float tempHigh[2];
float tempLow[2];
bool alarmEnabled;

// Convenience because I couldn't remember which was which
const int fIndex = 0;
const int cIndex = 1;

// Current conditions
float tempC;
float tempF;
int lastPosition;

// Callbacks and other internal functions
void probeChanged(TemperatureProbeModulo &p);
void buttonPressed(DisplayModulo &d, int b);
void knobPressed(KnobModulo &k);
void knobPositionChanged(KnobModulo &k);
void colorKnob(float t);
void resetHL();
void showMenu();
void nextMenu();

// Get around the lack of float capability in printf
// and lack of control in Print
// Formats a float like "% 5.1f"
void formatFloat(char *str, float t);

void setup() {
  // put your setup code here, to run once:

  // MUST call Modulo.setup()
  Modulo.setup();

  // Set initial states
  dispFC = dB;
  dispHL = dN;
  inMenu = false;
  alarmEnabled = false;
  updCount = 0;

  // Establish the callbacks
  probe.setTemperatureChangeCallback(probeChanged);
  display.setButtonPressCallback(buttonPressed);
  knob.setButtonPressCallback(knobPressed);
  knob.setPositionChangeCallback(knobPositionChanged);
  haveProbe = probe.getDeviceID() != 0xFFFFU;
  haveKnob = knob.getDeviceID() != 0xFFFFU;

  // and get initial states from the devices
  resetHL();
  probeChanged(probe);
}

void loop() {
  // put your main code here, to run repeatedly:

  // We just run the Modulo loop code
  Modulo.loop();
}

// Respond to the buttons on the Display
void buttonPressed(DisplayModulo & d, int b) {
  switch (b) {
    // Left button - cycle temperature display
    case 0:
      switch (dispFC) {
        case dF:
          dispFC = dC;
          break;

        case dC:
          dispFC = dB;
          break;

        case dB:
          dispFC = dF;
          break;
      }
      break;

    // Middle button - Display High or Low temps (or none)
    case 1:
      switch (dispHL) {
        case dN:
          dispHL = dH;
          break;

        case dH:
          dispHL = dL;
          break;

        case dL:
          dispHL = dN;
          break;

        default:
          dispHL = dN;
          break;
      }
      break;

    // Right button - Reset High and Low temps to current temps
    case 2:
      resetHL();
      break;
  }

  probeChanged(probe);
}

// Show the High/Low/Alarm menu
void showMenu() {
  display.setTextSize(2);
  String line[3];

  switch (menuMode) {
    case MenuModeSetHigh:
      line[0] = "Set High";
      line[2] = String(String(" ") + String(tempHighLimit) + String("C"));
      break;

    case MenuModeSetLow:
      line[0] = "Set Low";
      line[2] = String(String(" ") + String(tempLowLimit) + String("C"));
      break;

    case MenuModeSetAlarm:
      line[0] = "Set Alrm";
      if (alarmEnabled) {
        line[2] = "-ON";
      } else {
        line[2] = "-OFF";
      }
      break;
  }

  line[1] = String();

  for (int i = 0; i < 3; i++) {
    display.println(line[i]);
  }
}

// Select the next menu mode or exit menu mode
void nextMenu() {

  if (inMenu) {
    switch (menuMode) {
      case MenuModeSetHigh:
        menuMode = MenuModeSetLow;
        break;

      case MenuModeSetLow:
        menuMode = MenuModeSetAlarm;
        break;

      case MenuModeSetAlarm:
        inMenu = false;
        knobStatus = ModuloStatusOff;
        break;
    }
  } else {
    inMenu = true;
    knobStatus = ModuloStatusBlinking;
    menuMode = MenuModeSetHigh;
  }

  updateDisplay();
}

// Update the display
void updateDisplay() {
  int dindex;
  char tChar;
  char str[16];

  display.clear();

  if (inMenu) { // Use showMenu when we are in menu mode
    showMenu();
  } else {
    // Choose some display variables
    switch (dispFC) {
      case dF:
        dindex = fIndex;
        tChar = 'F';
        break;

      case dC:
        dindex = cIndex;
        tChar = 'C';
        break;

      case dB:
        dindex = fIndex;
        tChar = 'F';
        break;
    }

    updCount++;
    display.setTextSize(2);
    display.println(" =Temp=");

// Display debug info or the High/Low temps
#if defined( debugU )
    sprintf(str, "%8u", updCount);
    display.println(str);
#elif defined( debugT )
    display.println(setHue, 5);
#elif defined( debugK )
    display.print(knobStatus);
    display.print(":");
    display.println(knob.getPosition());
#else
    switch (dispHL) {
      case dN:
        display.println("");
        break;

      case dH:
        display.print("H=");
        formatFloat(str, tempHigh[dindex]);
        display.print(str);
        display.println(tChar);
        break;

      case dL:
        display.print("L=");
        formatFloat(str, tempLow[dindex]);
        display.print(str);
        display.println(tChar);
        break;

      default:
        display.println("??");
        break;
    }
#endif

    // Show a blank line if we aren't showing both temps
    if (dispFC != dB) {
      display.println("");
    }

    // Fahrenheit
    if (dispFC == dF || dispFC == dB) {
      formatFloat(str, tempF);
      display.print(str); display.println("F");
    }

    // Celcius
    if (dispFC == dC || dispFC == dB) {
      formatFloat(str, tempC);
      display.print(str); display.println("C");
    }
  }

  display.refresh();
}

// Callback when the temperature changes
// Also called internally to get current temperature
void probeChanged(TemperatureProbeModulo & p) {
  tempC = p.getTemperatureC();
  tempF = p.getTemperatureF();

  // Record high and low
  tempHigh[fIndex] = max(tempHigh[fIndex], tempF);
  tempLow[fIndex] = min(tempLow[fIndex], tempF);
  tempHigh[cIndex] = max(tempHigh[cIndex], tempC);
  tempLow[cIndex] = min(tempLow[cIndex], tempC);

  updateDisplay();

  colorKnob(tempC);
}

// Formats float like sprintf % 5.1f
void formatFloat(char *str, float t) {
  int pos;
  int dig;
  int frac;

  // Get the integer part
  dig = (int)t;

  // Get the fraction (one digit only needed)
  frac = (int)((t - (float)dig) * 10);
  // Make sure it's positive
  frac = abs(frac);      // Arduino docs say not to use other funtions inside abs()

  // Print the integer
  sprintf(str, "% 3d", dig);

  // Find the end
  pos = 0;

  while (str[pos] != (char)0) {
    pos++;
  }

  // Add in the period
  str[pos++] = '.';

  // Print the fracion digit
  sprintf(&str[pos], "%01d", frac);
}

// Set the knob color if we have a knob
void colorKnob(float t) {
  const float setSat = 1.0;
  const float setVal = 1.0;
  int cTemp;  // Constrained and normalized current temp
  int cHue;   // cTemp to Hue (180-0)

  if (haveKnob) {
    // Constrain Temp to High and Low limits (defined above)
    cTemp = min(tempHighLimit, max(tempLowLimit, t));

    // Convert temp to degrees (low temp = blue(180), high temp = red(0))
    cHue = map(cTemp, tempLowLimit, tempHighLimit, 180, 0);

    // Hue is 0.0 to 1.0 (0 to 360 degrees)
    setHue = (float)cHue / 360.0;

    // Change the Hue of the knob
    knob.setHSV(setHue, setSat, setVal);
  }
}

// Set recorded high and low temps to the current temp
void resetHL() {
  tempHigh[fIndex] = -100.0;
  tempLow[fIndex] = 200.0;
  tempHigh[cIndex] = -100.0;
  tempLow[cIndex] = 200.0;
}

// Knob pressed callback
void knobPressed(KnobModulo &k) {
  nextMenu();

  Modulo.setStatus(k.getDeviceID(), knobStatus);
}

// Knob position callback
void knobPositionChanged(KnobModulo &k) {
  if (inMenu) {
    int thisPosition = knob.getPosition();
    int incrDecr = 0;

    if (thisPosition > lastPosition) {
      incrDecr = 1;
    } else {
      incrDecr = -1;
    }

    switch (menuMode) {
      case MenuModeSetHigh:
        tempHighLimit += incrDecr;
        break;

      case MenuModeSetLow:
        tempLowLimit += incrDecr;
        break;

      case MenuModeSetAlarm:
        alarmEnabled = !alarmEnabled;
        break;
    }

    lastPosition = thisPosition;
    updateDisplay();
  } else {
#ifdef debugK
    updateDisplay();
#endif
    lastPosition = knob.getPosition();
  }
}

// That's it!

Credits

Bill Waggoner

Bill Waggoner

1 project • 1 follower
Aging computer geek. Programming since 1965, really! I've seen it all.

Comments