Проект двухступенчатого водоснабжения загородного дома

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Может причина в том, что цикл начинается из состояния 0? В состоянии 0 предполагается, что таймер 3ч уже запущен. Надо либо запускать таймер в setup(), либо начинать цикл из состояния 1.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

убрал пост, новый код ниже

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

убрал пост, новый код ниже

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

отлично... начал смотреть код библиотеки и нашел я ошибку - я проверяю включен таймер или нет, а надо проверять, был ли он переполнен или нет

т.е. вот так надо

case 0: if ( ((fSystemState == TANK_IS_EMPTY) || (fSystemState == TANK_LESS_THAN_A_HALF))  && (!timer3H.timerOverflow)) /*бак пустой полностью || уровень ниже половины) && 3ч не истек*/
      {
        st = 1;
        Serial.print("PUMP. st = ");
        Serial.println (st);
        timer3H.timerOff();//останавливаем 3ч таймер;
        //включаем насос;
      }
      if ( ((fSystemState == TANK_IS_EMPTY) || (fSystemState == TANK_LESS_THAN_A_HALF)) && (timer3H.timerOverflow)) /*(бак пустой полностью || уровень ниже половины) && 3ч истек*/
      {
        st = 2;
        Serial.print("3W opening. st = ");
        Serial.println (st);
        timer3H.timerOff();// останавливаем 3ч таймер;
        //открываем клапан;
        timer1M.timerOn(millis());// запускам таймер 1м;
      }

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Ну, вроде все работает.

Пойду почитаю про классы (чего в public, а чего нет), попробую-таки функции вынести в библиотеку (пока безуспешно) и потом поприкручиваю дисплейчик :)

/*
  WaterSystem.h
  Created by Angbor, January 1, 2016
  Released into the public domain
*/

#ifndef WaterSystem_h
#define WaterSystem_h

#include "Arduino.h"

class WaterTimer
{
	private:
		long timerLength; // длительность таймера

		unsigned long previousMillis;
		String timerName = "nill";
		void (*ptrOnFuniction)();
		byte withFunction = 0;

	public:
		WaterTimer(long tLength, void f(), byte withFunc, String tName, String tTipe);
		WaterTimer(long tLength, byte withFunc, String tName, String tTipe);
		void timerOn(unsigned long currentMillis);
		void timerOff();
		bool getIsTimerOn(unsigned long currentMillis);
		bool timerOverflow = false;
		String timerType = "loop"; // or "line"
		bool timerIsOn = false;  // Текущее состояние On или Off
};

#endif
/*
  WaterSystem.cpp
  Created by Angbor, January 1, 2016
  Released into the public domain
*/
 
#include <Arduino.h>
#include <WaterSystem.h>

WaterTimer::WaterTimer(long tLength, void f(), byte withFunc, String tName, String tTipe){
   timerName = tName;
   timerType = tTipe;
   withFunction = withFunc;
	ptrOnFuniction = f;
	timerLength = tLength;
	timerIsOn = false;
	previousMillis = 0;
}

WaterTimer::WaterTimer(long tLength, byte withFunc, String tName, String tTipe){
   timerName = tName;
   timerType = tTipe;
   withFunction = withFunc;
	timerLength = tLength;
	timerIsOn = false;
	previousMillis = 0;
}
                                                       
void WaterTimer::timerOn(unsigned long currentMillis) {
   timerOverflow = false;
   previousMillis = currentMillis;
	timerIsOn = true;
	Serial.print(timerName);
   Serial.println(" ON");
}

void WaterTimer :: timerOff() {
	timerIsOn = false;
	Serial.print(timerName);
   Serial.println(" OFF");
}

bool WaterTimer :: getIsTimerOn(unsigned long currentMillis){
    if ((timerIsOn) && (currentMillis - previousMillis >= timerLength))
        {
            if (timerType == "loop") {
                if (withFunction == 1) {
                    ptrOnFuniction();
                }
                previousMillis = currentMillis;
                }
            if (timerType == "line"){
            Serial.print(timerName);
            Serial.println(" overflow");
            timerOverflow = true;
         timerOff();
            }
            return true;
        }
        else {
            return false;
        }
}
#include <WaterSystem.h>

// определяем пины для переключателей
#define BOTTOM_SWITCH   2
#define MIDDLE_SWITCH   3
#define TOP_SWITCH      4
#define OVERFLOW_SWITCH 5

//=== замедления циклов ======
#define READ_PIN_DELAY 100
#define REGISTR_DELAY  150
#define BLINK_DELAY    300

//=== реле ======
#define PUMP            9
#define PUMP_ON         1
#define PUMP_OFF        0
#define THREE_WAY_VALWE 10
#define TWV_ON          1
#define TWV_OFF         0

//потом надо перевести это дело как-то в часы и минуты (соответственно таймеру). сейчас пока в милисекундах
#define DOWNTIME        5000 // 3H время простоя, после которого обязательна прокачка.
#define TWV_DELAY       3000 // 15M время прокачки
#define TWV_OPENING     2000 // 1M время на открытие вентиля (потом надо заменить это на чтение концевика 3W

