Browse Source

Basic UI

master
Robin Thoni 4 years ago
parent
commit
a2d09ea925
Signed by: Robin THONI <robin@rthoni.com> GPG Key ID: 4E09DEF46B99E61E
5 changed files with 418 additions and 1 deletions
  1. 39
    0
      Boiler.h
  2. 1
    1
      CMakeLists.txt
  3. 100
    0
      JC_Button.cpp
  4. 111
    0
      JC_Button.h
  5. 167
    0
      main.ino

+ 39
- 0
Boiler.h View File

@@ -0,0 +1,39 @@
1
+//
2
+// Created by robin on 1/27/20.
3
+//
4
+
5
+#ifndef HEATER_BOILER_H
6
+#define HEATER_BOILER_H
7
+
8
+typedef char temp_t;
9
+
10
+enum UiStateEnum
11
+{
12
+    Hibernate,
13
+    Lighting,
14
+    WaterSetting,
15
+    HeaterSetting
16
+};
17
+
18
+struct UiState
19
+{
20
+    UiStateEnum state;
21
+    unsigned long lastOpMs;
22
+    unsigned modeSequenceIndex;
23
+};
24
+
25
+struct BoilerLineState
26
+{
27
+    temp_t current;
28
+    temp_t setting;
29
+    bool isActive;
30
+};
31
+
32
+struct BoilerState
33
+{
34
+    BoilerLineState water;
35
+    BoilerLineState heater;
36
+    UiState uiState;
37
+};
38
+
39
+#endif //HEATER_BOILER_H

+ 1
- 1
CMakeLists.txt View File

@@ -18,6 +18,6 @@ set(${CMAKE_PROJECT_NAME}_PORT /dev/ttyUSB3)
18 18
 
19 19
 
20 20
 enable_language(ASM)
21
-set(${CMAKE_PROJECT_NAME}_ALL_SRCS main.ino)
21
+set(${CMAKE_PROJECT_NAME}_ALL_SRCS main.ino JC_Button.cpp)
22 22
 set(${CMAKE_PROJECT_NAME}_SKETCH main.ino)
23 23
 generate_arduino_firmware(${CMAKE_PROJECT_NAME})

+ 100
- 0
JC_Button.cpp View File

@@ -0,0 +1,100 @@
1
+// Arduino Button Library
2
+// https://github.com/JChristensen/JC_Button
3
+// Copyright (C) 2018 by Jack Christensen and licensed under
4
+// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
5
+
6
+#include "JC_Button.h"
7
+
8
+/*----------------------------------------------------------------------*
9
+/ initialize a Button object and the pin it's connected to.             *
10
+/-----------------------------------------------------------------------*/
11
+void Button::begin()
12
+{
13
+    pinMode(m_pin, m_puEnable ? INPUT_PULLUP : INPUT);
14
+    m_state = digitalRead(m_pin);
15
+    if (m_invert) m_state = !m_state;
16
+    m_time = millis();
17
+    m_lastState = m_state;
18
+    m_changed = false;
19
+    m_lastChange = m_time;
20
+}
21
+
22
+/*----------------------------------------------------------------------*
23
+/ returns the state of the button, true if pressed, false if released.  *
24
+/ does debouncing, captures and maintains times, previous state, etc.   *
25
+/-----------------------------------------------------------------------*/
26
+bool Button::read()
27
+{
28
+    uint32_t ms = millis();
29
+    bool pinVal = digitalRead(m_pin);
30
+    if (m_invert) pinVal = !pinVal;
31
+    if (ms - m_lastChange < m_dbTime)
32
+    {
33
+        m_changed = false;
34
+    }
35
+    else
36
+    {
37
+        m_lastState = m_state;
38
+        m_state = pinVal;
39
+        m_changed = (m_state != m_lastState);
40
+        if (m_changed) m_lastChange = ms;
41
+    }
42
+    m_time = ms;
43
+    return m_state;
44
+}
45
+
46
+/*----------------------------------------------------------------------*
47
+ * isPressed() and isReleased() check the button state when it was last *
48
+ * read, and return false (0) or true (!=0) accordingly.                *
49
+ * These functions do not cause the button to be read.                  *
50
+ *----------------------------------------------------------------------*/
51
+bool Button::isPressed()
52
+{
53
+    return m_state;
54
+}
55
+
56
+bool Button::isReleased()
57
+{
58
+    return !m_state;
59
+}
60
+
61
+/*----------------------------------------------------------------------*
62
+ * wasPressed() and wasReleased() check the button state to see if it   *
63
+ * changed between the last two reads and return false (0) or           *
64
+ * true (!=0) accordingly.                                              *
65
+ * These functions do not cause the button to be read.                  *
66
+ *----------------------------------------------------------------------*/
67
+bool Button::wasPressed()
68
+{
69
+    return m_state && m_changed;
70
+}
71
+
72
+bool Button::wasReleased()
73
+{
74
+    return !m_state && m_changed;
75
+}
76
+
77
+/*----------------------------------------------------------------------*
78
+ * pressedFor(ms) and releasedFor(ms) check to see if the button is     *
79
+ * pressed (or released), and has been in that state for the specified  *
80
+ * time in milliseconds. Returns false (0) or true (!=0) accordingly.   *
81
+ * These functions do not cause the button to be read.                  *
82
+ *----------------------------------------------------------------------*/
83
+bool Button::pressedFor(uint32_t ms)
84
+{
85
+    return m_state && m_time - m_lastChange >= ms;
86
+}
87
+
88
+bool Button::releasedFor(uint32_t ms)
89
+{
90
+    return !m_state && m_time - m_lastChange >= ms;
91
+}
92
+
93
+/*----------------------------------------------------------------------*
94
+ * lastChange() returns the time the button last changed state,         *
95
+ * in milliseconds.                                                     *
96
+ *----------------------------------------------------------------------*/
97
+uint32_t Button::lastChange()
98
+{
99
+    return m_lastChange;
100
+}

