#ifndef Peripherals_h #define Peripherals_h #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_CONFIG_FILE "/config/peripheral.json" #define EVENT_CONFIGURATION_SETTINGS "peripheralSettings" #define CONFIGURATION_SETTINGS_PATH "/api/peripheral/settings" #define EVENT_I2C_SCAN "i2cScan" #define I2C_INTERVAL 500 #define MAX_ESP_IMU_SIZE 500 #define EVENT_IMU "imu" /* * Servo Settings */ #ifndef FACTORY_SERVO_PWM_FREQUENCY #define FACTORY_SERVO_PWM_FREQUENCY 50 #endif #ifndef FACTORY_SERVO_OSCILLATOR_FREQUENCY #define FACTORY_SERVO_OSCILLATOR_FREQUENCY 27000000 #endif /* * OLED Settings */ #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define SCREEN_RESET -1 /* * I2C software connection */ #ifndef SDA_PIN #define SDA_PIN SDA #endif #ifndef SCL_PIN #define SCL_PIN SCL #endif #ifndef I2C_FREQUENCY #define I2C_FREQUENCY 100000UL #endif class PinConfig { public: int pin; String mode; String type; String role; PinConfig(int p, String m, String t, String r) : pin(p), mode(m), type(t), role(r) {} }; class PeripheralsConfiguration { public: int sda = SDA_PIN; int scl = SCL_PIN; long frequency = I2C_FREQUENCY; std::vector pins; static void read(PeripheralsConfiguration &settings, JsonObject &root) { root["sda"] = settings.sda; root["scl"] = settings.scl; root["frequency"] = settings.frequency; } static StateUpdateResult update(JsonObject &root, PeripheralsConfiguration &settings) { settings.sda = root["sda"] | SDA_PIN; settings.scl = root["scl"] | SCL_PIN; settings.frequency = root["frequency"] | I2C_FREQUENCY; return StateUpdateResult::CHANGED; }; }; class Peripherals : public StatefulService { public: Peripherals(PsychicHttpServer *server, FS *fs, SecurityManager *securityManager, EventSocket *socket) : _server(server), _socket(socket), _securityManager(securityManager), _httpEndpoint(PeripheralsConfiguration::read, PeripheralsConfiguration::update, this, server, CONFIGURATION_SETTINGS_PATH, securityManager, AuthenticationPredicates::IS_ADMIN), _eventEndpoint(PeripheralsConfiguration::read, PeripheralsConfiguration::update, this, socket, EVENT_CONFIGURATION_SETTINGS), #if FT_ENABLED(FT_MAG) _mag(12345), #endif #if FT_ENABLED(FT_BMP) _bmp(10085), #endif _fsPersistence(PeripheralsConfiguration::read, PeripheralsConfiguration::update, this, fs, DEVICE_CONFIG_FILE) { addUpdateHandler([&](const String &originId) { updatePins(); }, false); }; void begin() { _httpEndpoint.begin(); _eventEndpoint.begin(); _fsPersistence.readFromFS(); _socket->onEvent(EVENT_I2C_SCAN, [&](JsonObject &root, int originId) { scanI2C(); emitI2C(); }); _socket->onSubscribe(EVENT_I2C_SCAN, [&](const String &originId, bool sync) { scanI2C(); emitI2C(originId, sync); }); updatePins(); #if FT_ENABLED(FT_SERVO) _pca.begin(); _pca.setPWMFreq(FACTORY_SERVO_PWM_FREQUENCY); _pca.setOscillatorFrequency(FACTORY_SERVO_OSCILLATOR_FREQUENCY); #endif #if FT_ENABLED(FT_IMU) _imu.initialize(); imu_success = _imu.testConnection(); devStatus = _imu.dmpInitialize(); if (!imu_success) { ESP_LOGE("IMUService", "MPU initialize failed"); } _imu.setDMPEnabled(true); _imu.setI2CMasterModeEnabled(false); _imu.setI2CBypassEnabled(true); _imu.setSleepEnabled(false); #endif #if FT_ENABLED(FT_MAG) mag_success = _mag.begin(); if (!mag_success) { ESP_LOGE("IMUService", "MAG initialize failed"); } #endif #if FT_ENABLED(FT_BMP) bmp_success = _bmp.begin(); if (!bmp_success) { ESP_LOGE("IMUService", "BMP initialize failed"); } #endif #if FT_ENABLED(FT_ADS1015) || FT_ENABLED(FT_ADS1115) if (!_ads.begin()) { ESP_LOGE("Peripherals", "ADS1015/ADS1115 not found"); } _ads.startADCReading(ADS1X15_REG_CONFIG_MUX_DIFF_0_1, /*continuous=*/false); #endif }; void loop() { EXECUTE_EVERY_N_MS(_updateInterval, updateImu()); } void updatePins() { if (i2c_active) { Wire.end(); } if (_state.sda != -1 && _state.scl != -1) { Wire.begin(_state.sda, _state.scl, _state.frequency); i2c_active = true; } } void emitI2C(const String &originId = "", bool sync = false) { char output[150]; JsonDocument doc; JsonObject root = doc.to(); root["sda"] = _state.sda; root["scl"] = _state.scl; JsonArray addresses = root["addresses"].to(); for (auto &address : addressList) { addresses.add(address); } serializeJson(root, output); ESP_LOGI("Peripherals", "Emitting I2C scan results, %s %d", originId.c_str(), sync); _socket->emit(EVENT_I2C_SCAN, output, originId.c_str(), sync); } void scanI2C(uint8_t lower = 1, uint8_t higher = 127) { addressList.clear(); for (uint8_t address = lower; address < higher; address++) { Wire.beginTransmission(address); if (Wire.endTransmission() == 0) { addressList.emplace_back(address); ESP_LOGI("Peripherals", "I2C device found at address 0x%02X", address); } } uint8_t nDevices = addressList.size(); if (nDevices == 0) ESP_LOGI("Peripherals", "No I2C devices found"); else ESP_LOGI("Peripherals", "Scan complete - Found %d devices", nDevices); } /* SERVO FUNCTIONS*/ void pcaWrite(int index, int value) { #if FT_ENABLED(FT_SERVO) _pca.setPWM(index, 0, value); #endif } void pcaAngle(int index, int value) { #if FT_ENABLED(FT_SERVO) // uint16_t pwm = SERVO_MIN + value * SERVO_ANGLE_PWM_RATIO; _pca.setPWM(index, 0, 125 + value * 2); #endif } /* ADC FUNCTIONS*/ int16_t readADCVoltage(uint8_t channel) { int16_t voltage = -1; #if FT_ENABLED(FT_ADS1015) || FT_ENABLED(FT_ADS1115) float adc0 = _ads.readADC_SingleEnded(channel); voltage = _ads.computeVolts(adc0); #endif return voltage; } /* IMU FUNCTIONS */ bool readIMU() { bool updated = false; #if FT_ENABLED(FT_IMU) updated = imu_success && _imu.dmpGetCurrentFIFOPacket(fifoBuffer); _imu.dmpGetQuaternion(&q, fifoBuffer); _imu.dmpGetGravity(&gravity, &q); _imu.dmpGetYawPitchRoll(ypr, &q, &gravity); #endif return updated; } float getTemp() { float temp = -1; #if FT_ENABLED(FT_IMU) temp = imu_success ? imu_temperature : -1; #endif return temp; } float getAngleX() { float angle = 0; #if FT_ENABLED(FT_IMU) angle = imu_success ? ypr[0] * 180 / M_PI : 0; #endif return angle; } float getAngleY() { float angle = 0; #if FT_ENABLED(FT_IMU) angle = imu_success ? ypr[1] * 180 / M_PI : 0; #endif return angle; } float getAngleZ() { float angle = 0; #if FT_ENABLED(FT_IMU) angle = imu_success ? ypr[2] * 180 / M_PI : 0; #endif return angle; } /* MAG FUNCTIONS */ float getHeading() { float heading = 0; #if FT_ENABLED(FT_MAG) sensors_event_t event; _mag.getEvent(&event); heading = atan2(event.magnetic.y, event.magnetic.x); float declinationAngle = 0.22; heading += declinationAngle; if (heading < 0) heading += 2 * PI; if (heading > 2 * PI) heading -= 2 * PI; heading *= 180 / M_PI; #endif return heading; } /* BMP FUNCTIONS */ float getAltitude() { float altitude = -1; #if FT_ENABLED(FT_MAG) sensors_event_t event; _bmp.getEvent(&event); float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA; altitude = bmp_success && event.pressure ? _bmp.pressureToAltitude(seaLevelPressure, event.pressure) : -1; #endif return altitude; } float getPressure() { float pressure = -1; #if FT_ENABLED(FT_BMP) sensors_event_t event; _bmp.getEvent(&event); pressure = bmp_success && event.pressure ? event.pressure : -1; #endif return pressure; } float getTemperature() { float temperature = 0; #if FT_ENABLED(FT_BMP) _bmp.getTemperature(&temperature); temperature = bmp_success ? temperature : -1; #endif return temperature; } protected: void updateImu() { doc.clear(); bool newData = false; #if FT_ENABLED(FT_IMU) newData = imu_success && readIMU(); if (imu_success) { doc["x"] = round2(getAngleX()); doc["y"] = round2(getAngleY()); doc["z"] = round2(getAngleZ()); } #endif #if FT_ENABLED(FT_MAG) newData = newData || mag_success; if (mag_success) { doc["heading"] = round2(getHeading()); } #endif #if FT_ENABLED(FT_BMP) newData = newData || bmp_success; if (bmp_success) { doc["pressure"] = round2(getPressure()); doc["altitude"] = round2(getAltitude()); doc["bmp_temp"] = round2(getTemperature()); } #endif if (newData) { serializeJson(doc, message); _socket->emit(EVENT_IMU, message); } } private: PsychicHttpServer *_server; EventSocket *_socket; SecurityManager *_securityManager; HttpEndpoint _httpEndpoint; EventEndpoint _eventEndpoint; FSPersistence _fsPersistence; JsonDocument doc; char message[MAX_ESP_IMU_SIZE]; #if FT_ENABLED(FT_SERVO) Adafruit_PWMServoDriver _pca; #endif #if FT_ENABLED(FT_IMU) MPU6050 _imu; bool imu_success {false}; uint8_t devStatus {false}; Quaternion q; uint8_t fifoBuffer[64]; VectorFloat gravity; float ypr[3]; float imu_temperature {-1}; #endif #if FT_ENABLED(FT_MAG) Adafruit_HMC5883_Unified _mag; bool mag_success {false}; #endif #if FT_ENABLED(FT_BMP) Adafruit_BMP085_Unified _bmp; bool bmp_success {false}; #endif #if FT_ENABLED(FT_ADS1015) Adafruit_ADS1015 _ads; #endif #if FT_ENABLED(FT_ADS1115) Adafruit_ADS1115 _ads; #endif std::list addressList; bool i2c_active = false; unsigned long _updateInterval {I2C_INTERVAL}; }; #endif