Forrest Wang
Published © GPL3+

Smart Coop - init

A smart coop with four-way door and cleaner can be scheduled at specific time from cloud.

BeginnerWork in progressOver 1 day74
Smart Coop - init

Things used in this project

Hardware components

Arduino Nano Esp32
×1
Male/Female Jumper Wires
Male/Female Jumper Wires
×1
DFRobot Brushless DC Motor with Encoder 12V 159RPM
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×2
AC/DC Power Supply, 12 V
AC/DC Power Supply, 12 V
×1
Resistor 10k ohm
Resistor 10k ohm
×3
Breadboard (generic)
Breadboard (generic)
×1

Software apps and online services

Arduino IDE
Arduino IDE
Arduino IoT Cloud
Arduino IoT Cloud

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Drill, Screwdriver
Drill, Screwdriver

Story

Read more

Schematics

coop-circuit

Code

ESP32_project_oct27a

C/C++
#include "arduino_secrets.h"
#include <ESP32Servo.h>
#include <LinkedList.h>
#include <TimeLib.h>
#include <ESPDateTime.h>


/*
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/4112e99c-79b3-4482-9968-b32a3349c579

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  int servo_degree;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"

#define PIN_PWN 11
#define PIN_DIRECION 10
#define PIN_TRIGGER 9
#define PIN_INNER_SERVO 8
#define PIN_OUTER_SERVO 7
#define PIN_MOTOR_POSITION A0
#define CHECK_MOTOR_DURATION 200

struct Task {
  int id;
  int func;
  int time;  //unit: s
};
String last_version = "";
String circular_command = "";
bool hasTime = false;
int currentDay = 0;
bool isWifiConnected = false;
Servo innerServo;
Servo outerServo;
LinkedList<Task> taskList = LinkedList<Task>();
int currentStatus = 0;
long last_check_time = 0;
int motor_pwm = 0;
bool isCleaning = false;
int statusBeforeCleaning = 0;

void setup() {
  pinMode(PIN_PWN, OUTPUT);
  pinMode(PIN_DIRECION, OUTPUT);
  pinMode(PIN_TRIGGER, INPUT_PULLUP);
  pinMode(PIN_MOTOR_POSITION, INPUT);
  stopMotor();
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500);

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);

  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information youll get.
     The default is 0 (only errors).
     Maximum is 4
  */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
  innerServo.attach(PIN_INNER_SERVO);
  outerServo.attach(PIN_OUTER_SERVO);
}

void loop() {
  ArduinoCloud.update();
  // Your code here
  setRemoteTime();
  // If can't connect internet, just set local time through serial input
  setLocalTime();
  scheduleTask();
  checkCurrentMotorState();
}

void checkCurrentMotorState() {
  if (ArduinoCloud.connected() != 1) {
    return;
  }
  long current = millis();
  if (current - last_check_time > CHECK_MOTOR_DURATION) {
    last_check_time = current;
    // change the resolution to 12 bits and read A0
    analogReadResolution(12);
    int sensorValue = analogRead(PIN_MOTOR_POSITION);
    // Map the analog value to 0, 1, or 2
    switch (sensorValue) {
      case 0 ... 299:
        currentStatus = 0;
        break;
      case 1000 ... 1999:
        currentStatus = 1;
        break;
      case 2000 ... 2999:
        currentStatus = 2;
        break;
      case 3899 ... 4095:
        currentStatus = 3;
        break;
      default:
        Serial.println("motor position occurs exception!");
        break;
    }
    Serial.printf("Device value:%d, status: %d, remote status %d.\n", sensorValue, currentStatus, motor_status);
    if (motor_status != currentStatus) {
      moveMotor();
    } else {
      stopMotor();
      revertFloor();
    }
  }
}