// прототипы и укзатели функций
void blinkS();
void pinReading();
void regWork();

void (*ptrBlinkS)() = blinkS;
void (*ptrPinReading)() = pinReading;
void (*ptrRegWork)() = regWork;

// -- таймеры для снижения частоты смены состояний ---------------
#define WITH_FUNCTION    1
#define WITHOUT_FUNCTION 0

WaterTimer timerBlink(BLINK_DELAY, ptrBlinkS, WITH_FUNCTION, "BLINK_DELAY", "loop");
WaterTimer timerPinRead(READ_PIN_DELAY, ptrPinReading, WITH_FUNCTION, "READ_PIN_DELAY", "loop");
WaterTimer timerRegistrDelay(REGISTR_DELAY, ptrRegWork, WITH_FUNCTION, "REGISTR_DELAY", "loop");
WaterTimer timer3H(DOWNTIME, WITHOUT_FUNCTION, "3H", "line");
WaterTimer timer1M(TWV_OPENING, WITHOUT_FUNCTION, "1M", "line");
WaterTimer timer15M(TWV_DELAY, WITHOUT_FUNCTION, "15M", "line");

bool fPumpIsOn  = false;
bool f3wIsOpen  = false;

// описание состояний
#define TANK_IS_EMPTY         0
#define TANK_LESS_THAN_A_HALF 1
#define TANK_MORE_THAN_A_HALF 2
#define TANK_IS_FULL          3
#define WRONG_SENSOR_COMB     4
#define TANK_OVERFLOW         5
#define SENSORS_IS_OK         6 // заглушка для функции проверки аварии, когда в прошлом вызове была авария, а сейчас нет и еще нет сигнала от сенсоров, чтобы переписать состояние
byte fSystemState = 0;

String systemStateName[] = {"EMPTY", "LESS THEN A HALF", "MORE THAN A HALF", "FULL", "WRONG SENSORS", "OVERFLOW", "RESET SENSORS"};

// ====== для цикла чтения пинов
#define NUM 4
#define GET(b) digitalRead(arr[b])
const int arr[] = {BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};

// устанавливаем управление сдвиговым регистром
const int latchPin  = 8;
const int clockPin  = 12;
const int dataPin   = 11;
uint8_t   ledsBurn  = 0; //переменная для передачи в регистр

// =========================
byte switchState[]     = {1, 0, 0, 0}; // состояние переключателей
byte lastSwitchState[] = {0, 0, 0, 0}; // предыдущее состояние
byte blinkF[]          = {1, 0, 0, 1}; /*флаги для включения blink, здесь указываем по умолчанию какие диоды будут мигать (1),
                              а какие гореть постоянно (0)*/
String ledSName[] = {"Bottom switch", "Middle switch", "Top switch", "OverFlow switch"};
byte  ledS[] = {B00000001, B00000010, B00000100, B00001000}; //значения, соответствующие включенному LED

/*================================================
  ================================================
  ================================================
  =============== SETUP ==========================
  ================================================
  ================================================
  ================================================
*/
void setup() {
  {
    // Timer0 уже используется функцией millis()
    // чтобы не мешать, прерывемся примерно посередине и вызываем ниже функцию "Compare A"
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
  }

  unsigned long currentMillis = millis();
  timerBlink.timerOn(currentMillis);
  timerPinRead.timerOn(currentMillis);
  timerRegistrDelay.timerOn(currentMillis);
  timer3H.timerOn(currentMillis);

  // настройка сдвигового регистра
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  // инициализация реле
  pinMode(PUMP, OUTPUT);
  pinMode(THREE_WAY_VALWE, OUTPUT);

  // инициализация "кнопок"
  for (byte i = 0; i < NUM; i++) {
    pinMode(GET(i), INPUT);
  }

  Serial.begin(9600);
  while (!Serial) {
  }
}

/*================================================
  ================================================
  ================================================
  ===============  FUNCTIONS =====================
  ================================================
  ================================================
  ================================================
*/

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();

  timerBlink.getIsTimerOn(currentMillis);
  timerPinRead.getIsTimerOn(currentMillis);
  timerRegistrDelay.getIsTimerOn(currentMillis);
  timer3H.getIsTimerOn(currentMillis);
  timer1M.getIsTimerOn(currentMillis);
  timer15M.getIsTimerOn(currentMillis);
}

void blinkS() {
  for (int i = 0; i < 4 ; i++) {
    if ((blinkF[i] == 1) && (switchState[i]) == 1) {
      ledsBurn = ledsBurn ^ ledS[i];
    }
  }
}