+ 111
- 0
JC_Button.h View File

@@ -0,0 +1,111 @@
1
+// Arduino Button Library
2
+// https://github.com/JChristensen/JC_Button
3
+// Copyright (C) 2018 by Jack Christensen and licensed under
4
+// GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html
5
+
6
+#ifndef JC_BUTTON_H_INCLUDED
7
+#define JC_BUTTON_H_INCLUDED
8
+
9
+#include <Arduino.h>
10
+
11
+class Button
12
+{
13
+public:
14
+    // Button(pin, dbTime, puEnable, invert) instantiates a button object.
15
+    //
16
+    // Required parameter:
17
+    // pin      The Arduino pin the button is connected to
18
+    //
19
+    // Optional parameters:
20
+    // dbTime   Debounce time in milliseconds (default 25ms)
21
+    // puEnable true to enable the AVR internal pullup resistor (default true)
22
+    // invert   true to interpret a low logic level as pressed (default true)
23
+    Button(uint8_t pin, uint32_t dbTime=25, uint8_t puEnable=true, uint8_t invert=true)
24
+            : m_pin(pin), m_dbTime(dbTime), m_puEnable(puEnable), m_invert(invert) {}
25
+
26
+    // Initialize a Button object and the pin it's connected to
27
+    void begin();
28
+
29
+    // Returns the current debounced button state, true for pressed,
30
+    // false for released. Call this function frequently to ensure
31
+    // the sketch is responsive to user input.
32
+    bool read();
33
+
34
+    // Returns true if the button state was pressed at the last call to read().
35
+    // Does not cause the button to be read.
36
+    bool isPressed();
37
+
38
+    // Returns true if the button state was released at the last call to read().
39
+    // Does not cause the button to be read.
40
+    bool isReleased();
41
+
42
+    // Returns true if the button state at the last call to read() was pressed,
43
+    // and this was a change since the previous read.
44
+    bool wasPressed();
45
+
46
+    // Returns true if the button state at the last call to read() was released,
47
+    // and this was a change since the previous read.
48
+    bool wasReleased();
49
+
50
+    // Returns true if the button state at the last call to read() was pressed,
51
+    // and has been in that state for at least the given number of milliseconds.
52
+    bool pressedFor(uint32_t ms);
53
+
54
+    // Returns true if the button state at the last call to read() was released,
55
+    // and has been in that state for at least the given number of milliseconds.
56
+    bool releasedFor(uint32_t ms);
57
+
58
+    // Returns the time in milliseconds (from millis) that the button last
59
+    // changed state.
60
+    uint32_t lastChange();
61
+
62
+private:
63
+    uint8_t m_pin;          // arduino pin number connected to button
64
+    uint32_t m_dbTime;      // debounce time (ms)
65
+    bool m_puEnable;        // internal pullup resistor enabled
66
+    bool m_invert;          // if true, interpret logic low as pressed, else interpret logic high as pressed
67
+    bool m_state;           // current button state, true=pressed
68
+    bool m_lastState;       // previous button state
69
+    bool m_changed;         // state changed since last read
70
+    uint32_t m_time;        // time of current state (ms from millis)
71
+    uint32_t m_lastChange;  // time of last state change (ms)
72
+};
73
+
74
+// a derived class for a "push-on, push-off" (toggle) type button.
75
+// initial state can be given, default is off (false).
76
+class ToggleButton : public Button
77
+{
78
+public:
79
+
80
+    // constructor is similar to Button, but includes the initial state for the toggle.
81
+    ToggleButton(uint8_t pin, bool initialState=false, uint32_t dbTime=25, uint8_t puEnable=true, uint8_t invert=true)
82
+            : Button(pin, dbTime, puEnable, invert), m_toggleState(initialState) {}
83
+
84
+    // read the button and return its state.
85
+    // should be called frequently.
86
+    bool read()
87
+    {
88
+        Button::read();
89
+        if (wasPressed())
90
+        {
91
+            m_toggleState = !m_toggleState;
92
+            m_changed = true;
93
+        }
94
+        else
95
+        {
96
+            m_changed = false;
97
+        }
98
+        return m_toggleState;
99
+    }
100
+
101
+    // has the state changed?
102
+    bool changed() {return m_changed;}
103
+
104
+    // return the current state
105
+    bool toggleState() {return m_toggleState;}
106
+
107
+private:
108
+    bool m_toggleState;
109
+    bool m_changed;
110
+};
111
+#endif

