Проект "умной" теплицы.

Altivex
Offline
Зарегистрирован: 07.11.2016

День добрый !  Прошу помощи с оптимизацией кода на ардуино. 
Хотелось бы сделать что-то типа "умной"  теплицы.

Начал с датчика температуры. Под руками был аналоговый lm335. Раньшн подключал его к ардуино , проблем этот датчик не вызывает.

Далее по замыслам , нужно было добавить  пороговую температуру и гистерезис.

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

 Управление кнопочкой честно содрал отсюда) Так же посмотрел проект регулировки яркости светодиода.

И код даже работает. Правда , энкодер иногда гонит чушь - то есть кручу вправо - счетчик работает четко - один щелчок и плюс одно значение, а вот влево - может на щелчок прибавить, может отнять...  Но это может быть неисправен и энкодер.  Так же не обращайте внимание на analog_write -просто мне  по яркости свечения светододов удобнее отслеживать что происходит. В планах - подключитьдисплей для вывода информации.
По коду-  оптимизация заключается в добавлении процедуры.( Хотя бы, знаю что написано криво, но это криво работает и я даже понимаю как) Но не совсем понимаю как. 
Просто по подобной схеме нужно будет настроить датчик влажности почвы на полив и датчик влажности воздуха на проветривание помещения.  И получается, что код настройки  значений на энкодере будет повторяться 4 раза...  Мы же должны объявить процедуру , а в последующем уже обращаться к ней, так? 

И не нужно ли записывать отдельной командой записывать в память ардуино значения пороговой температуры и гистерезиса?
Код писал с комментариями, так что если где совсем мысль не верна - ткните носом пожалуйста.
 

int raw = 0; //значение аналогового выхода.
int regim = 1;// переменные отвечающие за режим кнопки
int flag = 0;//
float temp = 0;//температура в Кельвинах
float tempc = 0;// температура в Цельсиях
int tempz = 1; // температура срабатывания датчика( изначально задано 1 градус цельсия, чтобы температура не опустилась ниже нуля).
int gist = 0; // температурный гистерезис
int fadeAmount = 1;        // шаг изменения гистерезиса
unsigned long currentTime;
unsigned long loopTime;
const int pin_A = 2;       // pin 2 выходы энкодера
const int pin_B = 3;       // pin 3
unsigned char encoder_A; //значения выхода энкодера
unsigned char encoder_B;
unsigned char encoder_A_prev = 0;
void setup() {
  Serial.begin(9600);
  pinMode( A0, INPUT );//установка а0,1 как аналоговый вход
  pinMode( A1, INPUT );
  pinMode(9, OUTPUT);         // устанавливаем pin 9 как выход
  pinMode(10, OUTPUT);
  pinMode(pin_A, INPUT);
  pinMode(pin_B, INPUT);
  currentTime = millis();
  loopTime = currentTime;
}
void loop()
{

  if (digitalRead(A1) == HIGH && flag == 0) //если кнопка нажата
    // и перемення flag равна 0 , то ...
  {
    regim++;
    flag = 1; //это нужно для того что бы с каждым нажатием кнопки происходило только одно действие
    if (regim > 2) //ограничим количество режимов
    {
      regim = 1; //так как мы используем только одну кнопку,то переключать режимы будем циклично
    }
  }
  if (digitalRead(A1) == LOW && flag == 1) //если кнопка НЕ нажата и переменная flag равна - 1 ,то ...
  {
    flag = 0; //обнуляем переменную "knopka"
  }
  if (regim == 1) //первый режим
  {
    currentTime = millis();   //блок обработки энкодера, задаём пороговую температуру tempz
    if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс (200 Гц)
      encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
      encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
      if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю
        if (encoder_B) {
          // выход В в полож. сост., значит вращение по часовой стрелке, увеличиваем пороговую температуру, не более чем до50, шаг 1 градус.
          if (tempz + fadeAmount <= 50) tempz += fadeAmount;
        }
        else {
          // выход В в 0 сост., значит вращение против часовой стрелки, уменьшаем гистерезис, но не ниже 0
          if (tempz - fadeAmount >= 0) tempz -= fadeAmount;
        }
      }
      encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла
      loopTime = currentTime;
      analogWrite(9, tempz);   // устанавливаем вывод значения на 9 ножку, смотрим.
    }
  }
  if (regim == 2) //второй режим
  {
    currentTime = millis();   //блок обработки энкодера, задаём пороговую температуру гистерезиса
    if (currentTime >= (loopTime + 5)) { // проверяем каждые 5мс (200 Гц)
      encoder_A = digitalRead(pin_A);     // считываем состояние выхода А энкодера
      encoder_B = digitalRead(pin_B);     // считываем состояние выхода B энкодера
      if ((!encoder_A) && (encoder_A_prev)) {  // если состояние изменилось с положительного к нулю
        if (encoder_B) {
          // выход В в полож. сост., значит вращение по часовой стрелке
          // увеличиваем гистерезис, не более чем до50, шаг 1 градус.
          if (gist + fadeAmount <= 50) gist += fadeAmount;
        }
        else {
          // выход В в 0 сост., значит вращение против часовой стрелки
          // уменьшаем гистерезис, но не ниже 0
          if (gist - fadeAmount >= 0) gist -= fadeAmount;
        }
      }
      encoder_A_prev = encoder_A;     // сохраняем значение А для следующего цикла
      loopTime = currentTime;
      analogWrite(10, gist); // устанавливаем вывод значения на 9 ножку, смотрим.
          }
  }

  //блок считывания температуры с лм335
  {
    raw = analogRead(A0);
    temp = ( raw / 1023.0 ) * 5.0 * 1000 / 10;//перевод аналоговго сигнала в Кельвины
    tempc = temp - 273.15;                  //получаем температуру в градусах Цельсия
    if (tempz < tempc + gist)               // Задаётся температура ( пороговая) которая сравнивается с текущей температурой плюс температура гистерезиса: если заданная температура(пороговая) меньше температуры с датчика + гистерезис, то выход ставим в "высокое состояние" )
      digitalWrite(13, HIGH);
    else
      digitalWrite(13, LOW);
    if (tempz > tempc + gist)               // если заданная температура(пороговая) больше температуры с датчика + гистерезис, то выход ставим в "низкое состояние"
      digitalWrite(13, LOW);
    else
      digitalWrite(13, HIGH);
    delay(20);
    Serial.println(temp);                   //опрос порта на вывод температуры в кельвинах и цельсиях
    Serial.println(tempc);
    Serial.println(tempz);
    Serial.println(gist);
  }
}


 