void systemStateChanging (byte inInt) {
  static byte st = 0;
  switch (st)
  {
    case 0: if ( ((fSystemState == TANK_IS_EMPTY) || (fSystemState == TANK_LESS_THAN_A_HALF))  && (!timer3H.timerOverflow))
        /*бак пустой полностью || уровень ниже половины) && 3ч не истек*/
      {
        st = 1;
        Serial.println("PUMP");
        timer3H.timerOff();
        pumpOnOff(PUMP_ON);
      }
      if ( ((fSystemState == TANK_IS_EMPTY) || (fSystemState == TANK_LESS_THAN_A_HALF)) && (timer3H.timerOverflow))
        /*(бак пустой полностью || уровень ниже половины) && 3ч истек*/
      {
        st = 2;
        Serial.println("3W opening");
        timer3H.timerOff();
        threeWayOnOff(TWV_ON);
        timer1M.timerOn(millis());
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 1: if (fSystemState == TANK_IS_FULL) {
        /*бак полный*/
        st = 0;
        Serial.println("FULL");
        pumpOnOff(PUMP_OFF);
        timer3H.timerOn(millis());
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 2: if (timer1M.timerOverflow) {
        /*таймер 1М истек*/
        st = 3;
        Serial.println("3w is open start PUMP OUT");
        timer15M.timerOn(millis());
        pumpOnOff(PUMP_ON);
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 3: if (timer15M.timerOverflow) {
        /*таймер 15м истек*/
        st = 1;
        Serial.println("3w close start pump IN");
        threeWayOnOff(TWV_OFF);
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 4: if (inInt == 0/*сброс*/) {
        st = 0;
        Serial.println("DROP");
        // просто переход в обчныое состояние
      }
      break;
  }
}

bool caseOfEmergency () {
  if ((fSystemState == WRONG_SENSOR_COMB) || (fSystemState == TANK_OVERFLOW)) {
    timer1M.timerOff();
    timer15M.timerOff();
    timer3H.timerOn(millis());
    threeWayOnOff(TWV_OFF);
    pumpOnOff(PUMP_OFF);
    if (fSystemState == WRONG_SENSOR_COMB) {
      /*неверная комбинация датчиков*/
      Serial.println("WRONG COMB");
      return true;
    }
    if (fSystemState == TANK_OVERFLOW) {
      /* перелив*/
      Serial.println("OVERFLOW");
      return true;
    }
  }
  else return false;
}


void pumpOnOff (int onOrOff) {
  if ((onOrOff == 1) && !fPumpIsOn) {
    digitalWrite(PUMP, PUMP_ON);
    fPumpIsOn = true;
    Serial.println("PUMP IS ON");
  }
  if ((onOrOff == 0) && fPumpIsOn) {
    digitalWrite(PUMP, PUMP_OFF);
    fPumpIsOn = false;
    Serial.println("PUMP IS OFF");
  }
}

void threeWayOnOff (int onOrOff) {
  if ((onOrOff == 1) && !f3wIsOpen) {
    digitalWrite(THREE_WAY_VALWE, TWV_ON);
    f3wIsOpen = true;
    Serial.println("3W IS ON");
  }
  if ((onOrOff == 0) && f3wIsOpen) {
    digitalWrite(THREE_WAY_VALWE, TWV_OFF);
    f3wIsOpen = false;
    Serial.println("3W IS OFF");
  }
}

void sensorsRead() {
  if (checkSwitchesCombination()) {
    if ((switchState[0] == 1) && (switchState[1] == 0) && (switchState[2] == 0) && (switchState[3] == 0)) {
      fSystemState = TANK_IS_EMPTY;
      //  Serial.println("EMPTY");
    }
    else if ((switchState[0] == 0) && (switchState[1] == 0) && (switchState[2] == 0) && (switchState[3] == 0)) {
      //  Serial.println("LESS THAN A HALF");
      fSystemState = TANK_LESS_THAN_A_HALF;
    }
    else if ((switchState[0] == 0) && (switchState[1] == 1) && (switchState[2] == 0) && (switchState[3] == 0)) {
      fSystemState = TANK_MORE_THAN_A_HALF;
      //   Serial.println("MORE THAN A HALF");
      // в этом состоянии насос не включаем
    }
    else if ((switchState[0] == 0) && (switchState[1] == 1) && (switchState[2] == 1) && (switchState[3] == 0)) {
      fSystemState = TANK_IS_FULL;
      //    Serial.println("FULL");
    }
  }
}

void led_switching (int switchNumber) {
  if (switchState[switchNumber] == HIGH) {
    bitWrite(ledsBurn, switchNumber, 1);
  }
  else {
    if (switchState[switchNumber] == LOW) {
      bitWrite(ledsBurn, switchNumber, 0);
    }
  }
}

bool checkSwitchesCombination() {
  if (switchState[3] == 1) { // если переполнение
    if (fSystemState !=  TANK_OVERFLOW) {
      fSystemState = TANK_OVERFLOW;
    }
    return false;
  }
  else if (((switchState[0] == 0) && (switchState[1] == 0) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 1) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 0) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 1) && (switchState[2] == 0) && (switchState[3] == 0))) {
    // Serial.println("WRONG COMBINATION");
    if (fSystemState != WRONG_SENSOR_COMB) {
      fSystemState = WRONG_SENSOR_COMB;
    }
    return false;
  }
  else { // если все ОК
    if ((fSystemState == WRONG_SENSOR_COMB) || (fSystemState == TANK_OVERFLOW)) {// если в прошлом вызове была аварийная ситуация
      fSystemState = SENSORS_IS_OK;
      systemStateChanging(0);// сброс
    }
    return true;
  }
}

void regWork() {
  // использует подготовленную другими функциями перменную ledsBurn
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, ledsBurn);
  digitalWrite(latchPin, HIGH);
}