+ 167
- 0
main.ino View File

@@ -1,8 +1,175 @@
1
+#include <LiquidCrystal.h>
2
+#include <JC_Button.h>
3
+#include "Boiler.h"
4
+
5
+#define HIBERNATE_DELAY 5000
6
+#define TEMP_TRIGGER 10
7
+
8
+#define PIN_BTN_MODE 11
9
+#define PIN_BTN_MINUS 10
10
+#define PIN_BTN_PLUS 9
11
+#define PIN_LCD_LED 8
12
+#define PIN_LCD_RS 7
13
+#define PIN_LCD_ENABLE 6
14
+#define PIN_LCD_D0 5
15
+#define PIN_LCD_D1 4
16
+#define PIN_LCD_D2 3
17
+#define PIN_LCD_D3 2
18
+
19
+Button g_btnMode(PIN_BTN_MODE);
20
+Button g_btnMinus(PIN_BTN_MINUS);
21
+Button g_btnPlus(PIN_BTN_PLUS);
22
+Button* g_buttons[3] = {&g_btnMode, &g_btnMinus, &g_btnPlus};
23
+#define BUTTONS_COUNT (sizeof(g_buttons) / sizeof(*g_buttons))
24
+
25
+LiquidCrystal g_lcd(PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D0, PIN_LCD_D1, PIN_LCD_D2, PIN_LCD_D3);
26
+
27
+UiStateEnum g_modeSequence[3] = {WaterSetting, HeaterSetting, Lighting};
28
+#define MODE_SEQUENCE_COUNT (sizeof(g_modeSequence) / sizeof(*g_modeSequence))
29
+
30
+BoilerState g_boilerState = {
31
+        .water = {
32
+              .current = 44,
33
+              .setting = 44,
34
+              .isActive = false
35
+        },
36
+        .heater = {
37
+              .current = 44,
38
+              .setting = 44,
39
+              .isActive = false
40
+        },
41
+        .uiState = {
42
+                .state = Lighting,
43
+                .lastOpMs = 0,
44
+                .modeSequenceIndex = MODE_SEQUENCE_COUNT - 1
45
+        }
46
+};
47
+
48
+void printStateLine(char prefix, const BoilerLineState& boilerLineState, bool isModifying, bool isActive, LiquidCrystal lcd)
49
+{
50
+    char curTmp[5], setTmp[5], tmp[17];
51
+    dtostrf(boilerLineState.current / 2.0f, 2, 1, curTmp);
52
+    dtostrf(boilerLineState.setting / 2.0f, 2, 1, setTmp);
53
+    int count = snprintf(tmp, sizeof(tmp), "%c: %s [%s]%c%c", prefix, curTmp, setTmp, isModifying ? '<' : ' ', isActive ? 'o' : ' ');
54
+    for (; count < 17; ++count)
55
+    {
56
+        tmp[count] = ' ';
57
+    }
58
+    tmp[count] = 0;
59
+    lcd.print(tmp);
60
+}
61
+
62
+void printState(const BoilerState& boilerState, LiquidCrystal lcd)
63
+{
64
+    lcd.setCursor(0, 0);
65
+    printStateLine('S', boilerState.water, boilerState.uiState.state == WaterSetting, boilerState.water.isActive, lcd);
66
+    lcd.setCursor(0, 1);
67
+    printStateLine('C', boilerState.heater, boilerState.uiState.state == HeaterSetting, boilerState.heater.isActive, lcd);
68
+}
69
+
1 70
 void setup()