void moveMotor() {
  if (motor_status > currentStatus) {
    // 
    digitalWrite(PIN_DIRECION, 1);
  } else {
    // 
    digitalWrite(PIN_DIRECION, 0);
  }
  if (motor_pwm != 0) {
    motor_pwm = 0;
    analogWrite(PIN_PWN, motor_pwm);
  }
}

void stopMotor() {
  // todo: stop motor, better using a power switch avoid motor works when reset.
  if (motor_pwm != 255) {
    motor_pwm = 255;
    analogWrite(PIN_PWN, 255);
  }
}

void setLocalTime() {
  if (!hasTime && Serial.available() > 0) {
    Serial.print("Please enter date at first to schedule task ==> ");
    String date = Serial.readString();
    Serial.println(date);
    resetTime(date);
    hasTime = true;
  }
}

void setRemoteTime() {
  // can alse use ArduinoCloud.getLocalTime()
  if (!isWifiConnected && ArduinoCloud.connected() == 1) {
    // setup this after wifi connected
    // you can use custom timeZone,server and timeout
    // DateTime.setTimeZone("CST-8");
    // DateTime.setServer("asia.pool.ntp.org");
    // DateTime.begin(15 * 1000);
    DateTime.setTimeZone("CST-8");
    // this method config ntp and wait for time sync
    // default timeout is 10 seconds
    DateTime.begin(60 * 1000);
    if (DateTime.isTimeValid()) {
      hasTime = true;
      Serial.print("remote time : ");
      Serial.println(DateTime.toUTCString());
      time_t now = DateTime.now();
      resetTime(localtime(&now));
    } else {
      Serial.println("Failed to get time from server.");
    }
    isWifiConnected = true;
  }
}

void scheduleTask() {
  if (hasTime) {
    if (taskList.size() > 0) {
      Task todo = taskList.get(0);
      if (todo.time <= getTime(hour(), minute(), second())) {
        taskList.remove(0);
        Serial.printf("do task[%d,%d] at %d:%d:%d.\n", todo.id, todo.func, hour(), minute(), second());
        switch (todo.id) {
          case 10:
            scheduleDoor(todo.func);
            break;
          case 20:
            scheduleFloor(todo.func);
            break;
          default:
            break;
        }
      }
    } else {
      // add task list next day.
      if (currentDay != day()) {
        currentDay = day();
        addTaskByCommand(circular_command);
      }
    }
  }
}

void scheduleDoor(int func) {
  // sync servo degree in cloud after invoke ArduinoCloud.update()
  switch (func) {
    case 3:
      // can in and out
      inner_latch_degree = 90;
      outer_latch_degree = 90;
      openFloor();
      break;
    case 2:
      // only in
      inner_latch_degree = 0;
      outer_latch_degree = 90;
      break;
    case 1:
      // only out
      inner_latch_degree = 90;
      outer_latch_degree = 0;
      break;
    default:
      // close
      inner_latch_degree = 0;
      outer_latch_degree = 0;
      break;
  }
  innerServo.write(inner_latch_degree);
  outerServo.write(outer_latch_degree);
}

void scheduleFloor(int func) {
  switch (func) {
    case 3:
      cleanFloor();
      break;
    case 2:
      forbidFeeding();
      break;
    case 1:
      // open
      openFloor();
      break;
    default:
      // close
      closeFloor();
      break;
  }
}

void cleanFloor() {
  isCleaning = true;
  statusBeforeCleaning = motor_status;
  if (motor_status == 0) {
    openFloor();
  } else {
    closeFloor();
  }
}

void revertFloor() {
  if (isCleaning) {
    isCleaning = false;
    motor_status = statusBeforeCleaning;
  }
}

void forbidFeeding() {
  motor_status = 1;
}

void openFloor() {
  motor_status = 3;
}

void closeFloor() {
  motor_status = 0;
}