void pinReading() {
  for (byte i = 0; i < NUM; i++) {
    switchState[i] = GET(i);
    if (switchState[i] != lastSwitchState[i]) {
      led_switching(i);
      sensorsRead();
    }
    lastSwitchState[i] = switchState[i];//обновляем значение последнего состояния переключателя
    systemStateChanging(1);
  }
}

void loop() {

  while (Serial.available() > 0) {
    byte sensors = Serial.parseInt();
    if (Serial.read() == '\n') {
      if (sensors == 9) {
        //
      }
    }
  }
}

 

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Привет!
Внимательно просмотрел проект. Как-то сложноват он мне показался :(

Я бы не стал ждать, когда воды останется на 15 мин, а, допустим, постоянно подпитывал основной бак при снижении уровня примерно менее 50% V. Не знаю как у вас с электричеством, но пока оно есть, лучше заполнить бак доверху.

Для контроля работоспособности насоса и переполнения бака я себе закупил (поставлю по весне) реле ПОтока. Иногда называется реле ПРОтока. Такие реле бывают и для сильно загрязненной среды. Гугл/Али в помощь.

Может, инфа пригодится.

Удачи.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Олег, и тебе привет. Спасибо за советы.

Про сложность... ну да, есть такой момент. Это я еще дисплей не прикрутил :) Шучу.

Про подкачку воды. У меня есть некоторая идея - лучше выкачать воду, а потом ее закачать, чем она будет постоянно в баке. Некоторый обмен, снижающий риск застоя. Но это теория. На практике - залив сверху, забор снизу. Так что это просто некоторое внутренее желание. Хочу, чтобы вода сначала почти вся ушла из бака, а потом залилась новая.

Перед отъездом на зиму я уже погонял водопровод в ручном режиме. Т.е. бак наполнял вручную. Трехходовой вентиль включил, насос включил, 15 минут подождал, вентиль выключил, бак накачал, насос выключил :)

И вот мой опыт показывает, что при разборе воды из дома скорость наполнения емкости скваженным насосом такова, что не нужно никаких 15-ти минутных запасов воды. Вкачивает больше, чем разбирает. Так что нижний поплавок буду ставить сантиметрах в 10-15 ото дна.

Что касается электричества - бак в котельной в цоколе, без электричества насосная станция ничего по дому не раздаст, самотек невозможен (только то, что выдавид 100 литровый гидроаккумулятор). А если я генератор включаю, то какая разница? А если лефтричества нет долго, я его полюбому включу.

Относительно реле потока (или протока). Я не хочу дожидаться, пока вода пойдет в переливную трубу. Зачем? Поплавковый выключатель (продублированный на всякий случай) вполне себе вариант. А учитывая, что насос останавливается другим поплавком, то вообще предположить, что вода перельется - ну меньше 1%. А на этот процент как раз труба перелива.

Думаю, можно еще усложнить программу и прикрутить таймер, что если насос не выключается в течении 15-ти минут (нравится мне это количество, что уж тут, но можно и 25 :) ) - выключить принудительно и дать аварийную сигнализацию.

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Навскидку такая схема получилась:

