로보테크AI

융합_로보테크 AI 자율주행 로봇 개발자 과정-26/05/06

steezer 2026. 5. 6. 18:30

IPv4 주소가 192.168.0.187로 바뀜

ㄴ이유는 모름

 

내일까지 웹 완성(우영)

 

공기청정기(모터 + 디스플레이)로 간단하게 청소하는 느낌 구현 + MQTT

 

ESP 펌웨어 업데이트

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// ================ 환경에 맞게 수정 ================
const char* WIFI_SSID     = "3F_302";
const char* WIFI_PASSWORD = "0424719222!!";
const char* MQTT_BROKER   = "192.168.0.55";
// ================================================

const uint16_t MQTT_PORT      = 1883;
const char*    MQTT_CLIENT_ID = "esp01_actuator_node1";
const char*    MQTT_TOPIC     = "sensor/pm/zoneA";

WiFiClient   espClient;
PubSubClient mqttClient(espClient);

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  uint32_t start = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - start < 20000) {
    delay(500);
  }
}

int extractPm10(const char* json) {
  const char* key = strstr(json, "\"pm10\":");
  if (key == nullptr) return -1;
  return atoi(key + 7);
}

void onMqttMessage(char* topic, byte* payload, unsigned int length) {
  char buffer[160];
  if (length >= sizeof(buffer)) length = sizeof(buffer) - 1;
  memcpy(buffer, payload, length);
  buffer[length] = '\0';

  int pm10 = extractPm10(buffer);
  if (pm10 < 0) return;

  Serial.print("PM10:");
  Serial.print(pm10);
  Serial.print("\n");
}

void connectMqtt() {
  uint8_t attempts = 0;
  while (!mqttClient.connected() && attempts < 5) {
    if (mqttClient.connect(MQTT_CLIENT_ID)) {
      mqttClient.subscribe(MQTT_TOPIC, 0);
      return;
    }
    attempts++;
    delay(2000);
  }
}

void setup() {
  Serial.begin(9600);
  delay(100);
  setupWiFi();
  mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
  mqttClient.setCallback(onMqttMessage);
  connectMqtt();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    setupWiFi();
    return;
  }
  if (!mqttClient.connected()) {
    connectMqtt();
  }
  mqttClient.loop();
}

 

모터 + 부저 + LCD 코드

#include <SoftwareSerial.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Stepper.h>

// ============ 핀 설정 ============
const uint8_t PIN_ESP_RX = 2;       // ESP-01 TX → UNO D2
const uint8_t PIN_ESP_TX = 3;       // 미사용 (단방향)
const uint8_t PIN_BUZZER = 8;
const uint8_t PIN_STEP_IN1 = 4;
const uint8_t PIN_STEP_IN2 = 5;
const uint8_t PIN_STEP_IN3 = 6;
const uint8_t PIN_STEP_IN4 = 7;

// ============ 임계값 ============
const int PM10_ON_THRESHOLD  = 50;
const int PM10_OFF_THRESHOLD = 40;

// ============ 등급 기준 ============
const int PM10_GOOD_MAX = 30;
const int PM10_NORMAL_MAX = 80;

// ============ 모터 설정 ============
const int STEPS_PER_REV = 2048;     // 28BYJ-48 한 바퀴 (full-step 기준)
const int STEP_RPM = 15;            // 28BYJ-48 사실상 최대 속도

// ============ I2C LCD ============
const uint8_t LCD_I2C_ADDR = 0x27;  // 동작 안 하면 0x3F로 변경
LiquidCrystal_I2C lcd(LCD_I2C_ADDR, 16, 2);

// ============ 객체 ============
SoftwareSerial espSerial(PIN_ESP_RX, PIN_ESP_TX);
Stepper stepMotor(STEPS_PER_REV, PIN_STEP_IN1, PIN_STEP_IN3, PIN_STEP_IN2, PIN_STEP_IN4);

// ============ 상태 변수 ============
int  currentPm10 = -1;
bool motorRunning = false;
unsigned long lastDisplayUpdate = 0;
const unsigned long DISPLAY_REFRESH_MS = 500;


// ============ 등급 분류 ============
const char* gradeLabel(int pm10) {
  if (pm10 < 0) return "WAIT";
  if (pm10 <= PM10_GOOD_MAX) return "GOOD";
  if (pm10 <= PM10_NORMAL_MAX) return "NORMAL";
  return "BAD";
}


// ============ LCD 갱신 ============
void updateLcd(int pm10) {
  lcd.clear();
  lcd.setCursor(0, 0);
  if (pm10 < 0) {
    lcd.print("PM10: ---");
  } else {
    lcd.print("PM10: ");
    lcd.print(pm10);
  }
  lcd.setCursor(0, 1);
  lcd.print("Status: ");
  lcd.print(gradeLabel(pm10));
}


// ============ 부저 제어 ============
void buzzerOn() {
  tone(PIN_BUZZER, 800);
}

void buzzerOff() {
  noTone(PIN_BUZZER);
}


// ============ 모터 정지 ============
void stopMotor() {
  digitalWrite(PIN_STEP_IN1, LOW);
  digitalWrite(PIN_STEP_IN2, LOW);
  digitalWrite(PIN_STEP_IN3, LOW);
  digitalWrite(PIN_STEP_IN4, LOW);
}


// ============ ESP-01에서 PM10 수신 ============
int readPm10FromEsp() {
  static char lineBuffer[16];
  static uint8_t idx = 0;

  while (espSerial.available()) {
    char c = espSerial.read();
    if (c == '\n' || c == '\r') {
      if (idx == 0) continue;
      lineBuffer[idx] = '\0';
      idx = 0;
      if (strncmp(lineBuffer, "PM10:", 5) == 0) {
        return atoi(lineBuffer + 5);
      }
    } else if (idx < sizeof(lineBuffer) - 1) {
      lineBuffer[idx++] = c;
    }
  }
  return -1;
}


// ============ 셋업 ============
void setup() {
  pinMode(PIN_BUZZER, OUTPUT);
  digitalWrite(PIN_BUZZER, LOW);

  pinMode(PIN_STEP_IN1, OUTPUT);
  pinMode(PIN_STEP_IN2, OUTPUT);
  pinMode(PIN_STEP_IN3, OUTPUT);
  pinMode(PIN_STEP_IN4, OUTPUT);
  stopMotor();

  stepMotor.setSpeed(STEP_RPM);

  Serial.begin(9600);     // 디버그용
  espSerial.begin(9600);  // ESP-01과 통신

  Wire.begin();
  lcd.init();
  lcd.backlight();
  lcd.print("Booting...");
  delay(1000);
  updateLcd(-1);
}


// ============ 메인 루프 ============
void loop() {
  int newValue = readPm10FromEsp();

  if (newValue >= 0) {
    currentPm10 = newValue;

    if (currentPm10 > PM10_ON_THRESHOLD) {
      motorRunning = true;
      buzzerOn();
    } else if (currentPm10 < PM10_OFF_THRESHOLD) {
      motorRunning = false;
      stopMotor();
      buzzerOff();
    }

    Serial.print("[RX] PM10=");
    Serial.print(currentPm10);
    Serial.print(" motor=");
    Serial.println(motorRunning ? "ON" : "OFF");
  }

  if (motorRunning) {
    stepMotor.step(64);   // 작은 step씩 반복 → 부드러운 연속 회전
  }

  if (millis() - lastDisplayUpdate >= DISPLAY_REFRESH_MS) {
    updateLcd(currentPm10);
    lastDisplayUpdate = millis();
  }
}