#include "U8glib.h"
// pins assignemnt
#define BUTTON_UP_PIN 12 // pin for UP button
#define BUTTON_SELECT_PIN 8 // pin for SELECT button
#define BUTTON_DOWN_PIN 4 // pin for DOWN button
#define BUTTON_BACK_PIN 7 // pin for BACK button
#define OLED_SCL A5 // pin for UP button
#define OLED_SDA A4 // pin for SELECT button
// value types
#define SMALL_NUMBER 0
#define LARGE_NUMBER 1
#define HVAC_MODE 2
#define TEMP 3
#define PERC 4
// value options
#define OFF 0
#define ON 1
#define AUTO 2
#define PARTY 3
// item types
#define TYPE_NONE 0
#define TYPE_LIST 1
#define TYPE_SCREEN 2
#define TYPE_PAGE 3
#define TYPE_VALUE 4
#define TYPE_INFO 5
#define TYPE_EDITOR 6
// item data identificators of item lists
#define ITEM_TYPE 0
#define FIRST_ITEM 1
#define ITEM_COUNT 2
#define ORIGIN 3
// item data identificators of item values
#define ITEM_TYPE 0
#define VALUE_TYPE 1
#define ITEM_VALUE 2
#define ITEM_ADRESS 3
#define MAX_STRING_LENGTH 16 // maximum characters for the item name
#define NUM_ITEMS 63
// display
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g(U8G2_R0, OLED_SCL, OLED_SDA, U8X8_PIN_NONE);
enum BUTTON {
none,
back,
up,
down,
select
};
// help variables to handle button press detection
BUTTON button_pressed = none;
BUTTON button_action = none;
static uint32_t timer;
// variables to handle menus
byte firstItemIndex;
byte lastItemIndex;
byte activeScreenType;
byte activeScreenIndex;
byte revertScreenIndex;
bool editMode = false;
byte columnCount = 4;
byte columnItemIndex[8];
byte columnSelected;
byte selectedItemIndex;
// variables for consumption sensors
const char hvacModes[4][16] = {"OFF", "ON", "AUTO", "PARTY"};
const char lightModes[4][16] = {"OFF", "ON", "AUTO", "ECO"};
byte valueTypeSize[5] = {255, 255, 3, 255, 100};
unsigned int largeValues[4] = {11055, 1155, 1220, 2002};
const char knxGroupAdr[10][9] PROGMEM = {
"0.0.0",
"0.0.1",
"0.0.2",
"0.0.3",
"0.0.4",
"0.0.5",
"0.0.6",
"0.0.7",
"0.0.8",
"0.0.9",
};
// all items names
const char itemNames [NUM_ITEMS][MAX_STRING_LENGTH] PROGMEM = {
{ "" }, // null screen
{ "Main menu" },
// ------------------- MENUS -----------------------
// Main menu: index 2 - 8
{ "Heating" },
{ "Monitoring" },
{ "Lightning" },
{ "Settings" },
{ "KNX bus" },
{ "RESERVED" },
{ "RESERVED" },
// Heating menu: index 9 - 13
{ "uHVAC mode" },
{ "Room Controls" },
{ "Boiler status" },
{ "RESERVED" },
{ "RESERVED" },
// Monitoring menu: index 14 - 19
{ "Consumption" },
{ "Brightness" },
{ "Door contacts" },
{ "Flood detect" },
{ "RESERVED" },
// Lightning menu: index 19 - 23
{ "Navi lights" },
{ "Wardrobe Light" },
{ "Brightness" },
{ "RESERVED" },
{ "RESERVED" },
// Settings menu: index 24 - 28
{ "Date/Time" },
{ "Circulation" },
{ "Smartglass" },
{ "RESERVED" },
{ "RESERVED" },
// KNX bus menu: index 29 - 31
{ "BUSMonitor" },
{ "Send telegram" },
{ "RESERVED" },
// ---------------- SUBMENUS -----------------------
// consumption yeilds 32 - 35
{ "GAS" },
{ "L1" },
{ "L2" },
{ "L3" },
// temperatures 36 - 43
{ "Central" },
{ "Entrance" },
{ "Toilet" },
{ "LivingRoom" },
{ "Kitchen" },
{ "KidsRoom" },
{ "BedRoom" },
{ "BathRoom" },
// values for Central temperatures 44 - 45
{ "SetPoint" },
{ "RoomTemp" },
// values for Entrance temperatures 46 - 47
{ "SetPoint" },
{ "RoomTemp" },
// values for toilet temperatures 48 - 49
{ "SetPoint" },
{ "RoomTemp" },
// values for livingroom temperatures 50 - 51
{ "SetPoint" },
{ "RoomTemp" },
// values for kitchen temperatures 52 - 53
{ "SetPoint" },
{ "RoomTemp" },
// values for kidsroom temperatures 54 - 55
{ "SetPoint" },
{ "RoomTemp" },
// values for bedroom temperatures 56 - 57
{ "SetPoint" },
{ "RoomTemp" },
// values for bathroom temperatures 58 - 59
{ "SetPoint" },
{ "RoomTemp" },
// boiler status - 60 - 62
{ "HVAC MODE" },
{ "Water temp" },
{ "HWater temp" }
};
//all items data
byte itemData [NUM_ITEMS][4] = {
{ TYPE_NONE, 0, 0, 1 }, // null screen
{ TYPE_LIST, 2, 5, 1 },
// ------------------- MENUS -----------------------
// LIST (itemType, first item index, number of items, return menu index)
// Main menu: index 2 - 8
{ TYPE_LIST, 9, 3, 1},
{ TYPE_LIST, 14, 4, 1},
{ TYPE_LIST, 19, 3, 1},
{ TYPE_LIST, 24, 3, 1},
{ TYPE_LIST, 29, 2, 1},
{ TYPE_NONE, 0, 0, 0},
{ TYPE_NONE, 0, 0, 0},
// Heating menu: index 9 - 13
{ TYPE_VALUE, HVAC_MODE, OFF, 2},
{ TYPE_SCREEN, 36, 8, 2},
{ TYPE_LIST, 60, 3, 2},
{ TYPE_NONE, 0, 0, 0},
{ TYPE_NONE, 0, 0, 0},
// Monitoring menu: index 14 - 19
{ TYPE_LIST, 32, 4, 3},
{ TYPE_NONE, 0, 0, 3},
{ TYPE_NONE, 0, 0, 3},
{ TYPE_NONE, 0, 0, 3},
{ TYPE_NONE, 0, 0, 0},
// Lightning menu: index 19 - 23
{ TYPE_NONE, 0, 0, 4},
{ TYPE_NONE, 0, 0, 4},
{ TYPE_NONE, 0, 0, 4},
{ TYPE_NONE, 0, 0, 0},
{ TYPE_NONE, 0, 0, 0},
// Settings menu: index 24 - 28
{ TYPE_NONE, 0, 0, 5},
{ TYPE_NONE, 0, 0, 5},
{ TYPE_NONE, 0, 0, 5},
{ TYPE_NONE, 0, 0, 0},
{ TYPE_NONE, 0, 0, 0},
// KNX bus menu: index 29 - 31
{ TYPE_NONE, 0, 0, 6},
{ TYPE_NONE, 0, 0, 6},
{ TYPE_NONE, 0, 0, 0},
// ---------------- SUBMENUS -----------------------
// consumption yeilds 32 - 35
{ TYPE_INFO, LARGE_NUMBER, 0, 14},
{ TYPE_INFO, LARGE_NUMBER, 1, 14},
{ TYPE_INFO, LARGE_NUMBER, 2, 14},
{ TYPE_INFO, LARGE_NUMBER, 3, 14},
// temperatures 36 - 43 (itemName, itemType, setPointTemp, MeasuredTemp, ValveStatus)
{ TYPE_PAGE, 44, 2, 10},
{ TYPE_PAGE, 46, 2, 10},
{ TYPE_PAGE, 50, 2, 10},
{ TYPE_PAGE, 52, 2, 10},
{ TYPE_PAGE, 54, 2, 10},
{ TYPE_PAGE, 56, 2, 10},
{ TYPE_PAGE, 58, 2, 10},
{ TYPE_PAGE, 58, 2, 10},
// values for Central temperatures 44 - 45
{ TYPE_VALUE, TEMP, 20, 0},
{ TYPE_INFO, TEMP, 10, 0},
// values for Entrance temperatures 46 - 47
{ TYPE_VALUE, TEMP, 20, 1},
{ TYPE_INFO, TEMP, 12, 1},
// values for toilet temperatures 48 - 49
{ TYPE_VALUE, TEMP, 22, 2},
{ TYPE_INFO, TEMP, 16, 2},
// values for livingroom temperatures 50 - 51
{ TYPE_VALUE, TEMP, 22, 3},
{ TYPE_INFO, TEMP, 22, 3},
// values for kitchen temperatures 52 - 53
{ TYPE_VALUE, TEMP, 20, 4},
{ TYPE_INFO, TEMP, 10, 4},
// values for kidsroom temperatures 54 - 55
{ TYPE_VALUE, TEMP, 20, 5},
{ TYPE_INFO, TEMP, 11, 5},
// values for bedroom temperatures 56 - 57
{ TYPE_VALUE, TEMP, 21, 6},
{ TYPE_INFO, TEMP, 14, 6},
// values for bathroom temperatures 58 - 59
{ TYPE_VALUE, TEMP, 20, 7},
{ TYPE_INFO, TEMP, 10, 7},
// boiler status - 60 - 62
{ TYPE_INFO, HVAC_MODE, AUTO, 0},
{ TYPE_INFO, TEMP, 55, 0},
{ TYPE_INFO, TEMP, 60, 0},
};
// functions
void readFromKnxBus() {
}
void sendToKnxBus() {
}
void readInputs() {
}
void doAutomation() {
//temporary data incremention
largeValues[0]++;
largeValues[1] = largeValues[1] + 13;
}
void prepareScreenData() {
switch (activeScreenType) {
//prepare data for menu lists
case TYPE_LIST:
// fill up columns with data
for (int i = 1; i <= columnCount - 1; i++) {
columnItemIndex[i] = selectedItemIndex + (i - columnSelected);
if (columnItemIndex[i] > lastItemIndex) {
columnItemIndex[i] = 0;
}
}
break;
// prepare data for screen with pages
case TYPE_SCREEN:
// fill up remaining columns with data
columnItemIndex[0] = selectedItemIndex;
for (int i = 0; i < columnCount - 1; i++) {
if (i < itemData[columnItemIndex[0]][ITEM_COUNT]) {
columnItemIndex[i + 1] = itemData[columnItemIndex[0]][FIRST_ITEM] + i;
}
else {
columnItemIndex[i + 1] = 0;
}
}
break;
default:
break;
}
}
void changeScreen(byte destination) {
revertScreenIndex = itemData[destination][ORIGIN];
firstItemIndex = itemData[destination][FIRST_ITEM];
lastItemIndex = itemData[destination][FIRST_ITEM] + itemData[destination][ITEM_COUNT] - 1 ;
switch (itemData[destination][ITEM_TYPE]) {
case TYPE_LIST:
activeScreenType = TYPE_LIST;
columnItemIndex[0] = destination;
selectedItemIndex = activeScreenIndex >= firstItemIndex ? activeScreenIndex : firstItemIndex; // select previous item in destination list
// calculate column position of selected item according to count of items to be displayed
if (lastItemIndex - selectedItemIndex + 1 >= columnCount - 1) { // there is enough items to fill all columns
columnSelected = 1;
Serial.println("SELECTED ");
Serial.println(selectedItemIndex);
} else if(itemData[destination][ITEM_COUNT] < columnCount - 1) { // there is less items in total then columns
columnSelected = itemData[destination][ITEM_COUNT] - (lastItemIndex - selectedItemIndex);
} else { // there is less items remaining then colums
columnSelected = (columnCount - 1) - (lastItemIndex - selectedItemIndex);
}
break;
case TYPE_SCREEN:
activeScreenType = TYPE_SCREEN;
// columnItemIndex[1] = itemData[itemData[destination][FIRST_ITEM]][FIRST_ITEM]; // pointing to first item of page of screen
selectedItemIndex = itemData[destination][FIRST_ITEM];
columnSelected = 0;
break;
default:
break;
}
activeScreenIndex = destination;
}
void valueToText(byte value, byte valueType, char* OutStr) {
switch (valueType) {
case SMALL_NUMBER:
sprintf(OutStr, "%d", value);
break;
case LARGE_NUMBER:
sprintf(OutStr, "%d", largeValues[value]);
break;
case TEMP:
sprintf(OutStr, "%d\xB0""C", value);
break;
case HVAC_MODE:
strcpy(OutStr, hvacModes[value]);
break;
default:
break;
}
}
void drawScreen(byte drawItemIndex[], int columnNum, int selected) {
u8g_fntpgm_uint8_t *fontStd;
u8g_fntpgm_uint8_t *fontBold;
u8g_uint_t infoWidth;
char buffer[MAX_STRING_LENGTH];
byte columnHeight;
byte spaceThickness;
switch(columnNum){
case 3:
fontStd = u8g_font_10x20;
fontBold = u8g_font_10x20;
columnHeight = 21;
spaceThickness = 2;
break;
case 4:
fontStd = u8g_font_7x14;
fontBold = u8g_font_7x14B;
columnHeight = 16;
spaceThickness = 2;
break;
case 5:
fontStd = u8g_font_6x13;
fontBold = u8g_font_6x13B;
columnHeight = 12;
spaceThickness = 1;
break;
case 6:
fontStd = u8g_font_6x13;
fontBold = u8g_font_6x13B;
columnHeight = 10;
spaceThickness = 1;
break;
case 7:
fontStd = u8g_font_6x10;
fontBold = u8g_font_6x10;
columnHeight = 9;
spaceThickness = 1;
break;
case 8:
fontStd = u8g_font_5x8;
fontBold = u8g_font_5x8;
columnHeight = 8;
spaceThickness = 0;
break;
default:
break;
fontStd = u8g_font_7x14;
fontBold = u8g_font_7x14B;
columnHeight = 16;
spaceThickness = 2;
}
u8g.firstPage(); // required for page drawing mode for u8g library
do {
// draw line for label and use bold text for first column if header is desired
if (activeScreenType == TYPE_LIST) {
u8g.drawHLine(0, columnHeight, 120);
}
// draw text into columns
for (int i = 0; i < columnNum; i++) {
if (i == selected) { // highlight selected item
u8g.setFont(fontBold);
if (editMode == false) {
u8g.drawBox(0, (columnHeight + spaceThickness) + (columnHeight * (selected - 1)), 120, (columnHeight - spaceThickness));
u8g.setColorIndex(0);
}
}
if ((itemData[drawItemIndex[i]][ITEM_TYPE] == TYPE_INFO) || (itemData[drawItemIndex[i]][ITEM_TYPE] == TYPE_VALUE)) { // show values for infos
valueToText(itemData[drawItemIndex[i]][ITEM_VALUE], itemData[drawItemIndex[i]][VALUE_TYPE], buffer);
infoWidth = u8g.getStrWidth(buffer);
if (editMode == true) {
u8g.drawBox(119 - infoWidth, (columnHeight + spaceThickness) + (columnHeight * (selected - 1)), infoWidth + 1, (columnHeight - spaceThickness));
u8g.setColorIndex(0);
u8g.drawStr(120 - infoWidth, (columnHeight - spaceThickness) + (columnHeight * i), buffer);
u8g.setColorIndex(1);
} else {
u8g.drawStr(120 - infoWidth, (columnHeight - spaceThickness) + (columnHeight * i), buffer);
}
}
strcpy_P(buffer, itemNames[drawItemIndex[i]]);
u8g.drawStr(3, (columnHeight - spaceThickness) + (columnHeight * i), buffer); // draw column text
u8g.setColorIndex(1); // reset color for next item
u8g.setFont(fontStd); // reset color font next item
}
// draw scrollbar handle
//if(lastItemIndex - firstItemIndex + 1 > 3){
//u8g.drawBox(125, 16+(48/(lastItemIndex - firstItemIndex + 1) * (columnItemIndex[1] - firstItemIndex)), 3, (3*48/(lastItemIndex - firstItemIndex + 1)));
//}
} while ( u8g.nextPage() ); // required for page drawing mode with u8g library
}
void handleButtons() {
// checking button pressed
if ((digitalRead(BUTTON_BACK_PIN) == LOW) && (button_pressed == none)) {
button_pressed = back;
button_action = back;
timer = millis();
}
else if ((digitalRead(BUTTON_SELECT_PIN) == LOW) && (button_pressed == none)) {
button_pressed = select;
button_action = select;
timer = millis();
}
else if ((digitalRead(BUTTON_UP_PIN) == LOW) && (button_pressed == none)) {
button_pressed = up;
button_action = up;
timer = millis();
}
else if ((digitalRead(BUTTON_DOWN_PIN) == LOW) && (button_pressed == none)) {
button_pressed = down;
button_action = down;
timer = millis();
}
// unclick buttons
if ((digitalRead(BUTTON_BACK_PIN) == HIGH) && (button_pressed == back)) {
button_pressed = none;
}
if ((digitalRead(BUTTON_SELECT_PIN) == HIGH) && (button_pressed == select)) {
button_pressed = none;
}
if ((digitalRead(BUTTON_UP_PIN) == HIGH) && (button_pressed == up)) {
button_pressed = none;
}
if ((digitalRead(BUTTON_DOWN_PIN) == HIGH) && (button_pressed == down)) {
button_pressed = none;
}
// repeat action every 250 miliseconds for button holded down
if (button_pressed != none) {
if (millis() - timer > 250) {
timer = millis();
button_action = button_pressed;
}
}
// do action according to active screen
switch (activeScreenType) {
case TYPE_LIST:
// do action according to active button
switch (button_action) {
case back:
if (editMode == true) { // if edit mode is active, deactivate it
editMode = false;
} else { // otherwise just return to previous screen
changeScreen(revertScreenIndex);
}
break;
case select:
//if selected item is editable try to set edit mode
if (itemData[selectedItemIndex][ITEM_TYPE] == TYPE_VALUE) {
if (editMode == false) {
editMode = true;
} else { // if edit mode is already active, confirm changes
editMode = false;
// =missing_code=
// confirm value change to bus
}
// if selected item is another menu, go to that menu
} else if ((itemData[selectedItemIndex][ITEM_TYPE] == TYPE_LIST) || (itemData[selectedItemIndex][ITEM_TYPE] == TYPE_SCREEN)) {
changeScreen(selectedItemIndex);
}
break;
case up:
if (editMode == true) { // for value editing
if (itemData[selectedItemIndex][ITEM_VALUE] > 0 ) {
itemData[selectedItemIndex][ITEM_VALUE]--;
} else {
itemData[selectedItemIndex][ITEM_VALUE] = valueTypeSize[itemData[selectedItemIndex][VALUE_TYPE]];
}
}
else { // for list browsing
if (selectedItemIndex > firstItemIndex) {
selectedItemIndex -= 1;
columnSelected = columnSelected > 1 ? columnSelected - 1 : 1 ; // move selection one column up
} else {
selectedItemIndex = lastItemIndex;
columnSelected = lastItemIndex - firstItemIndex + 1 >= columnCount - 1 ? columnCount-1 : lastItemIndex - firstItemIndex + 1;
}
}
break;
case down:
if (editMode == true) { // for value editing
if (itemData[selectedItemIndex][ITEM_VALUE] < valueTypeSize[itemData[selectedItemIndex][VALUE_TYPE]] ) {
itemData[selectedItemIndex][ITEM_VALUE]++;
}
else {
itemData[selectedItemIndex][ITEM_VALUE] = 0;
}
}
else { // for list browsing
if (selectedItemIndex < lastItemIndex) {
selectedItemIndex += 1;
columnSelected = columnSelected < columnCount-1 ? columnSelected + 1 : columnCount-1 ; // move selection one column up
} else {
selectedItemIndex = firstItemIndex;
columnSelected = 1;
}
}
break;
default:
break;
}
break;
case TYPE_SCREEN: // need rewrite!!!
// do action according to active button
switch (button_action) {
case back:
changeScreen(revertScreenIndex);
break;
case up: // up button clicked - jump to previous page
selectedItemIndex = selectedItemIndex > firstItemIndex ? selectedItemIndex - 1 : lastItemIndex;// if there are no more previous pages go to last page
break;
case down: // up button clicked - jump to next page
selectedItemIndex = selectedItemIndex < lastItemIndex ? selectedItemIndex + 1 : firstItemIndex; // if there are no more next pages go ti first page
break;
default:
break;
}
break;
default:
break;
}
button_action = none; // reset button_action for next action detection
}
// program functions
void setup() {
Serial.begin(9600); //for debuging
u8g.begin();
u8g.setColorIndex(1); // set the color to white
// define pins for buttons
// INPUT_PULLUP means the button is HIGH when not pressed, and LOW when pressed
// since it´s connected between some pin and GND
pinMode(BUTTON_UP_PIN, INPUT_PULLUP); // up button
pinMode(BUTTON_SELECT_PIN, INPUT_PULLUP); // select button
pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); // down button
pinMode(BUTTON_BACK_PIN, INPUT_PULLUP); // back button
changeScreen(1); //set screen to main menu
}
void loop() {
readFromKnxBus();
readInputs();
doAutomation();
sendToKnxBus();
handleButtons();
prepareScreenData();
drawScreen(columnItemIndex, columnCount, columnSelected);
}