Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
|
I live and work south of Seattle, which overall is nice and cool. But something about my office (top floor, angle to afternoon sun, etc.) sometimes causes a temperature spike between midday and evening. If I remember to turn several fans on ahead of time, I can stay reasonably comfortable, but I often have my head in some code, and don't realize it's getting warm until it's too late...
I need something to kick my fans on automatically when it starts to get warm... I need a personal thermostat!
Getting a temperature sensor, relay, and Arduino to tie them together was easy enough. But I wanted a nice user interface, too, with flexibility to set the hysteresis (temperature gap between turning a relay on and turning it back off).
This is where Modulo made my personal thermostat into a really nice project. At first I tried to squeeze all the stuff I wanted on the display into a single Modulo Display, but at some point I realized I could just go with two instead, and spread out my display items, use bigger text, and so on. I also wanted a nice, big knob to twist to tweak the settings -- the Modulo Knob is perfect for this.
Tweaking the thermostat settings is simple. (In fact, it's an example of being easier to just do it then to explain it in words.) The highlighted (amber) parameter is what the knob changes; twist it to adjust the value. Push the knob button to select the next parameter; it sequences through Mode, On temp, and Off temp. There are four Modes (each with a unique knob color): Cool (blue), Heat (red), On (white), and Off (black). When adjusting the On temp, you'll notice that the Off temp changes, too, keeping the same hysteresis (difference between On and Off temps). But when adjusting the Off temp, it changes independently of the On temp; this effectively lets you change the hysteresis.
The hardware part of this project is relatively simple. There's plenty of help on the web for using the DHT22 temperature/humidity sensor; or you could substitute another sensor. The IoT Power Relay is incredibly easy to hook up; or (again) you could do your own relay solution.
I'm a software guy, so that's where most of my effort went on this project. As it stands now, the software works, but I'll no doubt continue to refine and improve it. I've tried to keep things loose and flexible; it should be relatively easy to change things: go to a single display, use a joystick/hat or the display buttons instead of the knob, etc. Also, note that C++ is not my primary language (I'm more fluent in Java or Ruby).
Note that you'll need the following Arduino libraries: Adafruit DHT Unified, Adafruit Unified Sensor, DHT sensor library, LinkedList (https://github.com/ivanseidel/LinkedList), and of course, the Modulo library.
/*
PersonalThermostat.ino - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "Modulo.h"
#include "Wire.h"
//#include "Mode.C"
#include "Sensor.C"
#include "Relay.C"
#include "UI.C"
#include "UiItem.C"
#include "UiColor.C"
const int SENSOR_PIN = 0;
const int RELAY_PIN = 4;
const unsigned long SENSOR_UPDATE_INTERVAL_MILLIS = 2000;
const unsigned long DISPLAY_UPDATE_INTERVAL_MILLIS = 50;
const float DEFAULT_TARGET_TEMP = 72.0;
const float DEFAULT_HYSTERESIS = 1.0;
const UiColor COLOR_TEMP = UiColor(1.0, 0.9, 0.5);
const UiColor COLOR_HUMID = UiColor(0.7, 1.0, 0.9);
const UiColor COLOR_PARAM = UiColor(0.5, 0.5, 0.7);
const UiColor COLOR_FOCUS = UiColor(0.9, 0.7, 0.2);
//const Mode::Enum DEFAULT_MODE = Mode::Enum::COOL;
//Mode::Enum mode;
enum Mode { OFF, COOL, HEAT, ON };
const Mode DEFAULT_MODE = Mode::COOL;
Mode mode;
enum ParameterFocus { MODE, TRIP_ON, TRIP_OFF };
const ParameterFocus DEFAULT_PARAMETER_FOCUS = ParameterFocus::TRIP_ON;
ParameterFocus parameterFocus = DEFAULT_PARAMETER_FOCUS;
float targetTemp = DEFAULT_TARGET_TEMP;
float hysteresis = DEFAULT_HYSTERESIS;
Sensor sensor(SENSOR_PIN);
Relay relay(RELAY_PIN);
UI ui;
UiItem* tempItem;
UiItem* humidItem;
UiItem* tripOnItem;
UiItem* tripOnSignItem;
UiItem* tripOffItem;
UiItem* tripOffSignItem;
UiItem* modeItem;
int knobPositionPrevious;
unsigned long last_sensor_update_millis;
long time_since_last_sensor_update;
float getTripOnTemp()
{
return targetTemp;
}
float getTripOffTemp()
{
float result = targetTemp - hysteresis;
if (mode == Mode::HEAT)
{
result = targetTemp + hysteresis;
}
return result;
}
void updateRelay()
{
switch (mode)
{
case Mode::OFF:
if (relay.isOn())
{
relay.set(false);
}
break;
case Mode::COOL:
if (relay.isOn())
{
if (sensor.getTemperatureF() < getTripOffTemp())
{
relay.set(false);
}
}
else // relay is off
{
if (sensor.getTemperatureF() >= getTripOnTemp())
{
relay.set(true);
}
}
break;
case Mode::HEAT:
if (relay.isOn())
{
if (sensor.getTemperatureF() > getTripOffTemp())
{
relay.set(false);
}
}
else // relay is off
{
if (sensor.getTemperatureF() <= getTripOnTemp())
{
relay.set(true);
}
}
break;
case Mode::ON:
if (!relay.isOn())
{
relay.set(true);
}
break;
}
ui.setIndicator(2, relay.isOn());
}
String modeToString(Mode mode_)
{
String result;
switch (mode_)
{
case Mode::OFF: result = String("Off"); break;
case Mode::COOL: result = String("Cool"); break;
case Mode::HEAT: result = String("Heat"); break;
case Mode::ON: result = String("On"); break;
}
return result;
}
Mode nextMode(Mode mode_)
{
Mode result;
switch (mode_)
{
case Mode::OFF: result = Mode::COOL; break;
case Mode::COOL: result = Mode::HEAT; break;
case Mode::HEAT: result = Mode::ON; break;
case Mode::ON: result = Mode::OFF; break;
}
return result;
}
Mode previousMode(Mode mode_)
{
Mode result;
switch (mode_)
{
case Mode::OFF: result = Mode::ON; break;
case Mode::COOL: result = Mode::OFF; break;
case Mode::HEAT: result = Mode::COOL; break;
case Mode::ON: result = Mode::HEAT; break;
}
return result;
}
ParameterFocus nextParameterFocus(ParameterFocus parameterFocus_)
{
ParameterFocus result;
switch(parameterFocus_)
{
case ParameterFocus::MODE: result = ParameterFocus::TRIP_ON; break;
case ParameterFocus::TRIP_ON: result = ParameterFocus::TRIP_OFF; break;
case ParameterFocus::TRIP_OFF: result = ParameterFocus::MODE; break;
}
return result;
}
void updateUIforMode()
{
modeItem->setText(modeToString(mode));
switch (mode)
{
case Mode::OFF:
ui.setKnobColor(UiColor::BLACK);
tripOnSignItem->setVisible(false);
tripOnItem->setVisible(false);
tripOffSignItem->setVisible(false);
tripOffItem->setVisible(false);
updateTripItems();
break;
case Mode::COOL:
ui.setKnobColor(UiColor::BLUE);
tripOnSignItem->setVisible(true);
tripOnSignItem->setText(String(">= "));
tripOnItem->setVisible(true);
tripOffSignItem->setVisible(true);
tripOffSignItem->setText(String("< "));
tripOffItem->setVisible(true);
updateTripItems();
break;
case Mode::HEAT:
ui.setKnobColor(UiColor::RED);
tripOnSignItem->setVisible(true);
tripOnSignItem->setText(String("<= "));
tripOnItem->setVisible(true);
tripOffSignItem->setVisible(true);
tripOffSignItem->setText(String("> "));
tripOffItem->setVisible(true);
updateTripItems();
break;
case Mode::ON:
ui.setKnobColor(UiColor::WHITE);
tripOnSignItem->setVisible(false);
tripOnItem->setVisible(false);
tripOffSignItem->setVisible(false);
tripOffItem->setVisible(false);
updateTripItems();
break;
}
}
void setMode(Mode mode_)
{
mode = mode_;
updateUIforMode();
}
void onKnobButtonRelease(KnobModulo& knob_)
{
parameterFocus = nextParameterFocus(parameterFocus);
}
void updateTripItems()
{
tripOnItem->setText(String(getTripOnTemp(), 1));
tripOffItem->setText(String(getTripOffTemp(), 1));
}
void onKnobPositionChange(KnobModulo& knob_)
{
int knobPositionCurrent = knob_.getPosition();
int knobPositionDelta = knobPositionCurrent - knobPositionPrevious;
knobPositionPrevious = knobPositionCurrent;
switch (parameterFocus)
{
case ParameterFocus::MODE:
if (knobPositionDelta < 0)
{
setMode(previousMode(mode));
}
else
{
setMode(nextMode(mode));
}
break;
case ParameterFocus::TRIP_ON:
if (mode == Mode::OFF || mode == Mode::ON) return;
targetTemp += (knobPositionDelta / 10.0);
updateTripItems();
break;
case ParameterFocus::TRIP_OFF:
if (mode == Mode::OFF || mode == Mode::ON) return;
if (mode == Mode::COOL)
{
hysteresis -= (knobPositionDelta / 10.0);
}
else if (mode == Mode::HEAT)
{
hysteresis += (knobPositionDelta / 10.0);
}
if (hysteresis < 0.1) hysteresis = 0.1;
updateTripItems();
break;
}
}
void initUI()
{
ui.initialize();
ui.item(1)->text("Temperature ")->position(0, 0) ->color(COLOR_TEMP);
tempItem = ui.item(1)->text("(temp)") ->position(20, 13)->textSize(2);
ui.item(1)->text("F");
ui.item(1)->text("Humidity ")->position(0, 32) ->color(COLOR_HUMID);
humidItem = ui.item(1)->text("(humid)") ->position(20, 45)->textSize(2);
ui.item(1)->text("%");
ui.item(2)->text("Mode ") ->position(0, 0)->color(COLOR_PARAM);
modeItem = ui.item(2)->text("(mode)")->textSize(2);
ui.item(2)->text("On ")->position(0, 20)->color(COLOR_PARAM);
tripOnSignItem = ui.item(2)->text("(onSign)");
tripOnItem = ui.item(2)->text("(onTemp)")->textSize(2);
ui.item(2)->text("F");
ui.item(2)->text("Off ") ->position(0, 40)->color(COLOR_PARAM);
tripOffSignItem = ui.item(2)->text("(offSign)");
tripOffItem = ui.item(2)->text("(offTemp)")->textSize(2);
ui.item(2)->text("F");
tempItem->setText(String(sensor.getTemperatureF(), 1));
humidItem->setText(String(sensor.getRHumidity(), 1));
tripOnItem->setText(String(getTripOnTemp(), 1));
tripOffItem->setText(String(getTripOffTemp(), 1));
updateUIforMode();
ui.setKnobColor(COLOR_FOCUS);
ui.setKnobButtonReleaseCallback(onKnobButtonRelease);
ui.setKnobPositionChangeCallback(onKnobPositionChange);
}
void updateUI()
{
UiColor color;
color = COLOR_PARAM;
if (parameterFocus == ParameterFocus::MODE) color = COLOR_FOCUS;
modeItem->setColor(color);
color = COLOR_PARAM;
if (parameterFocus == ParameterFocus::TRIP_ON) color = COLOR_FOCUS;
tripOnItem->setColor(color);
color = COLOR_PARAM;
if (parameterFocus == ParameterFocus::TRIP_OFF) color = COLOR_FOCUS;
tripOffItem->setColor(color);
ui.update();
}
void updateSensor()
{
ui.setIndicator(1, true);
sensor.update();
last_sensor_update_millis = millis();
ui.setIndicator(1, false);
tempItem->setText(String(sensor.getTemperatureF(), 1));
humidItem->setText(String(sensor.getRHumidity(), 1));
}
void setup()
{
delay(1000); // give bootloader a chance!
sensor.initialize();
relay.initialize();
initUI();
setMode(DEFAULT_MODE);
updateSensor();
updateRelay();
updateUI();
}
void loop()
{
time_since_last_sensor_update = millis() - last_sensor_update_millis;
if (time_since_last_sensor_update >= SENSOR_UPDATE_INTERVAL_MILLIS)
{
updateSensor();
updateRelay();
}
updateUI();
ui.loop();
delay(DISPLAY_UPDATE_INTERVAL_MILLIS);
}
/*
Relay.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
#include "Wire.h"
class Relay
{
public:
Relay(int pin_)
{
_pin = pin_;
_on = false;
}
void initialize()
{
pinMode(_pin, OUTPUT);
}
bool isOn()
{
return _on;
}
void set(bool on_)
{
if (on_)
{
digitalWrite(_pin, HIGH);
_on = true;
}
else
{
digitalWrite(_pin, LOW);
_on = false;
}
}
private:
int _pin;
bool _on;
}; // class Relay
/*
Sensor.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
const uint8_t DHT_TYPE = DHT22;
class Sensor
{
public:
Sensor(int pin_)
:
_dht(pin_, DHT_TYPE)
{
}
float getRHumidity()
{
return _humid;
}
float getTemperatureF()
{
return _tempF;
}
void initialize()
{
_dht.begin();
}
void update()
{
sensors_event_t event;
_dht.temperature().getEvent(&event);
float tempC = event.temperature;
if (isnan(tempC))
{
_tempF = tempC;
}
else
{
_tempF = (event.temperature * 1.8) + 32;
}
_dht.humidity().getEvent(&event);
_humid = event.relative_humidity;
}
private:
DHT_Unified _dht;
float _tempF;
float _humid;
}; // class Sensor
/*
UI.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
#include "Modulo.h"
#include "Wire.h"
#include <LinkedList.h>
#include "UiItem.C"
#include "UiColor.C"
#include "UiPosition.C"
class UI
{
public:
UI()
:
_display1(),
_display2(),
_knob()
{
}
UiItem* item(int n_)
{
UiItem* newItem = new UiItem();
if (n_ == 1)
{
_items1.add(newItem);
}
else if (n_ == 2)
{
_items2.add(newItem);
}
return newItem;
}
void setIndicator(int n_, bool on_)
{
if (n_ <= 0 || n_ > 3) return;
uint16_t deviceID;
switch(n_)
{
case 1: deviceID = _display1.getDeviceID(); break;
case 2: deviceID = _display2.getDeviceID(); break;
case 3: deviceID = _knob.getDeviceID(); break;
}
ModuloStatus status = ModuloStatus::ModuloStatusOff;
if (on_) status = ModuloStatus::ModuloStatusOn;
Modulo.setStatus(deviceID, status);
}
void setKnobColor(const UiColor& color)
{
_knob.setColor(color.getRed(), color.getGreen(), color.getBlue());
}
void setKnobPositionChangeCallback(void (*callback_)(KnobModulo&))
{
_knob.setPositionChangeCallback(callback_);
}
void setKnobButtonReleaseCallback(void (*callback_)(KnobModulo&))
{
_knob.setButtonReleaseCallback(callback_);
}
void drawItem(DisplayModulo& display_, UiItem* item_)
{
if (!item_->isVisible()) return;
UiPosition* pPosition = item_->getPosition();
if (pPosition->isSet())
{
display_.setCursor(pPosition->getX(), pPosition->getY());
}
UiColor* pColor = item_->getColor();
if (pColor->isSet())
{
display_.setTextColor(pColor->getRed(),
pColor->getGreen(),
pColor->getBlue());
}
display_.setTextSize(item_->getTextSize());
display_.print(item_->getText());
}
void initialize()
{
Modulo.setup();
for (int n = 1; n <= 3; n++)
{
for (int i = 1; i <= 3; i++)
{
setIndicator(i, true);
delay(100);
setIndicator(i, false);
}
}
}
void update()
{
updateDisplay(_display1, _items1);
updateDisplay(_display2, _items2);
}
void updateDisplay(DisplayModulo& display_, LinkedList<UiItem*>& items_)
{
display_.clear();
for (int i = 0; i < items_.size(); i++)
{
UiItem* e = items_.get(i);
drawItem(display_, e);
}
display_.refresh();
}
void loop()
{
Modulo.loop();
}
private:
DisplayModulo _display1;
DisplayModulo _display2;
KnobModulo _knob;
LinkedList<UiItem*> _items1;
LinkedList<UiItem*> _items2;
}; // end class UI
/*
UiColor.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
class UiColor
{
public:
UiColor(float red_, float green_, float blue_)
{
_set = true;
_red = red_;
_green = green_;
_blue = blue_;
}
UiColor()
{
_set = false;
_red = NAN;
_green = NAN;
_blue = NAN;
}
UiColor(const UiColor& color_)
{
_set = color_.isSet();
_red = color_.getRed();
_green = color_.getGreen();
_blue = color_.getBlue();
}
UiColor(UiColor* pColor_)
{
_set = pColor_->isSet();
_red = pColor_->getRed();
_green = pColor_->getGreen();
_blue = pColor_->getBlue();
}
float getRed() const { return _red; }
float getGreen() const { return _green; }
float getBlue() const { return _blue; }
bool isSet() const { return _set; }
static const UiColor NOT_SET;
static const UiColor BLACK;
static const UiColor WHITE;
static const UiColor RED;
static const UiColor GREEN;
static const UiColor BLUE;
private:
bool _set;
float _red;
float _green;
float _blue;
}; // end class UiColor
const UiColor UiColor::NOT_SET = new UiColor();
const UiColor UiColor::BLACK = new UiColor(0.0, 0.0, 0.0);
const UiColor UiColor::WHITE = new UiColor(1.0, 1.0, 1.0);
const UiColor UiColor::RED = new UiColor(1.0, 0.0, 0.0);
const UiColor UiColor::GREEN = new UiColor(0.0, 1.0, 0.0);
const UiColor UiColor::BLUE = new UiColor(0.0, 0.0, 1.0);
/*
UiItem.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
#include "UiColor.C"
#include "UiPosition.C"
class UiItem
{
public:
UiItem(const UiPosition& position_, const UiColor& color_, int textSize_, const String& text_)
{
_pPosition = new UiPosition(position_);
_pColor = new UiColor(color_);
_textSize = textSize_;
_pText = new String(text_);
_visible = true;
}
UiItem(const UiPosition& position_, const UiColor& color_, int textSize_, const char* charString_)
{
_pPosition = new UiPosition(position_);
_pColor = new UiColor(color_);
_textSize = textSize_;
_pText = new String(charString_);
_visible = true;
}
UiItem(const UiItem& other_)
{
_pPosition = new UiPosition(other_.getPosition());
_pColor = new UiColor(other_.getColor());
_textSize = other_.getTextSize();
_pText = new String(other_.getText());
_visible = other_.isVisible();
}
UiItem()
{
_pPosition = new UiPosition(DEFAULT_POSITION);
_pColor = new UiColor(DEFAULT_COLOR);
_textSize = DEFAULT_TEXTSIZE;
_pText = new String(DEFAULT_TEXT);
_visible = DEFAULT_VISIBLE;
}
UiPosition* getPosition() const { return _pPosition; }
UiItem* position(const UiPosition& position_)
{
if (_pPosition) delete _pPosition;
_pPosition = new UiPosition(position_);
return this;
}
UiItem* position(int x_, int y_)
{
if (_pPosition) delete _pPosition;
_pPosition = new UiPosition(x_, y_);
return this;
}
void setPosition(const UiPosition& position_)
{
position(position_);
}
void setPosition(int x_, int y_)
{
position(x_, y_);
}
UiColor* getColor() const { return _pColor; }
UiItem* color(const UiColor& color_)
{
if (_pColor) delete _pColor;
_pColor = new UiColor(color_);
return this;
}
void setColor(const UiColor& color_)
{
color(color_);
}
int getTextSize() const { return _textSize; }
UiItem* textSize(int textSize_)
{
_textSize = textSize_;
return this;
}
void setTextSize(int textSize_)
{
textSize(textSize_);
}
String getText() const { return *_pText; }
UiItem* text(const String& text_)
{
if (_pText) delete _pText;
_pText = new String(text_);
return this;
}
void setText(const String& text_)
{
text(text_);
}
boolean isVisible() const { return _visible; }
UiItem* setVisible(bool visible_)
{
_visible = visible_;
return this;
}
private:
const UiPosition DEFAULT_POSITION = UiPosition::NOT_SET;
const UiColor DEFAULT_COLOR = UiColor::NOT_SET;
const int DEFAULT_TEXTSIZE = 1;
const String DEFAULT_TEXT = String("?");
const bool DEFAULT_VISIBLE = true;
UiPosition* _pPosition;
UiColor* _pColor;
int _textSize;
String* _pText;
bool _visible;
}; // end class UiItem
/*
UiPosition.C - Personal Thermostat project
Copyright (C) 2016 Andy Moore. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#pragma once
class UiPosition
{
public:
UiPosition(int x_, int y_)
{
_set = true;
_x = x_;
_y = y_;
}
UiPosition()
{
_set = false;
}
UiPosition(const UiPosition& position_)
{
_set = position_.isSet();
_x = position_.getX();
_y = position_.getY();
}
UiPosition(UiPosition* pPosition_)
{
_set = pPosition_->isSet();
_x = pPosition_->getX();
_y = pPosition_->getY();
}
int getX() const { return _x; }
int getY() const { return _y; }
bool isSet() const { return _set; }
static const UiPosition NOT_SET;
private:
bool _set;
int _x;
int _y;
}; // end class UiPosition
const UiPosition UiPosition::NOT_SET = new UiPosition();
Comments