Вполне возможно,что я не совсем не прав.
PS. Картинка сделана в MS Visio,но какая-то серая :(

PPS. Не получается вставить pdf.
 

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

По моим соображениям, у твоей системы всего три основных состояния.
Ну и кучка дополнительных.
Опять-таки, на мой взгляд, получается два таймера, два уровнемера.

И еще на схеме забыл вставить запуск таймера по заполнению бака.
Как-то так.

Я тоже бросился было сначала код писать, но потом докумекал, что лучше сначала общий алгоритм прописать. Псоле ряда неудачных попыток остановился на опции "Фигуры горизонтальной функциональной  блок-схемы" из меню Бизнес-процессы.
Как-то так.

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

круто! спасибо за участие

единственное - картинка очень маленького разрешения, практически ничего не видно, кинь если не сложно на почту bob_shmidt ну там значек собаки и это все на mail.ru

про логику, дык ты посмотри на предыдущей странице темы у нас с Andy был целый эпос начиная с этого сообщения  и с блок схемами и т.д. и т.п.

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Вроде, исходник картинки беру высокого качества, но где-то внутри оно пережимается :(

Сейчас скину на почту.

PS. Я всю вашу ветку смотрел.

Гриша
Offline
Зарегистрирован: 27.04.2014

Очень тяжело читать эту тему, много развернутого кода и неформатных картинок, пожалуйста прочитайте эти посты

Пост для кода, самый первый (как свернуть код и другое)

Всегда
сворачивайте 
код.
больше 
10
строк.
Сильно 
долго 
проматывать

Пост для картинок   (без потери качества изображений!!!!!) пост 18

нажмите на нее

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

причесал тему слегка.
движок форума, конечно, не совсем мне понятен. некоторые мои посты я не могу отредактировать, нет такой "кнопки" (видимо это просиходит, если не просто написать новый пост, а нажатьь "ответить")
не понимаю, как удалить отдельный пост, приходится писать "заглушки"
очень жалко, что нет механизма личных сообщений...

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

Это конечно, смешно, но прошло больше, чем год :D;)
Когда я сел вспоминать, что я там наваял столько времени спустя, даже учитывая мою почти параноидальную склонность комментировать очевидные вещи, я с трудом справился :)
Поскольку задача передо мной сейчас была сугубо практическая - таки запустить автоматику, я решил оставить в стороне всякие излишества, типа дисплея (через неделю потеряшек в менюшках, отложил), типа часов реального времени (китайский шедевр при отключении упорно выдавал 0,6в с рабочей батарейки с ожидаемым сбросом времени), записи на SD (я уже и не вспомнил, что я таки хотел записывать:) )

Итак, вчера я все запустил. Работает. Вместо дисплея - 7 led-ов. Но, конечно, есть ряд нареканий и поводов к улучшению.
1. Логика кода была так жестко завязана на состояние поплавков в жесткой связке с светодиодами, что попытки поменять логику "мигания" подвела меня к необходимости вообще пересмотреть сам принцип сигнализации состояний. Иначе ничего не выходит.
2. При первом включении системы по умолчанию запускается полный цикл с прокачкой скважины и т.д. Но состояние поплавков считывается уже после того, как проходит цикл анализа состояния - в результате, даже если бак полный, полный цикл стартует с риском перелива (сегодня проверю, так ли это). Но почему и где это происходит - загадка. В своем же коде не могу разобраться :(
Попробую вообще с чистого листа начать.

Ну и плюшки на будущее.
1. Я хотел бы все-таки прикрутить сдвиговый регистр на вход (на выход уже работает). Пока не успешно.
2. Считывать состояние вентиля (там есть концевики), убрав один таймер.
3. Есть потребность летом один раз в сутки по любому прокачивать скважину, наполняя технический пруд.
4. Заказал на Али ультрасоник, очень уж хочется знать точно, сколько воды в баке. А то я его в пенал из ЭППС запаковал (иначе конденсатом заливает пол жестко) - ну и ничего не видно.
5. Дисплей, часы и SD :)

На память себе код

/*
  WaterSystem.cpp
  Created by Angbor, January 1, 2016
  Released into the public domain
*/
 
#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
  #else
  #include "WProgram.h"
  #endif

#include <WaterSystem.h>

WaterTimer::WaterTimer(long tLength, void f(), byte withFunc, String tName, String tTipe){
   timerName = tName;
   timerType = tTipe;
   withFunction = withFunc;
	ptrOnFuniction = f;
	timerLength = tLength;
	timerIsOn = false;
	previousMillis = 0;
}

WaterTimer::WaterTimer(long tLength, byte withFunc, String tName, String tTipe){
   timerName = tName;
   timerType = tTipe;
   withFunction = withFunc;
	timerLength = tLength;
	timerIsOn = false;
	previousMillis = 0;
}
                                                       
void WaterTimer::timerOn(unsigned long currentMillis) {
   timerOverflow = false;
   previousMillis = currentMillis;
	timerIsOn = true;
	//Serial.print(timerName);
        //Serial.println(" ON (msg from timer)");
}

void WaterTimer :: timerOff() {
	timerIsOn = false;
	//Serial.print(timerName);
       //Serial.println(" OFF (msg from timer)");
}

bool WaterTimer :: getIsTimerOn(unsigned long currentMillis){
	if ((timerIsOn) && (currentMillis - previousMillis >= timerLength))
		{
			if (timerType == "loop") {
				if (withFunction == 1) {
                               ptrOnFuniction();
				}
				previousMillis = currentMillis;
				}
			if (timerType == "line"){
		              //Serial.println("overflow (msg from timer)");
		              timerOverflow = true;
                              timerOff();
			}
			return true;
		}
		else {
			return false;
		}
}
/*
  WaterSystem.h
  Created by Angbor, January 1, 2016
  Released into the public domain
*/

#ifndef WaterSystem_h
#define WaterSystem_h

#include "Arduino.h"

class WaterTimer
{
	private:
		long timerLength; // длительность таймера
		unsigned long previousMillis;
		String timerName = "nill";
		void (*ptrOnFuniction)();
		byte withFunction = 0;