/*
  Since ServoDegree is READ_WRITE variable, onServoDegreeChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onInnerLatchDegreeChange() {
  // Add your code here to act upon ServoDegree change
  Serial.printf("change inner latch degree to %d.\n", inner_latch_degree);
  innerServo.write(inner_latch_degree);
}

void onOuterLatchDegreeChange() {
  // Add your code here to act upon ServoDegree change
  Serial.printf("change outer latch degree to %d.\n", outer_latch_degree);
  outerServo.write(outer_latch_degree);
}

/*
  Since MotorStatus is READ_WRITE variable, onMotorStatusChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onMotorStatusChange() {
  // Add your code here to act upon MotorStatus change
  Serial.printf("change motor status to %d.\n", motor_status);
}

/*
  Since TaskCommand is READ_WRITE variable, onTaskCommandChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onTaskCommandChange() {
  // Parse command, like: 2023-10-19-01-0:103-06:00:00/101-06:30:00/203-08:00:00/103-11:30:00/201-12:00:00/102-17:00:00/100-18:30:00;
  String version = task_command.substring(0, 13);
  Serial.printf("task command version[%s].\n", version);
  if (last_version == "" || version > last_version) {
    last_version = version;
    char taskType = task_command.charAt(14);
    String command = task_command.substring(16, task_command.length());
    if (taskType == '0') {
      currentDay = day();
      Serial.printf("get circular command at day[%d]\n.", currentDay);
      circular_command = command;
    } else {
      Serial.println("get oneshot command.");
    }
    addTaskByCommand(command);
    Serial.println("onTaskCommandChange done!");
  }
}

void addTaskByCommand(String command) {
  Serial.print("add task by command: ");
  Serial.println(command);
  int length = command.length();
  // split by /
  int startIndex = 0;
  for (int i = 0; i < length; i++) {
    char currentChar = command.charAt(i);
    if (currentChar == '/' || currentChar == ';' || i == length - 1) {
      String taskStr = command.substring(startIndex, i);
      Serial.printf("get task string[%s].\n", taskStr);
      enqueueTaskAtTime(getTask(taskStr));
      startIndex = i + 1;
    }
  }
}

/*
  103-06:00:00
*/
Task getTask(String taskStr) {
  int id = taskStr.substring(0, 2).toInt();
  int func = taskStr.substring(2, 3).toInt();
  int hh = taskStr.substring(4, 6).toInt();
  int mm = taskStr.substring(7, 9).toInt();
  int ss = taskStr.substring(10, 12).toInt();
  Serial.printf("get task[%d,%d,%d,%d,%d].\n", id, func, hh, mm, ss);
  Task task;
  task.id = id;
  task.func = func;
  task.time = getTime(hh, mm, ss);
  return task;
}

int getTime(int hh, int mm, int ss) {
  return hh * 3600 + mm * 60 + ss;
}

/*
  reset time by dateTime like 2023/10/02 20:49:08
*/
void resetTime(String time) {
  int year = time.substring(0, 4).toInt();
  int month = time.substring(5, 7).toInt();
  int day = time.substring(8, 10).toInt();
  int hh = time.substring(11, 13).toInt();
  int mm = time.substring(14, 16).toInt();
  int ss = time.substring(17, 19).toInt();
  setTime(hh, mm, ss, day, month, year);
}

void resetTime(tm* dt) {
  int year = dt->tm_year + 1900;
  int month = dt->tm_mon;
  int day = dt->tm_mday;
  int hh = dt->tm_hour;
  int mm = dt->tm_min;
  int ss = dt->tm_sec;
  Serial.printf("%04d/%02d/%02d %02d:%02d:%02d\n", year, month, day, hh, mm, ss);
  setTime(hh, mm, ss, day, month, year);
}

/*
  enqueue task at time
*/
void enqueueTaskAtTime(Task task) {
  int i = 0;
  for (; i < taskList.size(); i++) {
    Task item = taskList.get(i);
    if (item.time > task.time) {
      break;
    }
  }
  taskList.add(i, task);
}

Credits

Forrest Wang
1 project • 0 followers
Contact

Comments

Please log in or sign up to comment.