bwn
Offline
Зарегистрирован: 25.08.2014

В принципе, все задаваемые пороговые значения лучше хранить в EEPROM, при обесточивании устройства не понадобится вводить заново.

 

Altivex
Offline
Зарегистрирован: 07.11.2016

За eeprom- спасибо) Но пока что , это слишком " круто" для меня.Я пока дисплей то " знаю" как прикрутить только через библиотеку...
Есть энное кол- во литийона -  пока на нем сделаю резервное питание. 

Небольшое замечание по поводу энкодера - на землю стоят кондёры по 68 нанофарад.  Так , что хоть какая-то защита от дребезга есть.

Просто не пойму, почему в одну сторону всё четко считается, а в обратную - бардак. 

ВН
Offline
Зарегистрирован: 25.02.2016

Altivex пишет:
Просто не пойму, почему в одну сторону всё четко считается, а в обратную - бардак.

механика несимметричная

попробуйте такой вариант 


// Мой вариант подключения платы энкодера KY-040
// к выходам энкодера подключены конденсаторы по 33н 
// на плате уже исходно стоят подтягивающие резисторы  по 10к
// число проверок изменяется условием для  i, больше 16 не проверял 


#define CLK 2           //clk , подключен к пину с прерыванием INT0 
#define DT  3           //dt, может быть на произвольном цифровом пине
volatile byte enc = 0;  // счетчик энкодера от 0 до 255

void encoder()  //  Обработка прерывания 
{
  char j=0;
  for(byte i=0; i<8; i++)                 // несколько раз читаем состояния пина dt
    {if(digitalRead(DT)) j++; else j--; } // для защиты от дребезга контактов
      if(j>0) enc--;  else enc++;         // и по результатам тестов принимаем решение 
} 
 
void setup()
{
pinMode(CLK, INPUT);    //digitalWrite(CLK, HIGH); 
pinMode(DT, INPUT);     //digitalWrite(DT, HIGH); 
  attachInterrupt(0, encoder, FALLING); // clk вешаем на прерывание 0 по спаду импльса 
    Serial.begin(9600);
}

void loop()
{
Serial.println(enc);
}

зы. здесь на форуме есть темы и по енкодеру, и по умным теплицам, где много чего реализовано .... пользуйтесь поиском

 

Altivex
Offline
Зарегистрирован: 07.11.2016

Искал, находил, читал) Кое что , возможно, к себе утащу. Сейчас , скорее, все -же вопрос о том , как оптимизировать код.

Ну или же , допишу как есть, проверю работу и оформлю тему в проектах.  А там уже и код перепилим. 

ВН
Offline
Зарегистрирован: 25.02.2016

пост по энкодеру дополнил см. выше,   это же и к вопросу оптимизации подходит

какие там емкости подключены к выходам энкодера 33, 68 или 100 нан - не принципиально.

Altivex
Offline
Зарегистрирован: 07.11.2016