2 71
 {
72
+    for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
73
+    {
74
+        g_buttons[i]->begin();
75
+    }
76
+
77
+    pinMode(PIN_LCD_LED, OUTPUT);
78
+    digitalWrite(PIN_LCD_LED, 1);
79
+
80
+    g_lcd.begin(16, 2);
81
+    printState(g_boilerState, g_lcd);
82
+
3 83
     Serial.begin(9600);
4 84
 }
5 85
 
6 86
 void loop()
7 87
 {
88
+    bool needLcdUpdate = false;
89
+    unsigned long currentMs = millis();
90
+
91
+    for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
92
+    {
93
+        g_buttons[i]->read();
94
+        if (g_buttons[i]->isPressed())
95
+        {
96
+            g_boilerState.uiState.lastOpMs = currentMs;
97
+        }
98
+    }
99
+
100
+
101
+    if (g_boilerState.uiState.state == Hibernate)
102
+    {
103
+        for (unsigned i = 0; i < BUTTONS_COUNT; ++i)
104
+        {
105
+            if (g_buttons[i]->wasReleased())
106
+            {
107
+                g_boilerState.uiState.state = Lighting;
108
+                digitalWrite(PIN_LCD_LED, 1);
109
+                break;
110
+            }
111
+        }
112
+        needLcdUpdate = true;
113
+    }
114
+    else
115
+    {
116
+        if (currentMs - g_boilerState.uiState.lastOpMs >= HIBERNATE_DELAY)
117
+        {
118
+            g_boilerState.uiState.state = Hibernate;
119
+            digitalWrite(PIN_LCD_LED, 0);
120
+            g_boilerState.uiState.modeSequenceIndex = MODE_SEQUENCE_COUNT - 1;
121
+            needLcdUpdate = true;
122
+        }
123
+        else
124
+        {
125
+            if (g_btnMode.wasPressed())
126
+            {
127
+                g_boilerState.uiState.modeSequenceIndex =
128
+                        (g_boilerState.uiState.modeSequenceIndex + 1) % MODE_SEQUENCE_COUNT;
129
+                g_boilerState.uiState.state = g_modeSequence[g_boilerState.uiState.modeSequenceIndex];
130
+                needLcdUpdate = true;
131
+            }
132
+            else
133
+            {
134
+                temp_t* setting = NULL;
135
+                if (g_boilerState.uiState.state == WaterSetting)
136
+                {
137
+                    setting = &g_boilerState.water.setting;
138
+                }
139
+                else if (g_boilerState.uiState.state == HeaterSetting)
140
+                {
141
+                    setting = &g_boilerState.heater.setting;
142
+                }
143
+
144
+                if (setting)
145
+                {
146
+                    if (g_btnMinus.wasReleased())
147
+                    {
148
+                        --(*setting);
149
+                    }
150
+                    else if (g_btnPlus.wasReleased())
151
+                    {
152
+                        ++(*setting);
153
+                    }
154
+                    needLcdUpdate = true;
155
+                }
156
+            }
157
+        }
158
+    }
159
+
160
+    if (!g_boilerState.water.isActive && (g_boilerState.water.current <= g_boilerState.water.setting - TEMP_TRIGGER))
161
+    {
162
+        g_boilerState.water.isActive = true;
163
+        needLcdUpdate = true;
164
+    }
165
+    else if (g_boilerState.water.isActive && (g_boilerState.water.current >= g_boilerState.water.setting))
166
+    {
167
+        g_boilerState.water.isActive = false;
168
+        needLcdUpdate = true;
169
+    }
170
+
171
+    if (needLcdUpdate)
172
+    {
173
+        printState(g_boilerState, g_lcd);
174
+    }
8 175
 }

Loading…
Cancel
Save