Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Nächste Überarbeitung
Vorhergehende Überarbeitung
ikea:ps2014 [2025/11/12 17:13] – angelegt geraldikea:ps2014 [2025/12/01 22:41] (aktuell) – [Anforderungen / Eigenschaften nach dem Umbau] gerald
Zeile 5: Zeile 5:
  
 [[https://github.com/julisa99/IKEAPS2014]] [[https://github.com/julisa99/IKEAPS2014]]
 +
 +Feetech FS5103R 3 kg.cm 25T RC Servo 360 Grad kontinuierliche Drehung Standard Analog Radmotor für Arduino Raspberry
 +
 +
 +===== Mein Projekt dazu =====
 +
 +==== Anforderungen / Eigenschaften nach dem Umbau ====
 +
 +Arduino-IDE
 +
 +  * Servo-Motor, der den Faden bewegt und Lampe schliesst. Öffnen durch Schwerkraft
 +  * 50 Neopixel (10 Lichter pro Reihe (für je einen Arm) in 5 Reihen) 
 +  * HAL-Sensor auf dem Fadenwickler zur Messung der Umdrehungen
 +  * Schalter für die obere Endposition
 +  * PIR zur Bewegungserkennung unter der Lampe
 +  * ESP8266 (m1n1) zur Steuerung. WLAN
 +  * Webserver für Einstellungen und Steuerung
 +  * MQTT
 +  * OTA-Firmware-Updates
 +  * HA-Autorecovery
 +
 +
 +Verkabelung
 +
 +  * LIMIT_SWITCH (oben)  -> D7
 +  * HALL_SENSOR (magnet) -> D5
 +  * SERVO_SIGNAL         -> D4
 +  * NEOPIXEL_DATA        -> D2
 +  * PIR                  -> D1
 +
 +zwischen ESP und Neopixel einen 330–470 Ω Widerstand
 +
 +einen 1000 µF Puffer-Kondensator 5V → GND am Anfang des LED-Strips (verhindert Stromspitzen)
 +
 +kein Pull-Up-Widerstand bei HAL
 +
 +Hall-Sensor OUT ist typischerweise Open-Collector. Verwende einen externen 10 kΩ Pull-Up an 3.3 V (also zwischen OUT und 3.3V). Nicht an 5V ziehen!
 +
 +End-Switch: Optional: 100nF Kondensator direkt am Schalter gegen GND
 +
 +=== neoPixel ===
 +
 +String mit 50 neopixel, verkabelt (kein Strip)
 +
 +von oben nach unten, durchnummeriert 1-50. im Uhrzeigersinn. 
 +
 +  - 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
 +  - 11, 50, 43, 42, 35, 34, 27, 26, 19, 18
 +  - 12, 49, 44, 41, 36, 33, 28, 25, 20, 17
 +  - 13, 48, 45, 40, 37, 32, 29, 24, 21, 16
 +  - 14, 47, 46, 39, 38, 31, 30, 23, 22, 15
 +
 +
 +=== Code ===
 +
 +
 +Stand:
 +<code>
 +✔ WLAN konfigurieren per WiFiManager
 +✔ Kleinen Webserver hosten
 +✔ OTA-Updates
 +✔ MQTT mit drei Kommandos
 +✔ Servo hochfahren → bis Endschalter
 +✔ Servo runterfahren → per Hall-Sensor-Strecke
 +✔ Hall-Sensor zählt Impulse
 +✔ NeoPixel (Einzelgruppen, Basisfunktionen)
 +✔ Under-the-hood sauber strukturiert
 +✔ Voll lauffähig
 +</code>
 +
 +
 +<code>
 +/******************************************************
 + * Lampe IKEA2014 - Full Firmware
 + * - Non-blocking state machine (IDLE, HOMING, MOVING, STOPPED, ERROR)
 + * - Endstop (D7) with debounce via ISR + software anti-bounce
 + * - Hall sensor (D5) increments pulses via ISR
 + * - Servo FS90R on D4 with attach/detach to avoid jitter
 + * - NeoPixel 50x on D2 (groups: 0-9,10-19,20-29,30-39,40-49)
 + * - WiFiManager for WLAN setup
 + * - Web server for basic control + MQTT settings
 + * - OTA update via /update
 + * - MQTT command topic: lampe/ikea2014/cmd
 + * - MQTT status topic: lampe/ikea2014/status
 + * - EEPROM persistence for MQTT and default target turns
 + *
 + * Serial debug is verbose. Set monitor to 115200.
 + ******************************************************/
 +
 +#include <ESP8266WiFi.h>
 +#include <WiFiManager.h>
 +#include <Ticker.h>
 +#include <ESP8266WebServer.h>
 +#include <ESP8266HTTPUpdateServer.h>
 +#include <PubSubClient.h>
 +#include <EEPROM.h>
 +
 +#include <Servo.h>
 +#include <Adafruit_NeoPixel.h>
 +
 +// ---------------- CONFIG ----------------
 +#define DEVICE_NAME "lampe_ikea2014"
 +#define EEPROM_SIZE 512
 +
 +// EEPROM offsets
 +#define EEPROM_OFF_MQTT_HOST 0    // 64 bytes
 +#define EEPROM_OFF_MQTT_PORT 64   // 4 bytes (int)
 +#define EEPROM_OFF_MQTT_USER 96   // 64 bytes
 +#define EEPROM_OFF_MQTT_PASS 160  // 64 bytes
 +#define EEPROM_OFF_TARGET    224  // 4 bytes int
 +
 +// Pins (as wired)
 +const uint8_t PIN_LIMIT = D7;   // end switch, active LOW
 +const uint8_t PIN_HALL  = D5;   // hall sensor (interrupt FALLING)
 +const uint8_t PIN_SERVO = D4;   // FS90R signal
 +const uint8_t PIN_PIXEL = D2;   // NeoPixel data
 +const uint8_t PIN_PIR   = D1;   // optional
 +
 +// Servo microseconds for FS90R
 +const int SERVO_STOP_US = 1500;
 +const int SERVO_CW_US   = 1000; // adjust if direction inverted
 +const int SERVO_CCW_US  = 2000;
 +
 +// NeoPixel
 +const uint16_t NUM_PIXELS = 50;
 +Adafruit_NeoPixel pixels(NUM_PIXELS, PIN_PIXEL, NEO_GRB + NEO_KHZ800);
 +
 +// Groups (0-9,10-19,20-29,30-39,40-49)
 +const uint8_t GROUP1_START = 0,  GROUP1_COUNT = 10;
 +const uint8_t GROUP2_START = 10, GROUP2_COUNT = 10;
 +const uint8_t GROUP3_START = 20, GROUP3_COUNT = 10;
 +const uint8_t GROUP4_START = 30, GROUP4_COUNT = 10;
 +const uint8_t GROUP5_START = 40, GROUP5_COUNT = 10;
 +
 +// Motion params
 +volatile long hallPulses = 0;
 +volatile unsigned long lastHallMicros = 0;
 +const unsigned long HALL_DEBOUNCE_US = 50000UL; // 50ms
 +int pulsesPerRevolution = 1; // magnets per rev
 +
 +// Default target turns (will be loaded/saved from EEPROM)
 +int targetTurns = 5;
 +
 +// State machine
 +enum class State { IDLE, HOMING, MOVING, STOPPED, ERROR };
 +volatile State state = State::IDLE;
 +
 +// Flags
 +volatile bool hallEvent = false;
 +volatile bool limitRawTriggered = false;
 +unsigned long lastLimitDebounceMs = 0;
 +const unsigned long LIMIT_DEBOUNCE_MS = 300; // anti-bounce window
 +bool homed = false;
 +bool servoAttached = false;
 +
 +// Servo
 +Servo servoMotor;
 +
 +// WiFi / MQTT / Web
 +WiFiClient wifiClient;
 +PubSubClient mqtt(wifiClient);
 +WiFiManager wifiManager;
 +ESP8266WebServer server(80);
 +ESP8266HTTPUpdateServer httpUpdater;
 +
 +// MQTT settings (loaded from EEPROM)
 +char mqttHost[64] = "";
 +uint16_t mqttPort = 1883;
 +char mqttUser[64] = "";
 +char mqttPass[64] = "";
 +String mqttBaseTopic = String("lampe/ikea2014");
 +
 +// Timing
 +unsigned long lastPublishMs = 0;
 +const unsigned long PUBLISH_INTERVAL_MS = 2000;
 +
 +// Stall detection
 +unsigned long lastPulseTimeMs = 0;
 +const unsigned long STALL_TIMEOUT_MS = 3000; // ms without pulse = stall when moving
 +
 +// Heartbeat ticker
 +Ticker heartbeatTicker;
 +
 +// --------------- EEPROM helpers ---------------
 +void eepromReadString(int addr, char* buf, size_t maxlen) {
 +  for (size_t i=0;i<maxlen;i++) {
 +    buf[i] = EEPROM.read(addr + i);
 +    if (buf[i] == 0) break;
 +  }
 +  buf[maxlen-1] = 0;
 +}
 +void eepromWriteString(int addr, const char* s, size_t maxlen) {
 +  size_t i;
 +  for (i=0;i<maxlen;i++) {
 +    if (s[i] == 0) { EEPROM.write(addr + i, 0); break; }
 +    EEPROM.write(addr + i, s[i]);
 +  }
 +  if (i==maxlen) EEPROM.write(addr + maxlen - 1, 0);
 +}
 +void eepromWriteInt(int addr, int32_t v) {
 +  EEPROM.write(addr+0, (v>>24)&0xFF);
 +  EEPROM.write(addr+1, (v>>16)&0xFF);
 +  EEPROM.write(addr+2, (v>>8)&0xFF);
 +  EEPROM.write(addr+3, (v>>0)&0xFF);
 +}
 +int32_t eepromReadInt(int addr) {
 +  int32_t v = 0;
 +  v |= (int32_t)EEPROM.read(addr+0) << 24;
 +  v |= (int32_t)EEPROM.read(addr+1) << 16;
 +  v |= (int32_t)EEPROM.read(addr+2) << 8;
 +  v |= (int32_t)EEPROM.read(addr+3) << 0;
 +  return v;
 +}
 +
 +// -------------- ISRs (very small) --------------
 +void ICACHE_RAM_ATTR hallISR() {
 +  unsigned long now = micros();
 +  if (now - lastHallMicros < HALL_DEBOUNCE_US) return;
 +  lastHallMicros = now;
 +  hallPulses++;
 +  hallEvent = true;
 +  lastPulseTimeMs = millis();
 +}
 +void ICACHE_RAM_ATTR limitISR() {
 +  // just mark raw trigger; do debouncing in loop
 +  limitRawTriggered = true;
 +}
 +
 +// -------------- Servo helpers --------------
 +void servoAttachIfNeeded() {
 +  if (!servoAttached) {
 +    servoMotor.attach(PIN_SERVO, 1000, 2000);
 +    servoAttached = true;
 +    Serial.println("[SERVO] attached");
 +  }
 +}
 +void servoDetachIfNeeded() {
 +  if (servoAttached) {
 +    servoMotor.detach();
 +    servoAttached = false;
 +    Serial.println("[SERVO] detached");
 +  }
 +}
 +void servoWriteMicro(int microsVal) {
 +  servoAttachIfNeeded();
 +  servoMotor.writeMicroseconds(microsVal);
 +}
 +void servoStop() {
 +  // set neutral, small delay, then detach
 +  servoWriteMicro(SERVO_STOP_US);
 +  delay(5);
 +  servoDetachIfNeeded();
 +  Serial.println("[SERVO] STOP (neutral + detach)");
 +}
 +void servoUp() {
 +  servoWriteMicro(SERVO_CW_US);
 +  Serial.println("[SERVO] UP (homing direction) -> pwm " + String(SERVO_CW_US));
 +}
 +void servoDown() {
 +  servoWriteMicro(SERVO_CCW_US);
 +  Serial.println("[SERVO] DOWN -> pwm " + String(SERVO_CCW_US));
 +}
 +
 +// -------------- NeoPixel helpers --------------
 +uint32_t col(uint8_t r,uint8_t g,uint8_t b){ return pixels.Color(r,g,b); }
 +void clearAll() { for (uint16_t i=0;i<NUM_PIXELS;i++) pixels.setPixelColor(i,0); pixels.show(); }
 +void setRange(uint8_t start, uint8_t count, uint32_t color) { for (uint8_t i=0;i<count;i++) pixels.setPixelColor(start+i, color); pixels.show(); }
 +void heartbeat() { static bool on=false; on=!on; if(on) setRange(GROUP5_START, GROUP5_COUNT, col(0,12,0)); else setRange(GROUP5_START, GROUP5_COUNT, 0); }
 +
 +// -------------- MQTT helpers --------------
 +void mqttPublishState() {
 +  if (!mqtt.connected()) return;
 +  String topic = mqttBaseTopic + "/status";
 +  String payload = String("state:") + String((int)state) + ",pulses:" + String(hallPulses) + ",homed:" + (homed?"1":"0");
 +  mqtt.publish(topic.c_str(), payload.c_str(), true);
 +  Serial.println("[MQTT] Published state -> " + payload);
 +}
 +void mqttPublishTopic(const char* suffix, const char* msg) {
 +  if (!mqtt.connected()) { Serial.println("[MQTT] publish skip (not connected): " + String(suffix)); return; }
 +  String topic = mqttBaseTopic + "/" + String(suffix);
 +  mqtt.publish(topic.c_str(), msg);
 +  Serial.println("[MQTT] Published " + topic + " -> " + String(msg));
 +}
 +
 +// -------------- MQTT callback --------------
 +void mqttCallback(char* topic, byte* payload, unsigned int length) {
 +  String msg;
 +  for (unsigned int i=0;i<length;i++) msg += (char)payload[i];
 +  msg.trim();
 +  Serial.println("[MQTT] msg on " + String(topic) + " : " + msg);
 +
 +  if (msg.equalsIgnoreCase("HOME")) {
 +    if (state==State::IDLE || state==State::STOPPED) {
 +      Serial.println("[CMD] HOME requested -> switching to HOMING");
 +      state = State::HOMING;
 +    } else Serial.println("[CMD] HOME ignored (bad state)");
 +  } else if (msg.startsWith("MOVE:")) {
 +    int n = msg.substring(5).toInt(); if (n<=0) n = targetTurns;
 +    hallPulses = 0;
 +    targetTurns = n;
 +    Serial.println("[CMD] MOVE requested -> targetTurns=" + String(targetTurns));
 +    state = State::MOVING;
 +  } else if (msg.equalsIgnoreCase("MOVE")) {
 +    hallPulses = 0;
 +    Serial.println("[CMD] MOVE (use saved targetTurns=" + String(targetTurns) + ")");
 +    state = State::MOVING;
 +  } else if (msg.equalsIgnoreCase("STOP")) {
 +    Serial.println("[CMD] STOP requested");
 +    state = State::STOPPED;
 +    servoStop();
 +  } else if (msg.startsWith("SET_TARGET:")) {
 +    int v = msg.substring(11).toInt();
 +    if (v>0) {
 +      targetTurns = v;
 +      eepromWriteInt(EEPROM_OFF_TARGET, targetTurns);
 +      EEPROM.commit();
 +      Serial.println("[CMD] SET_TARGET saved -> " + String(targetTurns));
 +      mqttPublishTopic("debug","TARGET_UPDATED");
 +    }
 +  } else {
 +    Serial.println("[MQTT] Unknown command: " + msg);
 +  }
 +}
 +
 +// -------------- MQTT connect try --------------
 +void mqttTryConnect() {
 +  if (mqtt.connected()) return;
 +  if (strlen(mqttHost) == 0) {
 +    Serial.println("[MQTT] No broker configured, skip connect");
 +    return;
 +  }
 +  mqtt.setServer(mqttHost, mqttPort);
 +  Serial.println("[MQTT] Connecting to " + String(mqttHost) + ":" + String(mqttPort));
 +  if (mqtt.connect(DEVICE_NAME, mqttUser, mqttPass)) {
 +    String cmdTopic = mqttBaseTopic + "/cmd";
 +    mqtt.setCallback(mqttCallback);
 +    mqtt.subscribe(cmdTopic.c_str());
 +    Serial.println("[MQTT] Connected and subscribed to " + cmdTopic);
 +    mqttPublishTopic("debug","connected");
 +  } else {
 +    Serial.println("[MQTT] Connect failed");
 +  }
 +}
 +
 +// -------------- Web handlers --------------
 +void handleRoot() {
 +  Serial.println("[WEB] / requested");
 +  String html = "<html><head><meta name='viewport' content='width=device-width'><title>" + String(DEVICE_NAME) + "</title></head><body>";
 +  html += "<h3>" + String(DEVICE_NAME) + "</h3>";
 +  html += "<p>State: " + String((int)state) + "</p>";
 +  html += "<p>Homed: " + String(homed ? \"yes\" : \"no\") + "</p>";
 +  html += "<p>Pulses: " + String(hallPulses) + "</p>";
 +  html += "<p><button onclick='fetch(\"/cmd?c=HOME\")'>HOME</button> <button onclick='fetch(\"/cmd?c=MOVE\")'>MOVE</button> <button onclick='fetch(\"/cmd?c=STOP\")'>STOP</button></p>";
 +  html += "<h4>MQTT Settings</h4>";
 +  html += \"<form method='POST' action='/setmqtt'>Broker: <input name='broker' value='\" + String(mqttHost) + \"' size=30><br>Port: <input name='port' value='\" + String(mqttPort) + \"' size=6><br>User: <input name='user' value='\" + String(mqttUser) + \"' size=20><br>Pass: <input name='pass' value='' size=20><br>Topic base: <input name='topic' value='\" + mqttBaseTopic + \"' size=30><br><input type='submit' value='Save'></form>\";
 +  html += \"</body></html>\";
 +  server.send(200, "text/html", html);
 +}
 +
 +void handleCmdWeb() {
 +  Serial.println("[WEB] /cmd called");
 +  if (!server.hasArg("c")) { server.send(400, "text/plain", "no cmd"); return; }
 +  String c = server.arg("c");
 +  if (c == "HOME") { Serial.println("[WEB] HOME"); state = State::HOMING; server.send(200, "text/plain", "HOMING"); }
 +  else if (c == "MOVE") { Serial.println("[WEB] MOVE"); hallPulses = 0; state = State::MOVING; server.send(200, "text/plain", "MOVING"); }
 +  else if (c == "STOP") { Serial.println("[WEB] STOP"); state = State::STOPPED; servoStop(); server.send(200, "text/plain", "STOPPED"); }
 +  else server.send(400, "text/plain", "unknown");
 +}
 +
 +void handleSetMqtt() {
 +  Serial.println("[WEB] /setmqtt POST");
 +  if (!server.hasArg("broker")) { server.send(400, "text/plain", "missing"); return; }
 +  String b = server.arg("broker"); String p = server.arg("port"); String u = server.arg("user"); String pw = server.arg("pass"); String t = server.arg("topic");
 +  b.toCharArray(mqttHost, sizeof(mqttHost)); mqttPort = (uint16_t)p.toInt(); u.toCharArray(mqttUser, sizeof(mqttUser)); pw.toCharArray(mqttPass, sizeof(mqttPass)); mqttBaseTopic = t;
 +  eepromWriteString(EEPROM_OFF_MQTT_HOST, mqttHost, 64);
 +  eepromWriteInt(EEPROM_OFF_MQTT_PORT, mqttPort);
 +  eepromWriteString(EEPROM_OFF_MQTT_USER, mqttUser, 64);
 +  eepromWriteString(EEPROM_OFF_MQTT_PASS, mqttPass, 64);
 +  EEPROM.commit();
 +  Serial.println("[WEB] Saved MQTT config to EEPROM: host=" + String(mqttHost) + " port=" + String(mqttPort));
 +  server.send(200, "text/plain", "Saved");
 +  mqttTryConnect();
 +}
 +
 +// -------------- Setup --------------
 +void setup() {
 +  Serial.begin(115200);
 +  delay(50);
 +  Serial.println("\n\n[BOOT] lampe_ikea2014 starting...");
 +
 +  EEPROM.begin(EEPROM_SIZE);
 +  eepromReadString(EEPROM_OFF_MQTT_HOST, mqttHost, sizeof(mqttHost));
 +  mqttPort = (uint16_t)eepromReadInt(EEPROM_OFF_MQTT_PORT);
 +  if (mqttPort == 0) mqttPort = 1883;
 +  eepromReadString(EEPROM_OFF_MQTT_USER, mqttUser, sizeof(mqttUser));
 +  eepromReadString(EEPROM_OFF_MQTT_PASS, mqttPass, sizeof(mqttPass));
 +  int storedTarget = (int)eepromReadInt(EEPROM_OFF_TARGET);
 +  if (storedTarget > 0) targetTurns = storedTarget;
 +
 +  Serial.println("[BOOT] EEPROM:");
 +  Serial.println(String("[BOOT] mqttHost: ") + (strlen(mqttHost)?mqttHost:"<empty>"));
 +  Serial.println(String("[BOOT] mqttPort: ") + String(mqttPort));
 +  Serial.println(String("[BOOT] mqttUser: ") + (strlen(mqttUser)?mqttUser:"<empty>"));
 +  Serial.println(String("[BOOT] targetTurns: ") + String(targetTurns));
 +
 +  // pins & ISRs
 +  pinMode(PIN_LIMIT, INPUT_PULLUP);
 +  pinMode(PIN_HALL, INPUT_PULLUP);
 +  pinMode(PIN_PIR, INPUT);
 +  attachInterrupt(digitalPinToInterrupt(PIN_HALL), hallISR, FALLING);
 +  attachInterrupt(digitalPinToInterrupt(PIN_LIMIT), limitISR, FALLING);
 +  Serial.println("[BOOT] Pins configured and ISRs attached");
 +
 +  // NeoPixel
 +  pixels.begin(); pixels.setBrightness(120); clearAll();
 +  Serial.println("[BOOT] NeoPixel ready");
 +
 +  // servo initial state: neutral & detached
 +  servoMotor.attach(PIN_SERVO, 1000, 2000);
 +  servoMotor.writeMicroseconds(SERVO_STOP_US);
 +  delay(5);
 +  servoMotor.detach();
 +  servoAttached = false;
 +  Serial.println("[BOOT] Servo initialized (neutral & detached)");
 +
 +  // WiFiManager (blocking until wifi configured or known)
 +  Serial.println("[BOOT] Starting WiFiManager (autoConnect)...");
 +  wifiManager.autoConnect(DEVICE_NAME);
 +  Serial.println("[BOOT] WiFi connected, IP: " + WiFi.localIP().toString());
 +
 +  // web + OTA
 +  httpUpdater.setup(&server);
 +  server.on("/", handleRoot);
 +  server.on("/cmd", handleCmdWeb);
 +  server.on("/setmqtt", HTTP_POST, handleSetMqtt);
 +  server.begin();
 +  Serial.println("[BOOT] Webserver + OTA ready");
 +
 +  // MQTT
 +  mqtt.setClient(wifiClient);
 +  mqtt.setCallback(mqttCallback);
 +  mqttTryConnect();
 +
 +  // heartbeat
 +  heartbeatTicker.attach(1.0, heartbeat);
 +  Serial.println("[BOOT] Heartbeat started");
 +
 +  // start homing if needed (non-blocking)
 +  if (digitalRead(PIN_LIMIT) == HIGH) {
 +    state = State::HOMING;
 +    Serial.println("[BOOT] Limit not pressed -> start HOMING state");
 +  } else {
 +    homed = true;
 +    state = State::IDLE;
 +    Serial.println("[BOOT] Limit pressed -> already homed, state IDLE");
 +  }
 +}
 +
 +// -------------- Main loop (non-blocking FSM) --------------
 +void loop() {
 +  server.handleClient();
 +  if (!mqtt.connected()) mqttTryConnect(); else mqtt.loop();
 +
 +  unsigned long now = millis();
 +
 +  // process raw limit triggers with debounce
 +  if (limitRawTriggered) {
 +    limitRawTriggered = false;
 +    unsigned long t = millis();
 +    if (t - lastLimitDebounceMs > LIMIT_DEBOUNCE_MS) {
 +      lastLimitDebounceMs = t;
 +      Serial.println("[LIMIT] Valid limit event (debounced)");
 +      // If homing, react immediately in state machine section
 +      // We also set homed flag later there.
 +      // store timestamp of last pulse as safety
 +      lastPulseTimeMs = millis();
 +      // Mark a software event sign that limit was hit
 +      // We don't directly change state here; state machine handles it.
 +      // To allow immediate reaction, we can set hallPulses=0 if homing ends.
 +      if (state == State::HOMING) {
 +        // set hallPulses to 0 at home
 +        hallPulses = 0;
 +        homed = true;
 +        servoStop();
 +        state = State::IDLE;
 +        Serial.println("[HOMING] Completed at boot or runtime -> homed=true, state=IDLE");
 +        mqttPublishTopic("debug","HOMED");
 +      } else {
 +        // limit pressed unexpected
 +        Serial.println("[LIMIT] limit hit while not homing (state=" + String((int)state) + ")");
 +      }
 +    } else {
 +      Serial.println("[LIMIT] Ignored bounce (within debounce window)");
 +    }
 +  }
 +
 +  // state machine
 +  static State lastState = State::ERROR; // force initial print
 +  if (lastState != state) {
 +    Serial.println("[STATE] " + String((int)lastState) + " -> " + String((int)state));
 +    lastState = state;
 +  }
 +
 +  switch (state) {
 +    case State::IDLE:
 +      // ensure servo detached (no PWM)
 +      servoStop();
 +      // do nothing else
 +      break;
 +
 +    case State::HOMING:
 +      // run servo up until limit interrupt occurs (limitRawTriggered processed above)
 +      // ensure servo is running up
 +      servoUp();
 +      break;
 +
 +    case State::MOVING:
 +      // run down until pulses reached or error
 +      servoDown();
 +      {
 +        long goal = (long)targetTurns * pulsesPerRevolution;
 +        if ((long)hallPulses >= goal) {
 +          Serial.println("[MOVING] Goal reached: pulses=" + String(hallPulses) + " goal=" + String(goal));
 +          servoStop();
 +          state = State::IDLE;
 +          mqttPublishTopic("debug","MOVED");
 +        }
 +        // stall detection
 +        if (millis() - lastPulseTimeMs > STALL_TIMEOUT_MS) {
 +          Serial.println("[ERROR] Stall detected: no hall pulses for " + String(STALL_TIMEOUT_MS) + " ms");
 +          servoStop();
 +          state = State::ERROR;
 +          mqttPublishTopic("error","stall_detected");
 +        }
 +        // safety: if limit is pressed unexpectedly -> error
 +        if (digitalRead(PIN_LIMIT) == LOW) {
 +          Serial.println("[ERROR] Limit hit while moving -> emergency stop");
 +          servoStop();
 +          state = State::ERROR;
 +          mqttPublishTopic("error","limit_hit_during_move");
 +        }
 +      }
 +      break;
 +
 +    case State::STOPPED:
 +      servoStop();
 +      // stay stopped until command
 +      break;
 +
 +    case State::ERROR:
 +      // motor stopped; require manual HOME to recover
 +      servoStop();
 +      // optionally blink LEDs to indicate error
 +      setRange(GROUP1_START, GROUP1_COUNT, col(16,0,0)); // red top group
 +      break;
 +  }
 +
 +  // publish periodic state
 +  if (now - lastPublishMs > PUBLISH_INTERVAL_MS) {
 +    lastPublishMs = now;
 +    mqttPublishState();
 +  }
 +
 +  // handle hall event logging (do not print from ISR)
 +  if (hallEvent) {
 +    hallEvent = false;
 +    Serial.println("[HALL] Pulse detected, total=" + String(hallPulses) + "  lastPulseTimeMs=" + String(lastPulseTimeMs));
 +    // publish per-pulse (optional)
 +    mqttPublishTopic("pulse", String(hallPulses).c_str());
 +  }
 +
 +  yield();
 +}
 +
 +</code>
  
 
Nach oben
ikea/ps2014.1762967623.txt.gz · Zuletzt geändert: von gerald
chimeric.de = chi`s home Creative Commons License Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0
DFmW2CEce3htPL1uNQuHUVu4Tk6WXigFQp   Dogecoin Donations Accepted Here    DFmW2CEce3htPL1uNQuHUVu4Tk6WXigFQp  DFmW2CEce3htPL1uNQuHUVu4Tk6WXigFQp