Ух , появилось время, проверил код. Увы - тоже самое. В одну сторону считает нормально , во вторую -не пойми как. убрал макетку, подсоединил шлейф - тоже самое. Сменил плату  -  на нано. Тоже самое. Подозреваю что такая фигня происходит из-за дребезга. Ибо считать может 1,2 ,3..... 9 и  при следующем "шажке" - 16, 17... 

А назад прокручиваешь - 17, 16, -233, -232, -231....-225, -189.... 

В общем не могу понять - где косяк то всплыл... Ёмкостей мало , или 3 тий китайский энкодер - совсем уж китайский?

tawR
Offline
Зарегистрирован: 18.11.2016

#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Ethernet.h>

//DHT temperature and humidity
#define dhtPin 7
#define dhType DHT11
DHT dhtSensor(dhtPin,dhType);

//dallas temperature
#define bus A2 //
OneWire oneWire(bus); // шина для чтения датчика
DallasTemperature dallaSensor(&oneWire);

//fork 
#define moisturePin A0
#define ledPin 13

#define sensorCount 4
float sensorData[sensorCount];
char *sensorName[]= {"dht_t", "dht_h", "ground_t", "ground_h", "brightness"};
#define dhtTemp 0
#define dhtHumidity 1
#define dallas 2
#define fork 3
#define brightness 4

//Block C ----------------------------------------------------------------------------
#include <ArduinoJson.h>
#include <Servo.h>
#define INPUT_BUFFER_LEN 128
#define servoPin 5
#define pumpRelayPin 14

Servo servo;

bool isProtectionMode = false;
bool servoState = false;
bool pumpState = false;
bool lightOn = true;
bool handRefreshTime;
bool flags[3];

char *flagName[] = {"servState","pumpState","isProtectionMode"};
char inputBuffer[INPUT_BUFFER_LEN] = "";

short int dayTime;
short int nighTime;

//------------------------------------------------------------------------------------

char* sensorDescription[] = {"Air temperature in *C", "Air humidity in %", "Soil temperature in *C", "Soil humidity in %", "Brightness in %"}; 

#define brightneSensor A1

//Ethernet

byte mac[] = {0xBA, 0xDC, 0xC0, 0xFE, 0x00, 0x00};

IPAddress ip(192, 168, 1, 250);
IPAddress dnsServerIP(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);
EthernetClient client;
IPAddress iot_add(52,4,204,67);

bool openWindow = false;
short int  reacTime = 3; //задержка системы в секундах

void ifBadEthernetSetUp() {
  if (Ethernet.begin(mac) == 0)
  {
    Serial.println("Failed to configure Ethernet using DHCP");
    Ethernet.begin(mac, ip, dnsServerIP, gateway, subnet);
  }
}

void connectToThigworx() {
  if (client.connect(iot_add, 80)) {
    Serial.println("iot connected");
    if(client.connected()){
       Serial.println("client connected");
    client.print("POST /Thingworx/Things/");
    client.print(thingName);
    client.print("/Services/");
    client.print(serviceName);
    client.print("?appKey=");
    client.print(appKey);
    client.print("&method=post&x-thingworx-session=true");
    }
  }
}
// CCCC
void sendFlags(){
   for(short i=0; i<3; ++i){
    client.print("&");
    client.print(flagName[i]);
    client.print("=");
    client.print(flags[i]);
  }
}
//CCCCC

void sendData(){
  for(short i=0; i<sensorCount; ++i){
    client.print("&");
    client.print(sensorName[i]);
    client.print("=");
    client.print(sensorData[i]);
  }
  sendFlags();
//    client.print("&");
//    client.print("windowOpen");
//    client.print("=");
//    client.print(openWindow);
}

void closePack(){
  client.println(" HTTP/1.1");
  client.println("Accept: application/json");
  client.print("Host: ");
  client.println(iot_server);
  client.println("Content-Type: application/json");
  client.println();
}

float readDHTemperature(){
  return dhtSensor.readTemperature();
}

float readDHTHumidity(){
  return dhtSensor.readHumidity();
}

float readDallas(){
   dallaSensor.requestTemperatures();
  return dallaSensor.getTempCByIndex(0);
}

float readMoisture(){
  return analogRead(moisturePin)/10.23;
}

int readBrightness(){
  float valueBrightness=abs((1000-analogRead(brightneSensor))/10);

 return valueBrightness;
}

void getInfo(){
//  sensorData[dhtTemp] = readDHTemperature();
//  sensorData[dhtHumidity]= readDHTHumidity();
//  sensorData[dallas] = readDallas();
//  sensorData[fork] = readMoisture();
//  sensorData[brightness] = readBrightness(); 
  sensorData[dhtTemp] = random(0,60);
  sensorData[dhtHumidity]= random(5,100);
  sensorData[dallas] = random(0,20);
  sensorData[fork] = random(0,100);
  sensorData[brightness] = 5;     
}

