Skip to content

Week 12 > Mechanical & Machine Design

alt text

For machine building week, Skylabworkshop + Fablab Winam + Innobiz-K Fablabs will work collaboratively on a common project. Using the MQTT network, machines will be built that will make rudimentary communication between the labs possible. The project will be called the ‘Hello-Hello Binary Communicators’.

In the movie ‘The Martian’, astronaut Mark Watney was stranded alone on the planet Mars. To communicate with distant earth, Mark utilized the rotating head of an abandoned rover to make conversations possible. We want to tap into this spirit of ingenuity…making do with whatever tech we have on hand…to build a meaningful device.

In his first communication attempt, he simply had the rotating head point left and right, indicating binary outcome answers of ‘Yes’ or ‘No’. With this simple, but slow means of communication, he was able to confirm communication capabilities between Earth and Mars.

Each of the 3 labs will also build Binary Communicators.”

alt text alt text

Machine Design Components

Mechanism: Rack & Pinion
Actuation: Left/Right Motion
Automation: Arduino Programming > ESP32 (MCU), Motor Control (Stepper & Servo), MQTT Networking
Function: Indicate “Hello” & “Bye” messages
User Interface: MQTTX

GANTT Chart

alt text

Ethiopia:Innobiz-K > Tamrat Teklemarkos

alt text
alt text

Open & Close a window

Kenya:Fablab Winam > Charles Wangara

alt text
alt text
alt text

Forward & Reverse conveyance

Japan:Skylabworkshop > Rico Kanthatham

Left & Right Pointing Motion

The Skylabworkshop Binary Communicator
alt text
alt text

Rack & Pinion Mechanism driven by a Servo Motor alt text

CAD Design
alt text
alt text

Circuit Diagram

Control Code

Code produced by ChatGPT with the prompt:

“An arduino program for an ESP32 microcontroller connected to a servo capable publishing temperature sensor data and receiving control instruction to move the servo motor smoothly to the 0 degree or 180 degree positions”

/*
  ESP32 MQTT Servo Controller - Smooth Motion Version

  Features:
  - WiFi connection
  - MQTT publish / subscribe
  - Automatic MQTT reconnection
  - JSON telemetry
  - Remote servo command handling
  - Last Will & Testament (online/offline status)
  - Smooth non-blocking servo motion

  MQTT commands to send to topic_command:
  - Skylab_HELLO   -> servo goes to 0
  - Skylab_CENTER  -> servo goes to 90
  - Skylab_BYE     -> servo goes to 180
*/

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <ESP32Servo.h>

// ==========================================
// 1. CONFIGURATION
// ==========================================
const char* ssid       = "F660A-Y7NE-G";
const char* password   = "6qetgqq9";

const char* mqtt_server = "broker.hivemq.com";
const int   mqtt_port   = 1883;
const char* mqtt_user   = "";
const char* mqtt_pass   = "";

const char* device_id = "skylab_esp32_servo";

// MQTT topics
const char* topic_telemetry = "esp32/skylab/data";
const char* topic_command   = "esp32/skylab/cmd";
const char* topic_status    = "esp32/skylab/status";

// ==========================================
// 2. GLOBAL OBJECTS & VARIABLES
// ==========================================
WiFiClient wifiClient;
PubSubClient client(wifiClient);
Servo myservo;

const int SERVO_PIN = 18;

// Current and target servo positions
int pos = 90;
int targetPos = 90;

// Motion timing
unsigned long lastServoStepTime = 0;
const unsigned long servoStepInterval = 15;  // bigger = slower
const int servoStepSize = 1;                 // bigger = faster / less smooth

// Telemetry timing
unsigned long lastMsgTime = 0;
const unsigned long interval = 5000;

// ==========================================
// 3. WIFI SETUP
// ==========================================
void setupWiFi() {
  Serial.println();
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(1000);
  }

  Serial.println();
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  Serial.print("Signal strength (RSSI): ");
  Serial.println(WiFi.RSSI());
}