	public:
		WaterTimer(long tLength, void f(), byte withFunc, String tName, String tTipe);
		WaterTimer(long tLength, byte withFunc, String tName, String tTipe);
		void timerOn(unsigned long currentMillis);
		void timerOff();
		bool getIsTimerOn(unsigned long currentMillis);
		bool timerOverflow = false;
		String timerType = "loop"; // or "line"
		bool timerIsOn = false;  // Текущее состояние On или Off
};

#endif
#include <WaterSystem.h>

// определяем пины для переключателей
#define BOTTOM_SWITCH   2
#define MIDDLE_SWITCH   3
#define TOP_SWITCH      4
#define OVERFLOW_SWITCH 5

//=== замедления циклов ======
#define READ_PIN_DELAY 100
#define REGISTR_DELAY  150
#define BLINK_DELAY    300

//=== реле ======
#define PUMP            9
#define PUMP_ON         1
#define PUMP_OFF        0
#define THREE_WAY_VALWE 10
#define TWV_ON          1
#define TWV_OFF         0

//потом надо перевести это дело как-то в часы и минуты (соответственно таймеру). сейчас пока в милисекундах
#define DOWNTIME        10800000// 3 часа, таймер 3H время простоя, после которого обязательна прокачка.
#define TWV_DELAY       1200000//  20 мин., таймер 15M время прокачки
#define TWV_OPENING     20000// 20 сек., таймер 1M время на открытие вентиля (потом надо заменить это на чтение концевика 3W

// прототипы и укзатели функций
void blinkS();
void pinReading();
void regWork();

void (*ptrBlinkS)() = blinkS;
void (*ptrPinReading)() = pinReading;
void (*ptrRegWork)() = regWork;

// -- таймеры  ---------------
#define WITH_FUNCTION    1
#define WITHOUT_FUNCTION 0

WaterTimer timerBlink(BLINK_DELAY, ptrBlinkS, WITH_FUNCTION, "BLINK_DELAY", "loop");
WaterTimer timerPinRead(READ_PIN_DELAY, ptrPinReading, WITH_FUNCTION, "READ_PIN_DELAY", "loop");
WaterTimer timerRegistrDelay(REGISTR_DELAY, ptrRegWork, WITH_FUNCTION, "REGISTR_DELAY", "loop");
WaterTimer timer3H(DOWNTIME, WITHOUT_FUNCTION, "3H", "line");
WaterTimer timer1M(TWV_OPENING, WITHOUT_FUNCTION, "1M", "line");
WaterTimer timer15M(TWV_DELAY, WITHOUT_FUNCTION, "15M", "line");

bool fPumpIsOn  = false;
bool f3wIsOpen  = false;
bool firstSTart = true; // отслеживание первого включения платы и изменение флага в 3-х часовом таймере

// описание состояний
#define TANK_IS_EMPTY         0
#define TANK_LESS_THAN_A_HALF 1
#define TANK_MORE_THAN_A_HALF 2
#define TANK_IS_FULL          3
#define WRONG_SENSOR_COMB     4
#define TANK_OVERFLOW         5
#define SENSORS_IS_OK         6 // заглушка для функции проверки аварии, когда в прошлом вызове была авария, а сейчас нет и еще нет сигнала от сенсоров, чтобы переписать состояние
byte fSystemState = 0;


// ====== для цикла чтения пинов
#define NUM 4
#define GET(b) digitalRead(arr[b])
const byte arr[] = {BOTTOM_SWITCH, MIDDLE_SWITCH, TOP_SWITCH, OVERFLOW_SWITCH};

// устанавливаем управление сдвиговым регистром
const int latchPin  = 8;
const int clockPin  = 12;
const int dataPin   = 11;
uint8_t   ledsBurn  = 0; //переменная для передачи в регистр

// =========================
byte switchState[]     = {0, 0, 0, 0}; // состояние переключателей
byte lastSwitchState[] = {0, 0, 0, 0}; // предыдущее состояние
byte blinkF[]          = {1, 0, 0, 1}; /*флаги для включения blink, здесь указываем по умолчанию какие диоды будут мигать (1),
                              а какие гореть постоянно (0)*/
byte  ledS[] = {B00000001, B00000010, B00000100, B00001000}; //значения, соответствующие включенному LED

/*================================================
  =============== SETUP ==========================
  ================================================
*/
void setup() {
  {
    // Timer0 уже используется функцией millis()
    // чтобы не мешать, прерывемся примерно посередине и вызываем ниже функцию "Compare A"
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
  }
/*Serial.begin(9600);
  while (!Serial) {
  }*/
  unsigned long currentMillis = millis();
  timerBlink.timerOn(currentMillis);
  timerPinRead.timerOn(currentMillis);
  timerRegistrDelay.timerOn(currentMillis);
  timer3H.timerOn(currentMillis);
  timer3H.timerOff();

  // настройка сдвигового регистра
  pinMode(latchPin, OUTPUT);//8
  pinMode(clockPin, OUTPUT);//12
  pinMode(dataPin, OUTPUT);//11

  // инициализация реле
  pinMode(PUMP, OUTPUT);//9
  pinMode(THREE_WAY_VALWE, OUTPUT);//10

  for (byte i = 0; i < NUM; i++) {// 2,3,4,5
    pinMode(GET(i), INPUT);
  }
  ledsBurn = ledsBurn ^ B10000000; // включаем диод "питание ON"
}

