diff --git a/robot_v2_esp32c6/robot_v2_esp32c6.ino b/robot_v2_esp32c6/robot_v2_esp32c6.ino new file mode 100644 index 0000000..fa29d07 --- /dev/null +++ b/robot_v2_esp32c6/robot_v2_esp32c6.ino @@ -0,0 +1,314 @@ +\ +/**************************************************** + * ROBOT V2 + * ESP32-C6 + NEO-M8N + MPU6050 + MMC5883MA + BMP180 + * Sender data til Home Assistant via REST + ****************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +// ---------------- WIFI / HA ---------------- +const char* WIFI_SSID = "DIT_WIFI"; +const char* WIFI_PASSWORD = "DIT_PASS"; + +const char* HA_HOST = "192.168.2.10"; +const uint16_t HA_PORT = 8123; +const char* HA_TOKEN = "DIN_LONG_LIVED_ACCESS_TOKEN"; + +// ---------------- PINS ---------------- +const int I2C_SDA_PIN = 6; +const int I2C_SCL_PIN = 7; + +const int GPS_RX_PIN = 17; // ESP RX <- GPS TX +const int GPS_TX_PIN = 16; // ESP TX -> GPS RX + +// ---------------- GPS ---------------- +HardwareSerial GPSserial(1); +TinyGPSPlus gps; + +// ---------------- BMP180 ---------------- +Adafruit_BMP085 bmp; + +// ---------------- MPU6050 ---------------- +MPU6050 mpu6050(Wire); + +// ---------------- MMC5883MA ---------------- +const uint8_t MMC5883_ADDR = 0x30; + +// MMC5883MA registers +const uint8_t MMC_REG_XOUT_L = 0x00; +const uint8_t MMC_REG_STATUS = 0x07; +const uint8_t MMC_REG_CTRL_0 = 0x08; +const uint8_t MMC_REG_CTRL_1 = 0x09; + +// ---------------- Logic ---------------- +bool isMoving = false; +bool gpsFix = false; + +double acceptedLat = 0.0; +double acceptedLon = 0.0; +double acceptedSpeedKmh = 0.0; +double acceptedHdop = 99.9; +int acceptedSats = 0; +float headingDeg = 0.0; +unsigned long lastAcceptedFixMs = 0; + +unsigned long lastImuMs = 0; +unsigned long lastGpsLogicMs = 0; +unsigned long lastPushMs = 0; + +const float MOTION_THRESHOLD = 0.08f; +const unsigned long STILL_HOLD_MS = 3000; +unsigned long lastMotionDetectedMs = 0; + +// ---------------- Utils ---------------- +String haBaseUrl() { + return "http://" + String(HA_HOST) + ":" + String(HA_PORT); +} + +void wifiConnect() { + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + + unsigned long startMs = millis(); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + if (millis() - startMs > 30000) { + ESP.restart(); + } + } +} + +bool postState(const String& entityId, const String& state, const String& unit = "", const String& devClass = "", const String& stateClass = "") { + WiFiClient client; + HTTPClient http; + String url = haBaseUrl() + "/api/states/" + entityId; + + if (!http.begin(client, url)) return false; + + http.addHeader("Authorization", "Bearer " + String(HA_TOKEN)); + http.addHeader("Content-Type", "application/json"); + + String json = "{\"state\":\"" + state + "\",\"attributes\":{"; + bool first = true; + + if (unit.length()) { + json += "\"unit_of_measurement\":\"" + unit + "\""; + first = false; + } + if (devClass.length()) { + if (!first) json += ","; + json += "\"device_class\":\"" + devClass + "\""; + first = false; + } + if (stateClass.length()) { + if (!first) json += ","; + json += "\"state_class\":\"" + stateClass + "\""; + first = false; + } + if (!first) json += ","; + json += "\"friendly_name\":\"" + entityId + "\""; + json += "}}"; + + int code = http.POST(json); + http.end(); + return (code >= 200 && code < 300); +} + +// ---------------- MMC5883MA helpers ---------------- +void mmcWrite8(uint8_t reg, uint8_t val) { + Wire.beginTransmission(MMC5883_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +uint8_t mmcRead8(uint8_t reg) { + Wire.beginTransmission(MMC5883_ADDR); + Wire.write(reg); + Wire.endTransmission(false); + Wire.requestFrom((int)MMC5883_ADDR, 1); + if (!Wire.available()) return 0; + return Wire.read(); +} + +void mmcReadXYZ(uint16_t &x, uint16_t &y, uint16_t &z) { + // Start measurement + mmcWrite8(MMC_REG_CTRL_0, 0x01); // TM_M + delay(10); + + // Wait until measurement done + unsigned long start = millis(); + while (!(mmcRead8(MMC_REG_STATUS) & 0x01)) { + if (millis() - start > 20) break; + delay(1); + } + + Wire.beginTransmission(MMC5883_ADDR); + Wire.write(MMC_REG_XOUT_L); + Wire.endTransmission(false); + Wire.requestFrom((int)MMC5883_ADDR, 6); + + if (Wire.available() >= 6) { + uint8_t xl = Wire.read(); + uint8_t xh = Wire.read(); + uint8_t yl = Wire.read(); + uint8_t yh = Wire.read(); + uint8_t zl = Wire.read(); + uint8_t zh = Wire.read(); + + x = ((uint16_t)xh << 8) | xl; + y = ((uint16_t)yh << 8) | yl; + z = ((uint16_t)zh << 8) | zl; + } else { + x = y = z = 0; + } +} + +void mmcInit() { + // Optional SET/RESET cycle + mmcWrite8(MMC_REG_CTRL_0, 0x08); // SET + delay(2); + mmcWrite8(MMC_REG_CTRL_0, 0x10); // RESET + delay(2); + + // BW = 00 default 16-bit + mmcWrite8(MMC_REG_CTRL_1, 0x00); +} + +// Raw unsigned output is centered around 32768 counts +float mmcReadHeading() { + uint16_t rx, ry, rz; + mmcReadXYZ(rx, ry, rz); + + float x = (float)rx - 32768.0f; + float y = (float)ry - 32768.0f; + + float heading = atan2f(y, x) * 180.0f / PI; + if (heading < 0) heading += 360.0f; + return heading; +} + +// ---------------- Motion detect ---------------- +void updateMotion() { + mpu6050.update(); + + float ax = mpu6050.getAccX(); + float ay = mpu6050.getAccY(); + float az = mpu6050.getAccZ(); + + float magnitude = sqrtf(ax * ax + ay * ay + az * az); + float deviation = fabsf(magnitude - 1.0f); + + if (deviation > MOTION_THRESHOLD) { + lastMotionDetectedMs = millis(); + isMoving = true; + } else { + if (millis() - lastMotionDetectedMs > STILL_HOLD_MS) { + isMoving = false; + } + } +} + +// ---------------- GPS logic ---------------- +void processGPS() { + while (GPSserial.available()) { + gps.encode(GPSserial.read()); + } + + if (millis() - lastGpsLogicMs < 1000) return; + lastGpsLogicMs = millis(); + + bool validLocation = gps.location.isValid(); + int sats = gps.satellites.isValid() ? gps.satellites.value() : 0; + double hdop = gps.hdop.isValid() ? gps.hdop.hdop() : 99.9; + + gpsFix = validLocation && sats >= 4 && hdop <= 5.0; + + if (isMoving && gpsFix) { + acceptedLat = gps.location.lat(); + acceptedLon = gps.location.lng(); + acceptedSpeedKmh = gps.speed.isValid() ? gps.speed.kmph() : 0.0; + acceptedHdop = hdop; + acceptedSats = sats; + lastAcceptedFixMs = millis(); + } + + if (lastAcceptedFixMs == 0 && gpsFix) { + acceptedLat = gps.location.lat(); + acceptedLon = gps.location.lng(); + acceptedSpeedKmh = gps.speed.isValid() ? gps.speed.kmph() : 0.0; + acceptedHdop = hdop; + acceptedSats = sats; + lastAcceptedFixMs = millis(); + } + + if (isMoving) { + headingDeg = mmcReadHeading(); + } +} + +// ---------------- Push to HA ---------------- +void pushToHA() { + unsigned long ageSec = (lastAcceptedFixMs == 0) ? 999999 : ((millis() - lastAcceptedFixMs) / 1000); + + float tempC = bmp.readTemperature(); + float pressureHpa = bmp.readPressure() / 100.0f; + + postState("sensor.robot_gps_fix_raw", gpsFix ? "on" : "off"); + postState("sensor.robot_motion_raw", isMoving ? "on" : "off"); + + postState("sensor.robot_gps_latitude", String(acceptedLat, 6)); + postState("sensor.robot_gps_longitude", String(acceptedLon, 6)); + postState("sensor.robot_gps_satellites", String(acceptedSats)); + postState("sensor.robot_gps_hdop", String(acceptedHdop, 1)); + postState("sensor.robot_gps_speed_kmh", String(acceptedSpeedKmh, 1), "km/h", "speed", "measurement"); + postState("sensor.robot_heading_deg", String(headingDeg, 1), "°"); + postState("sensor.robot_last_position_age", String(ageSec), "s", "duration", "measurement"); + postState("sensor.robot_temperature", String(tempC, 1), "°C", "temperature", "measurement"); + postState("sensor.robot_pressure", String(pressureHpa, 1), "hPa", "atmospheric_pressure", "measurement"); +} + +void setup() { + Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); + Wire.setClock(400000); + + wifiConnect(); + + GPSserial.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); + + if (!bmp.begin()) { + // BMP180 ikke fundet - fortsæt alligevel + } + + mpu6050.begin(); + mpu6050.calcGyroOffsets(true); + + mmcInit(); + + lastMotionDetectedMs = millis(); +} + +void loop() { + if (WiFi.status() != WL_CONNECTED) { + wifiConnect(); + } + + if (millis() - lastImuMs >= 50) { + lastImuMs = millis(); + updateMotion(); + } + + processGPS(); + + if (millis() - lastPushMs >= 5000) { + lastPushMs = millis(); + pushToHA(); + } +}