#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);
}
Comments
Please log in or sign up to comment.