/*================================================
  ===============  FUNCTIONS =====================
  ================================================
*/

SIGNAL(TIMER0_COMPA_vect)
{
  unsigned long currentMillis = millis();

  timerBlink.getIsTimerOn(currentMillis);
  timerPinRead.getIsTimerOn(currentMillis);
  timerRegistrDelay.getIsTimerOn(currentMillis);
  timer3H.getIsTimerOn(currentMillis);
  timer1M.getIsTimerOn(currentMillis);
  timer15M.getIsTimerOn(currentMillis);
}

void blinkS() { // отвечает за мигание
  for (byte i = 0; i < NUM /*4*/ ; i++) {
    if (blinkF[i] == 1 && switchState[i] == 1) {
    ledsBurn = ledsBurn ^ ledS[i];
    }
  }
}

void systemStateChanging (byte inInt) {
  static byte st = 0;
  switch (st)
  {
    case 0:
     if ( fSystemState == TANK_IS_EMPTY  && !timer3H.timerOverflow  && !firstSTart)
        /*бак пустой полностью && 3ч не истек и это не первое включение платы*/
      {
        st = 1;
        timer3H.timerOff();
        pumpOnOff(PUMP_ON);
      }
      if ((fSystemState == TANK_IS_EMPTY &&  timer3H.timerOverflow) || firstSTart)
      /*бак пустой полностью  && 3ч истек*/
      {
        st = 2;
        firstSTart = false;
        timer3H.timerOff();
        threeWayOnOff(TWV_ON);
        timer1M.timerOn(millis());
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 1: 
    if (fSystemState == TANK_IS_FULL) {
        /*бак полный*/
        st = 0;
        pumpOnOff(PUMP_OFF);
        timer3H.timerOn(millis());
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 2: 
    if (timer1M.timerOverflow) {
        /*таймер 1М истек*/
        st = 3;
        timer15M.timerOn(millis());
        pumpOnOff(PUMP_ON);
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 3: 
    if (timer15M.timerOverflow) {
        /*таймер 15м истек*/
        st = 1;
        threeWayOnOff(TWV_OFF);
      }
      if (caseOfEmergency ()) {
        st = 4;
      }
      break;
    //==============================================
    case 4: 
    if (inInt == 0/*сброс*/) {
        st = 0;
        // просто переход в обычное состояние
      }
      break;
  }
}

bool caseOfEmergency () {
  if ((fSystemState == WRONG_SENSOR_COMB) || (fSystemState == TANK_OVERFLOW)) {
    timer1M.timerOff();
    timer15M.timerOff();
    timer3H.timerOn(millis());
    threeWayOnOff(TWV_OFF);
    pumpOnOff(PUMP_OFF);
    if (fSystemState == WRONG_SENSOR_COMB) {
      /*неверная комбинация датчиков*/
      return true;
    }
    if (fSystemState == TANK_OVERFLOW) {
      /* перелив*/
      return true;
    }
  }
  else return false;
}


void pumpOnOff (int onOrOff) {
  if ((onOrOff == 1) && !fPumpIsOn) {
    digitalWrite(PUMP, PUMP_ON);
    ledsBurn = ledsBurn ^ B00010000;
    fPumpIsOn = true;
  }
  if ((onOrOff == 0) && fPumpIsOn) {
    digitalWrite(PUMP, PUMP_OFF);
    ledsBurn = ledsBurn ^ B00010000;
    fPumpIsOn = false;
  }
}

void threeWayOnOff (int onOrOff) {
  if ((onOrOff == 1) && !f3wIsOpen) {
    digitalWrite(THREE_WAY_VALWE, TWV_ON);
    ledsBurn = ledsBurn ^ B00100000;
    f3wIsOpen = true;
  }
  if ((onOrOff == 0) && f3wIsOpen) {
    digitalWrite(THREE_WAY_VALWE, TWV_OFF);
    ledsBurn = ledsBurn ^ B00100000;
    f3wIsOpen = false;
  }
}

void sensorsRead() {
  if (checkSwitchesCombination()) {
    if ((switchState[0] == 1) && (switchState[1] == 0) && (switchState[2] == 0) && (switchState[3] == 0)) {
      fSystemState = TANK_IS_EMPTY;
    }
    else if ((switchState[0] == 0) && (switchState[1] == 0) && (switchState[2] == 0) && (switchState[3] == 0)) {
      fSystemState = TANK_LESS_THAN_A_HALF;
    }
    else if ((switchState[0] == 0) && (switchState[1] == 1) && (switchState[2] == 0) && (switchState[3] == 0)) {
      fSystemState = TANK_MORE_THAN_A_HALF;
      // в этом состоянии насос не включаем
    }
    else if ((switchState[0] == 0) && (switchState[1] == 1) && (switchState[2] == 1) && (switchState[3] == 0)) {
      fSystemState = TANK_IS_FULL;
    }
  }
}

void led_switching (int switchNumber) {
  if (switchState[switchNumber] == HIGH) {
     bitWrite(ledsBurn, switchNumber, 1);
  }
  else {
    if (switchState[switchNumber] == LOW) {
      bitWrite(ledsBurn, switchNumber, 0);
    }
  }
}

bool checkSwitchesCombination() {
  if (switchState[3] == 1) { // если переполнение
    if (fSystemState !=  TANK_OVERFLOW) {
      fSystemState = TANK_OVERFLOW;
    }
    return false;
  }
  else if (((switchState[0] == 0) && (switchState[1] == 0) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 1) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 0) && (switchState[2] == 1) && (switchState[3] == 0)) ||
           ((switchState[0] == 1) && (switchState[1] == 1) && (switchState[2] == 0) && (switchState[3] == 0))) {
    if (fSystemState != WRONG_SENSOR_COMB) {
      fSystemState = WRONG_SENSOR_COMB;
    }
    return false;
  }
  else { // если все ОК
    if ((fSystemState == WRONG_SENSOR_COMB) || (fSystemState == TANK_OVERFLOW)) {// если в прошлом вызове была аварийная ситуация
      fSystemState = SENSORS_IS_OK;
      systemStateChanging(0);// сброс
    }
    return true;
  }
}

void regWork() {
  // использует подготовленную другими функциями перменную ledsBurn
  digitalWrite(latchPin, LOW);
  shiftOut(dataPin, clockPin, MSBFIRST, ledsBurn);
  digitalWrite(latchPin, HIGH);
}

void pinReading() {
  for (byte i = 0; i < NUM; i++) {
    switchState[i] = GET(i);
    if (switchState[i] != lastSwitchState[i]) {
      led_switching(i);
      sensorsRead();
    }
    lastSwitchState[i] = switchState[i];//обновляем значение последнего состояния переключателя
    systemStateChanging(1);
  }
}

void loop() {

}

 

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

В догонку.

Перелива нет, если после подачи напряжения с полным баком нчинается полный цикл, то сначала идет прокачка на улицу, потом в дом и сразу же срабатывает отключение по верхнему поплавку.

Так что пока полет нормальный, всем доволен.

Не уверен, что в ближайшее время полезу что-то там менять. И так дел куча :)