void showInfo(){
  for(short int i=0; i<sensorCount; ++i){
    Serial.println(String(sensorDescription[i]) + ": "+String(sensorData[i]));
  }
   Serial.println("-------------------------------");
    Serial.println("\t\n");
}

void blinkledOn(){
  digitalWrite(ledPin, HIGH);
}

void blinkledOFF(){
  digitalWrite(ledPin, LOW);
}

void setReacTime(){
  if(handRefreshTime){
    if(readBrightness() < 20){
    reacTime = nighTime;
  }else{
    reacTime = dayTime;
  }
  }
  if(readBrightness() < 20){
    reacTime =5 ;
  }else{
    reacTime =1;
  }
}

//Block C ----------------------------------------------------------------------------
void getServerResponse(){
  short int i;
  bool inputFlag=false;
  char inputSmb;
  while(client.connected()){
    while(client.available()){
      inputSmb = client.read();
      if(inputSmb == '{'){
        inputFlag= true;
      }else{
        if(inputSmb == '}'){
          inputFlag = false;
        }
      }
      if(inputFlag == true){
        inputBuffer[i] = inputSmb;
        i++;
      }
    }
  }
  inputBuffer[i] = '}';
  inputBuffer[i+1] = '\0';
}

void responseParser(){
  StaticJsonBuffer<INPUT_BUFFER_LEN> jsonBuffer;
  JsonObject& jsonArray = jsonBuffer.parseObject(inputBuffer);
  
  servoState = jsonArray["servState"];
  pumpState = jsonArray["PumpState"];
  lightOn = jsonArray["lightState"];
  isProtectionMode = jsonArray["isProtectionMode"];
  handRefreshTime = jsonArray[""]; 
  dayTime =  jsonArray[""]; 
  nighTime= jsonArray[""]; ; 
}

void workWithThingWorx(){
  connectToThigworx();
  sendData();
  closePack();
  getServerResponse();
  responseParser();
  client.stop();
}

void tesTBlocC(){
  Serial.println(servoState);
  Serial.println(pumpState);
  Serial.println(isProtectionMode);
  Serial.println(lightOn);
}

void getFlagState(){// {"servState","pumpState","isProtectionMode",reacTime"}
  flags[0]=servoState;
  flags[1] = pumpState;
  flags[2] = isProtectionMode;
  
}


void controlDevices() {
  if (servoState)
  {
    servo.write(120);
  }
  else
  {
    servo.write(90);
  }
  if(lightOn){
   digitalWrite(ledPin,HIGH);
  }
  else{
     analogWrite(ledPin,LOW);
  }
  // Помпа
  if(pumpState){
    digitalWrite(pumpRelayPin, HIGH);
  }
 //  //?
}
#define max_dht_t 15
#define max_ground_t 15
#define max_dht_hum 30
#define max_ground_hum 15
#define min_brightness 40

void protectedMode(){//{"dht_t", "dht_h", "ground_t", "ground_h", "brightness"};
  if(sensorData[0]>= max_dht_t || sensorData[2]>=max_ground_t|| sensorData[1]>= max_dht_hum){
    servoState= true;
  }
  if(sensorData[3]>=max_ground_hum){
    pumpState = true;
  }
  if(sensorData[4]<=min_brightness){
    lightOn = true;
  }
}

//-------------------------------------------------------------------------------------

void setup() {
  // put your setup code here, to run once:
 Serial.begin(9600);
 dhtSensor.begin();
 pinMode(ledPin, OUTPUT);
 servo.attach(servoPin);
 dallaSensor.begin();
 
ifBadEthernetSetUp();
}

void loop() {
  blinkledOn();
  getInfo();
  getFlagState();
  workWithThingWorx();
  if(flags[2]){
    protectedMode();
  }
  controlDevices();
  showInfo();
  setReacTime();
  tesTBlocC();
  delay(reacTime*1000);
  blinkledOFF();
}

 

Altivex
Offline
Зарегистрирован: 07.11.2016

Готовый код - не вариант. Даже с учетом того , что есть dht11.

Меня больше другое интересует - Если писать обращение к энкодеру через код Грея - все работатет. Ну почти все. То есть - крутим энкодер вправо , мониторим порт и видим  стрелочку вправо ->, если влево , то <-.И всё работает чётко. Но стоит добавить счетчик ( +1 вправо и -1 влево) , как начинается черт знает что. Стрелочки всегда правильно выдаёт , а вот цифры выводит через раз. Иногда выводит просто стрелочку и всё...

tawR
Offline
Зарегистрирован: 18.11.2016

Я думаю, что стоит посмотреть в сторону фильтра Калмана. Тут неплохая статья.