| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 | #include "AppCore.h"
#include "Logs.h"
#define xstr(s) str(s)
#define str(s) #s
#define HIBERNATE_DELAY 5000
#define SENSORS_CHECK_INTERVAL 2000
#define SENSORS_REQUEST_DELAY 750
#define TEMP_TRIGGER ((temp_t)(5 * 10.0f))
#define TEMP_INTERVAL ((temp_t)(0.5 * 10.0f))
#define LCD_CHAR_SENSOR 1
#define PIN_ONEWIRE A0
#define PIN_RELAY2 A1
#define PIN_RELAY1 A2
#define PIN_BTN_CANCEL 9
#define PIN_BTN_OK 10
#define PIN_BTN_MINUS 11
#define PIN_BTN_PLUS 12
#define PIN_LCD_LED 2
#define PIN_LCD_RS 8
#define PIN_LCD_ENABLE 7
#define PIN_LCD_D4 6
#define PIN_LCD_D5 5
#define PIN_LCD_D6 4
#define PIN_LCD_D7 3
UiStateEnum AppCore::s_menuSequence[4]{MenuWaterSetting, MenuHeaterSetting, MenuTempTrigger, MenuVersion};
AppCore::AppCore()
        : m_appCoreState(new AppCoreState{
        .appState = {
                .lastSensorRequestMs = 0,
                .hasReadSensors = true,
                .water = {
                        .current = TEMP_T_INVALID,
                        .setting = 0,
                        .isActive = false,
                        .pinNo = PIN_RELAY1
                },
                .heater = {
                        .current = TEMP_T_INVALID,
                        .setting = 0,
                        .isActive = false,
                        .pinNo = PIN_RELAY2
                },
                .tempTrigger = TEMP_TRIGGER,
                .pCurrentSettingEdit = nullptr,
                .currentSettingEditTmp = 0
        },
        .uiState = {
                .state = HomeLighting,
                .lastOpMs = 0,
                .isUpdateNeeded = true
        }
})
          , m_pBtnCancel(new Button{PIN_BTN_CANCEL})
          , m_pBtnOk(new Button{PIN_BTN_OK})
          , m_pBtnMinus(new Button{PIN_BTN_MINUS})
          , m_pBtnPlus(new Button{PIN_BTN_PLUS})
          , m_pButtons{m_pBtnCancel, m_pBtnOk, m_pBtnMinus, m_pBtnPlus}
          , m_pLcd(new LiquidCrystal{PIN_LCD_RS, PIN_LCD_ENABLE, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7})
          , m_pOneWire(new OneWire{PIN_ONEWIRE})
          , m_pSensors(new DallasTemperature{m_pOneWire})
          , m_sensor1{0}
          , m_sensor2{0}
{
}
void AppCore::setup()
{
    Serial.begin(9600);
    LOG_FN_BEGIN(1);
    m_pSensors->begin();
    LOG(5, "Found %i sensors", m_pSensors->getDeviceCount());
    m_pSensors->getAddress(m_sensor1, 0);
    m_pSensors->getAddress(m_sensor2, 1);
    m_pSensors->setWaitForConversion(false);
    for (auto& g_button : m_pButtons)
    {
        g_button->begin();
    }
    pinMode(PIN_RELAY1, OUTPUT);
    digitalWrite(PIN_RELAY1, LOW);
    pinMode(PIN_RELAY2, OUTPUT);
    digitalWrite(PIN_RELAY2, LOW);
    pinMode(PIN_LCD_LED, OUTPUT);
    digitalWrite(PIN_LCD_LED, 1);
    m_pLcd->begin(16, 2);
    byte sensorChar[8] = {
            B00100,
            B01110,
            B01110,
            B01110,
            B01110,
            B11111,
            B11111,
            B01110,
    };
    m_pLcd->createChar(LCD_CHAR_SENSOR, sensorChar);
    bool allButtonsPressed = true;
    for (auto& g_button : m_pButtons)
    {
        g_button->read();
        allButtonsPressed = allButtonsPressed && g_button->isPressed();
    }
    if (!allButtonsPressed)
    {
        LOG(5, "%s: Loading settings", __FUNCTION__);
        m_storage.load(*m_appCoreState);
    }
    else
    {
        LOG(5, "%s: Resetting settings", __FUNCTION__);
        m_storage.save(*m_appCoreState);
        m_pLcd->clear();
        m_pLcd->setCursor(6, 0);
        m_pLcd->print("Reset");
        bool allButtonsPressed = true;
        while (allButtonsPressed)
        {
            for (auto& g_button : m_pButtons)
            {
                g_button->read();
                allButtonsPressed = allButtonsPressed && g_button->isPressed();
            }
        }
    }
    LOG_FN_END(1);
}
void AppCore::loop()
{
    LOG_FN_BEGIN(50);
    const auto& currentMs = millis();
    if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_CHECK_INTERVAL)
    {
        m_appCoreState->appState.lastSensorRequestMs = currentMs;
        m_appCoreState->appState.hasReadSensors = false;
        m_pSensors->requestTemperaturesByAddress(m_sensor1);
        m_pSensors->requestTemperaturesByAddress(m_sensor2);
    }
    if (currentMs - m_appCoreState->appState.lastSensorRequestMs >= SENSORS_REQUEST_DELAY &&
        !m_appCoreState->appState.hasReadSensors)
    {
        m_appCoreState->appState.hasReadSensors = true;
        readAndUpdateSensors(&m_appCoreState->appState.water, m_sensor1);
        readAndUpdateSensors(&m_appCoreState->appState.heater, m_sensor2);
        checkBoilerItem(&m_appCoreState->appState.water);
        checkBoilerItem(&m_appCoreState->appState.heater);
    }
    for (auto& pButton : m_pButtons)
    {
        pButton->read();
        if (pButton->isPressed())
        {
            m_appCoreState->uiState.lastOpMs = currentMs;
            break;
        }
    }
    if (m_appCoreState->uiState.state == HomeHibernate)
    {
        for (auto& pButton : m_pButtons)
        {
            if (pButton->wasReleased())
            {
                setState(HomeLighting);
                break;
            }
        }
    }
    else
    {
        if (currentMs - m_appCoreState->uiState.lastOpMs >= HIBERNATE_DELAY)
        {
            setState(HomeHibernate);
        }
        else
        {
            if (m_appCoreState->uiState.state == HomeLighting)
            {
                if (m_pBtnCancel->wasReleased())
                {
                    setState(HomeHibernate);
                }
                else if (m_pBtnOk->wasReleased())
                {
                    setState(s_menuSequence[0]);
                }
            }
            else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger || m_appCoreState->uiState.state == MenuVersion)
            {
                if (m_pBtnCancel->wasReleased())
                {
                    setState(HomeLighting);
                }
                else if (m_pBtnOk->wasReleased())
                {
                    UiStateEnum nextState = m_appCoreState->uiState.state;
                    if (m_appCoreState->uiState.state == MenuWaterSetting)
                    {
                        nextState = MenuWaterSettingEdit;
                    }
                    else if (m_appCoreState->uiState.state == MenuHeaterSetting)
                    {
                        nextState = MenuHeaterSettingEdit;
                    }
                    else if (m_appCoreState->uiState.state == MenuTempTrigger)
                    {
                        nextState = MenuTempTriggerEdit;
                    }
                    setState(nextState);
                }
                else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased())
                {
                    int idx = 0;
                    while (m_appCoreState->uiState.state != s_menuSequence[idx])
                    {
                        ++idx;
                    }
                    auto shift = m_pBtnMinus->wasReleased() ? (-1) : 1;
                    setState(s_menuSequence[(idx + shift) % ((sizeof(s_menuSequence) / sizeof(*s_menuSequence)))]);
                }
            }
            else if (m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit)
            {
                if (m_pBtnCancel->wasReleased() || m_pBtnOk->wasReleased())
                {
                    UiStateEnum nextState = m_appCoreState->uiState.state;
                    if (m_appCoreState->uiState.state == MenuWaterSettingEdit)
                    {
                        nextState = MenuWaterSetting;
                    }
                    else if (m_appCoreState->uiState.state == MenuHeaterSettingEdit)
                    {
                        nextState = MenuHeaterSetting;
                    }
                    else if (m_appCoreState->uiState.state == MenuTempTriggerEdit)
                    {
                        nextState = MenuTempTrigger;
                    }
                    if (m_pBtnOk->wasReleased())
                    {
                        *m_appCoreState->appState.pCurrentSettingEdit = m_appCoreState->appState.currentSettingEditTmp;
                    }
                    m_appCoreState->appState.currentSettingEditTmp = 0;
                    m_appCoreState->appState.pCurrentSettingEdit = nullptr;
                    setState(nextState);
                }
                else if (m_pBtnMinus->wasReleased() || m_pBtnPlus->wasReleased())
                {
                    m_appCoreState->appState.currentSettingEditTmp += (m_pBtnMinus->wasReleased() ? -1 : 1) * TEMP_INTERVAL;
                    m_appCoreState->uiState.isUpdateNeeded = true;
                }
            }
        }
    }
    if (m_appCoreState->uiState.isUpdateNeeded)
    {
        printState();
    }
    LOG_FN_END(50);
}
void AppCore::setState(
        UiStateEnum state
)
{
    LOG_FN_BEGIN(1);
    LOG(5, "Changing state %i => %i", m_appCoreState->uiState.state, state);
    if (state == HomeLighting)
    {
        digitalWrite(PIN_LCD_LED, 1);
    }
    else if (state == HomeHibernate)
    {
        digitalWrite(PIN_LCD_LED, 0);
        m_storage.save(*m_appCoreState);
    }
    else if (state == MenuWaterSettingEdit)
    {
        m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.water.setting;
        m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.water.setting;
    }
    else if (state == MenuHeaterSettingEdit)
    {
        m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.heater.setting;
        m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.heater.setting;
    }
    else if (state == MenuTempTriggerEdit)
    {
        m_appCoreState->appState.currentSettingEditTmp = m_appCoreState->appState.tempTrigger;
        m_appCoreState->appState.pCurrentSettingEdit = &m_appCoreState->appState.tempTrigger;
    }
    m_appCoreState->uiState.state = state;
    m_appCoreState->uiState.isUpdateNeeded = true;
    LOG_FN_END(1);
}
void AppCore::checkBoilerItem(
        BoilerItemState* boilerItemState
)
{
    LOG_FN_BEGIN(2);
    if (!boilerItemState->isActive && boilerItemState->current != TEMP_T_INVALID &&
        boilerItemState->current <= boilerItemState->setting - m_appCoreState->appState.tempTrigger)
    {
        boilerItemState->isActive = true;
        digitalWrite(boilerItemState->pinNo, HIGH);
        m_appCoreState->uiState.isUpdateNeeded = true;
    }
    else if (boilerItemState->isActive &&
             (boilerItemState->current == TEMP_T_INVALID || boilerItemState->current >= boilerItemState->setting))
    {
        boilerItemState->isActive = false;
        digitalWrite(boilerItemState->pinNo, LOW);
        m_appCoreState->uiState.isUpdateNeeded = true;
    }
    LOG_FN_END(2);
}
void AppCore::readAndUpdateSensors(
        BoilerItemState* boilerItemState
        , const uint8_t* sensor
)
{
    LOG_FN_BEGIN(2);
    auto raw = m_pSensors->getTempC(sensor);
    temp_t temp = TEMP_T_INVALID;
    if (raw != DEVICE_DISCONNECTED_C)
    {
        temp = (temp_t) (raw * 10);
    }
    if (temp != boilerItemState->current)
    {
        boilerItemState->current = temp;
        m_appCoreState->uiState.isUpdateNeeded = true;
    }
    LOG_FN_END(2);
}
void AppCore::printState()
{
    LOG_FN_BEGIN(2);
    if (m_appCoreState->uiState.state == HomeLighting || m_appCoreState->uiState.state == HomeHibernate)
    {
        m_pLcd->setCursor(0, 0);
        printStateLine('S', &m_appCoreState->appState.water);
        m_pLcd->setCursor(0, 1);
        printStateLine('C', &m_appCoreState->appState.heater);
    }
    else if (m_appCoreState->uiState.state == MenuWaterSetting || m_appCoreState->uiState.state == MenuHeaterSetting || m_appCoreState->uiState.state == MenuTempTrigger ||
            m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit)
    {
        const auto isEdit = m_appCoreState->uiState.state == MenuWaterSettingEdit || m_appCoreState->uiState.state == MenuHeaterSettingEdit || m_appCoreState->uiState.state == MenuTempTriggerEdit;
        const char* pTitle = nullptr;
        temp_t* pTemp = nullptr;
        if (isEdit)
        {
            pTemp = &m_appCoreState->appState.currentSettingEditTmp;
        }
        else
        {
            if (m_appCoreState->uiState.state == MenuWaterSetting ||
                m_appCoreState->uiState.state == MenuWaterSettingEdit)
            {
                pTitle = "Sanitaire";
                pTemp = &m_appCoreState->appState.water.setting;
            }
            else if (m_appCoreState->uiState.state == MenuHeaterSetting ||
                     m_appCoreState->uiState.state == MenuHeaterSettingEdit)
            {
                pTitle = "Chauffage";
                pTemp = &m_appCoreState->appState.heater.setting;
            }
            else if (m_appCoreState->uiState.state == MenuTempTrigger ||
                     m_appCoreState->uiState.state == MenuTempTriggerEdit)
            {
                pTitle = "Delta";
                pTemp = &m_appCoreState->appState.tempTrigger;
            }
        }
        char tmpStr[17], tmpTemp[7];
        m_pLcd->setCursor(0, 0);
        snprintf(tmpStr, sizeof(tmpStr), "%s :                ", pTitle);
        m_pLcd->print(tmpStr);
        m_pLcd->setCursor(0, 1);
        tempToStr(tmpTemp, *pTemp, 4);
        snprintf(tmpStr, sizeof(tmpStr), "%s%s C                ", isEdit ? "> " : "  ", tmpTemp);
        m_pLcd->print(tmpStr);
    }
    else if (m_appCoreState->uiState.state == MenuVersion)
    {
        char tmpStr[17];
        m_pLcd->setCursor(0, 0);
        snprintf(tmpStr, sizeof(tmpStr), "%s :                ", "Version");
        m_pLcd->print(tmpStr);
        m_pLcd->setCursor(0, 1);
        snprintf(tmpStr, sizeof(tmpStr), "%s - %s                ", xstr(APP_CORE_VERSION), xstr(APP_CORE_COMMIT));
        m_pLcd->print(tmpStr);
    }
    m_appCoreState->uiState.isUpdateNeeded = false;
    LOG_FN_END(2);
}
void
AppCore::printStateLine(
        char prefix
        , const BoilerItemState* boilerItemState
)
{
    LOG_FN_BEGIN(2);
    char curTmp[7], setTmp[7], tmp[17];
    tempToStr(curTmp, boilerItemState->current, 5);
    tempToStr(setTmp, boilerItemState->setting, 4);
    int count = snprintf(tmp, sizeof(tmp), "%c:%s [%s] %c", prefix, curTmp, setTmp, boilerItemState->isActive ? LCD_CHAR_SENSOR : ' ');
    for (; count < 17; ++count)
    {
        tmp[count] = ' ';
    }
    tmp[count] = 0;
    m_pLcd->print(tmp);
    LOG_FN_END(2);
}
void AppCore::tempToStr(
        char* out
        , temp_t temp
        , signed char width
)
{
    LOG_FN_BEGIN(2);
    LOG(5, "%s: temp=%i", __FUNCTION__, (int)temp);
    if (temp == TEMP_T_INVALID)
    {
        strcpy(out, " --.-");
    }
    else
    {
        dtostrf(temp / 10.0f, width, 1, out);
    }
    LOG_FN_END(2);
}
 |