hijin
Offline
Зарегистрирован: 20.08.2017

Прошу прощения что вторгаюсь в Вашу тему но хочю предложить варианты работы насоса (у меня он работает в колодце с малым дебетом воды) без ардуино стоимомтью 50 центов а дальше( заисит только от потребляеьой нагрузки) в файле насос есть две схемы одна на реле вторая на оптопарах работа схеты на реле при включении устройства если верхний датчик находится в воде (7) ток через открытый транзистор включает реле и замыкает ево же контакты так как контакты нижнего датчика (8) находятся в воде ток через контакты реле самоблокирует реле до тех пор пока вода не опустится ниже контактов нижнего уровня. Цепь разомкнется и будет ждать контакта на верхнем уровне (схема представлена для электродных датчиков (я использовал стержни от батареек так как они наименее подвержны электролизу при постоянном токе) На схеме обозначено одно реле с одной групой контактов. Для комутации высокого напряжения необходимо второе реле которе подключается паралельно первому или если применять одно то с двумя группами контактов Цена только от стоимости реле На второй схеме практически тот же принцип но с использованием оптопар Я использовал самую рпспространенную РС817 и МОС30,,,, причем МОС30.... это уже управление нагрузкой (его также можно заменить на реле или с его выводов включив симистор управлять более мошной нагрузкой). И цена ниже и габариты Транзисторы могут преминятся любые на напряжение источника питания я применял КТ315В Г также о второй схеме необжодимо увеличить сопротивление резисторов при увелечении напряжения

схемы  и многое другое можнонайтм по ссылке

http://flprog.ru/forum/18-2165-2

Angbor
Angbor аватар
Offline
Зарегистрирован: 26.10.2015

базиба :)

как обычно, спустя год .... :)

все до сих пор работает. Из реально актуальных вопросов к доработке только один - таймер по началу закачивания бака. И то только потому, что сделал переключение в ручной режим и вентиль, переводящий вывод насоса на поли. И этот самый вентиль в котельной :/

Окрываешь вентиль на полив, включаешь вручную насос - пошел поливать. Устал, выключил насос, забыл перекрыть вентиль. Включилась автоматика - в котельной все поплыло...

Но сейчас я занялся автоматической кормушкой для кур, это поактуальнее, позже выложу проектик.