|
|
|
|
@ -3,20 +3,30 @@
|
|
|
|
|
#include "EspMQTTClient.h"
|
|
|
|
|
|
|
|
|
|
#include "i2c_comms.h"
|
|
|
|
|
#include "io.h"
|
|
|
|
|
|
|
|
|
|
#define MOTOR_STATUS_UPDATE_PERIOD_MS 500
|
|
|
|
|
#define HEARTBEAT_PERIOD_MS 1000
|
|
|
|
|
|
|
|
|
|
EspMQTTClient mqtt_client("devtest", // Wifi SSID
|
|
|
|
|
"87usc6rs", // WiFi password
|
|
|
|
|
"10.9.2.101", // MQTT Broker server ip
|
|
|
|
|
"", // Can be omitted if not needed
|
|
|
|
|
"", // Can be omitted if not needed
|
|
|
|
|
"Airseeder", // Client name that uniquely identify your device
|
|
|
|
|
1883 // The MQTT port, default to 1883. this line can be omitted
|
|
|
|
|
#define POWER_BUTTON_HOLD_MS 300
|
|
|
|
|
|
|
|
|
|
EspMQTTClient mqtt_client("devtest", // Wifi SSID
|
|
|
|
|
"87usc6rs", // WiFi password
|
|
|
|
|
"10.9.2.101", // MQTT Broker server ip
|
|
|
|
|
"", // Can be omitted if not needed
|
|
|
|
|
"", // Can be omitted if not needed
|
|
|
|
|
"Airseeder", // Client name that uniquely identify your device
|
|
|
|
|
1883 // The MQTT port, default to 1883. this line can be omitted
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
void setup() {
|
|
|
|
|
// Hold system power on only if button is held down for long enough
|
|
|
|
|
digitalWrite(GPIO_SYS_EN, LOW);
|
|
|
|
|
pinMode(GPIO_SYS_EN, OUTPUT);
|
|
|
|
|
|
|
|
|
|
digitalWrite(GPIO_ACT_LED, LOW);
|
|
|
|
|
pinMode(GPIO_ACT_LED, OUTPUT);
|
|
|
|
|
|
|
|
|
|
Serial.setTimeout(1);
|
|
|
|
|
Serial.begin(57600);
|
|
|
|
|
i2c_comms_setup();
|
|
|
|
|
@ -32,8 +42,70 @@ void setup() {
|
|
|
|
|
|
|
|
|
|
char msg_buffer[100];
|
|
|
|
|
|
|
|
|
|
#define MAX_MSG_SEGMENTS 7
|
|
|
|
|
String msg_segments[MAX_MSG_SEGMENTS];
|
|
|
|
|
|
|
|
|
|
// Breaks up the message string into segments delimited by a ' ' character, putting them into `msg_segments`. Returns
|
|
|
|
|
// the number of segments found. Yes, it uses lots of dynamic Arduino Strings. Pretty sure it's not a problem on the
|
|
|
|
|
// ESP32.
|
|
|
|
|
int parse_message(const String &message) {
|
|
|
|
|
int seg_start = 0;
|
|
|
|
|
int seg_end = 0;
|
|
|
|
|
int seg_count = 0;
|
|
|
|
|
while (((seg_end = message.indexOf(' ', seg_start)) >= 0) and (seg_count < (MAX_MSG_SEGMENTS - 1))) {
|
|
|
|
|
if (seg_start != seg_end) {
|
|
|
|
|
msg_segments[seg_count] = message.substring(seg_start, seg_end);
|
|
|
|
|
seg_count++;
|
|
|
|
|
}
|
|
|
|
|
seg_start = seg_end + 1;
|
|
|
|
|
}
|
|
|
|
|
msg_segments[seg_count] = message.substring(seg_start);
|
|
|
|
|
seg_count++;
|
|
|
|
|
return seg_count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void onRollerControlMessage(const String &message) {}
|
|
|
|
|
typedef enum { MODE_DISABLED = 0, MODE_AUTO = 1, MODE_MANUAL = 2 } roller_control_mode_t;
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
roller_control_mode_t mode = MODE_DISABLED;
|
|
|
|
|
float wheel_ratio; // Roller to wheel ratio - so 2.5 would be 2.5 roller revs for each wheel rev
|
|
|
|
|
float manual_speed;
|
|
|
|
|
unsigned long manual_time_started;
|
|
|
|
|
unsigned long manual_duration;
|
|
|
|
|
} roller_control_t;
|
|
|
|
|
|
|
|
|
|
roller_control_t roller_ctrl[3];
|
|
|
|
|
|
|
|
|
|
// For each roller, need to store the current control mode, ratio, manual speed, manual speed started, manual speed
|
|
|
|
|
// duration
|
|
|
|
|
|
|
|
|
|
// [roller1_mode] [roller2_mode] [roller2_mode] [roller1_ratio] [roller2_ratio] [roller3_ratio]
|
|
|
|
|
// Mode is 0 for disabled, 1 for auto. If mode was manual, it is now overwritten, and manual control stops.
|
|
|
|
|
void onRollerControlMessage(const String &message) {
|
|
|
|
|
if (parse_message(message) >= 6) {
|
|
|
|
|
if (msg_segments[0].toInt() == 1) {
|
|
|
|
|
roller_ctrl[0].mode = MODE_AUTO;
|
|
|
|
|
} else {
|
|
|
|
|
roller_ctrl[0].mode = MODE_DISABLED;
|
|
|
|
|
}
|
|
|
|
|
if (msg_segments[1].toInt() == 1) {
|
|
|
|
|
roller_ctrl[1].mode = MODE_AUTO;
|
|
|
|
|
} else {
|
|
|
|
|
roller_ctrl[1].mode = MODE_DISABLED;
|
|
|
|
|
}
|
|
|
|
|
if (msg_segments[2].toInt() == 1) {
|
|
|
|
|
roller_ctrl[2].mode = MODE_AUTO;
|
|
|
|
|
} else {
|
|
|
|
|
roller_ctrl[2].mode = MODE_DISABLED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
roller_ctrl[0].wheel_ratio = msg_segments[3].toFloat();
|
|
|
|
|
roller_ctrl[1].wheel_ratio = msg_segments[4].toFloat();
|
|
|
|
|
roller_ctrl[2].wheel_ratio = msg_segments[5].toFloat();
|
|
|
|
|
|
|
|
|
|
// TODO trip motor control update flag
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Builtin function name used by EspMQTTClient
|
|
|
|
|
void onConnectionEstablished() {
|
|
|
|
|
@ -56,6 +128,21 @@ unsigned long last_heartbeat = 0;
|
|
|
|
|
void loop() {
|
|
|
|
|
now = millis();
|
|
|
|
|
|
|
|
|
|
if (now > POWER_BUTTON_HOLD_MS) {
|
|
|
|
|
digitalWrite(GPIO_SYS_EN, HIGH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Comms LED is solid blue if everything connected
|
|
|
|
|
if (mqtt_client.isConnected()){
|
|
|
|
|
digitalWrite(GPIO_ACT_LED, HIGH);
|
|
|
|
|
} else if (mqtt_client.isWifiConnected()){
|
|
|
|
|
// If WiFi is connected but MQTT is not, blinks with a period of 3 Hz
|
|
|
|
|
digitalWrite(GPIO_ACT_LED, (now % 333)<167);
|
|
|
|
|
} else {
|
|
|
|
|
// If no WiFi, blinks with a period of 1 Hz
|
|
|
|
|
digitalWrite(GPIO_ACT_LED, (now % 1000)<501);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (now - last_motor_status > MOTOR_STATUS_UPDATE_PERIOD_MS) {
|
|
|
|
|
last_motor_status = now;
|
|
|
|
|
request_motor_status_update();
|
|
|
|
|
@ -91,6 +178,11 @@ void loop() {
|
|
|
|
|
mqtt_client.publish("airseeder/status/heartbeat", msg_buffer, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Have roller control update required flag. Trip it if something comes in on MQTT or if been longer than update
|
|
|
|
|
// period. Also trip it if we have anything in manual override mode and now - started > duration If we have new
|
|
|
|
|
// wheel speed data or if longer than control update, send control update Generate update from current roller modes,
|
|
|
|
|
// set ratios, and wheel speed Modes and ratios are stored locally here in the main module
|
|
|
|
|
|
|
|
|
|
motor_control_group.motor_1.enabled = true;
|
|
|
|
|
motor_control_group.motor_1.desired_speed = ((millis() % 10000) / 10000.0) * 0.65;
|
|
|
|
|
set_motor_control(motor_control_group);
|
|
|
|
|
|