// ==========================================
// 4. MQTT CALLBACK
// ==========================================
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("]: ");

  char messageBuffer[128];
  unsigned int copyLength = length;

  if (copyLength >= sizeof(messageBuffer)) {
    copyLength = sizeof(messageBuffer) - 1;
  }

  for (unsigned int i = 0; i < copyLength; i++) {
    messageBuffer[i] = (char)payload[i];
  }
  messageBuffer[copyLength] = '\0';

  String msg = String(messageBuffer);
  msg.trim();

  Serial.println(msg);

  if (strcmp(topic, topic_command) == 0) {
    if (msg == "Skylab_HELLO") {
      Serial.println("Target servo angle set to 0");
      targetPos = 0;
      client.publish(topic_telemetry, "{\"servo\":\"HELLO\",\"target_angle\":0}");
    }
    else if (msg == "Skylab_CENTER") {
      Serial.println("Target servo angle set to 90");
      targetPos = 90;
      client.publish(topic_telemetry, "{\"servo\":\"CENTER\",\"target_angle\":90}");
    }
    else if (msg == "Skylab_BYE") {
      Serial.println("Target servo angle set to 180");
      targetPos = 180;
      client.publish(topic_telemetry, "{\"servo\":\"BYE\",\"target_angle\":180}");
    }
    else {
      client.publish(topic_telemetry, "{\"error\":\"unknown_command\"}");
    }
  }
}

// ==========================================
// 5. MQTT RECONNECT
// ==========================================
void reconnectMQTT() {
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");

    if (client.connect(device_id, mqtt_user, mqtt_pass, topic_status, 1, true, "offline")) {
      Serial.println("connected");

      client.publish(topic_status, "online", true);
      client.subscribe(topic_command);

      Serial.print("Subscribed to: ");
      Serial.println(topic_command);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" - retrying in 5 seconds");
      delay(5000);
    }
  }
}

// ==========================================
// 6. SERVO SMOOTH MOTION
// ==========================================
void updateServoSmooth() {
  unsigned long now = millis();

  if (now - lastServoStepTime >= servoStepInterval) {
    lastServoStepTime = now;

    if (pos < targetPos) {
      pos += servoStepSize;
      if (pos > targetPos) {
        pos = targetPos;
      }
      myservo.write(pos);
    }
    else if (pos > targetPos) {
      pos -= servoStepSize;
      if (pos < targetPos) {
        pos = targetPos;
      }
      myservo.write(pos);
    }
  }
}

// ==========================================
// 7. SEND TELEMETRY
// ==========================================
void publishTelemetry() {
  StaticJsonDocument<256> doc;

  doc["device"] = device_id;
  doc["uptime"] = millis() / 1000;
  doc["wifi_rssi"] = WiFi.RSSI();
  doc["servo_angle"] = pos;
  doc["servo_target"] = targetPos;
  doc["temp"] = random(60, 80);

  char buffer[256];
  serializeJson(doc, buffer);

  Serial.print("Publishing data: ");
  Serial.println(buffer);

  client.publish(topic_telemetry, buffer);
}

// ==========================================
// 8. MAIN SETUP
// ==========================================
void setup() {
  Serial.begin(115200);
  delay(1500);

  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);

  myservo.setPeriodHertz(50);
  myservo.attach(SERVO_PIN, 1000, 2000);

  Serial.print("Servo attached to pin ");
  Serial.println(SERVO_PIN);

  pos = 90;
  targetPos = 90;
  myservo.write(pos);
  delay(1000);

  setupWiFi();

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);

  randomSeed(micros());
}

// ==========================================
// 9. MAIN LOOP
// ==========================================
void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi disconnected. Reconnecting...");
    setupWiFi();
  }

  if (!client.connected()) {
    reconnectMQTT();
  }

  client.loop();

  updateServoSmooth();

  unsigned long now = millis();
  if (now - lastMsgTime >= interval) {
    lastMsgTime = now;
    publishTelemetry();
  }
}

MQTT Networking

Utilizing the MQTT Networking Protocol and the MQTTX control application.

  • Each lab subscribes to the other lab’s specific broadcast channel
  • Other labs can send specific control messages to move another lab’s machine remotely

To control the Ehiopia machine…
- Subscriber Topic > esp32/tamrat/cmd
- Left Motion > “Skylab_HELLO”
- Right Motion > “Skylab_BYE”

To control the Kenya machine…
- Subscriber Topic > esp32/skylab/control
- Left Motion > “Forward”
- Right Motion > “Reverse”

To control the Japan machine…

  • Subscriber Topic > esp32/skylab/cmd
  • Left Motion > “Skylab_HELLO”
  • Right Motion > “Skylab_BYE”