From 1ff7307a0a899e8dd2a89f1aa6ad2ccdc41abbda Mon Sep 17 00:00:00 2001 From: Dustin Brewer Date: Wed, 21 Nov 2018 22:03:32 -0800 Subject: [PATCH] Initial working component --- ESPHomeLibRoombaComponent.h | 332 ++++++++++++++++++++++++++++++++++++ README.md | 150 ++++++++++++++++ 2 files changed, 482 insertions(+) create mode 100644 ESPHomeLibRoombaComponent.h create mode 100644 README.md diff --git a/ESPHomeLibRoombaComponent.h b/ESPHomeLibRoombaComponent.h new file mode 100644 index 0000000..516938b --- /dev/null +++ b/ESPHomeLibRoombaComponent.h @@ -0,0 +1,332 @@ +#include "esphomelib/application.h" +#include + +using namespace esphomelib; + +static const char *TAG = "component.Roomba"; + +Roomba roomba(&Serial, Roomba::Baud115200); +void roombaWakeup(uint8_t pin) +{ + digitalWrite(pin, HIGH); + delay(100); + digitalWrite(pin, LOW); + delay(500); + digitalWrite(pin, HIGH); + delay(100); +} + +class RoombaOnSwitch : public switch_::Switch +{ + protected: + uint8_t brcPin; + + public: + RoombaOnSwitch(const std::string &name, uint8_t brcPin) : switch_::Switch(name) + { + this->brcPin = brcPin; + } + + void write_state(bool state) override + { + this->publish_state(true); + roombaWakeup(this->brcPin); + roomba.cover(); + this->publish_state(false); + } +}; + +class RoombaOffSwitch : public switch_::Switch +{ + protected: + uint8_t brcPin; + + public: + RoombaOffSwitch(const std::string &name, uint8_t brcPin) : switch_::Switch(name) + { + this->brcPin = brcPin; + } + + void write_state(bool state) override + { + this->publish_state(true); + roombaWakeup(this->brcPin); + roomba.cover(); + this->publish_state(false); + } +}; + +class RoombaDockSwitch : public switch_::Switch +{ + protected: + uint8_t brcPin; + + public: + RoombaDockSwitch(const std::string &name, uint8_t brcPin) : switch_::Switch(name) + { + this->brcPin = brcPin; + } + + void write_state(bool state) override + { + this->publish_state(true); + roombaWakeup(this->brcPin); + roomba.dock(); + this->publish_state(false); + } +}; + +class RoombaLocateSwitch : public switch_::Switch +{ + protected: + uint8_t brcPin; + uint8_t song; + + public: + RoombaLocateSwitch(const std::string &name, uint8_t brcPin, uint8_t song) : switch_::Switch(name) + { + this->brcPin = brcPin; + } + + void write_state(bool state) override + { + this->publish_state(true); + roombaWakeup(this->brcPin); + roomba.playSong(this->song); + this->publish_state(false); + } +}; + +class RoombaSpotSwitch : public switch_::Switch +{ + protected: + uint8_t brcPin; + + public: + RoombaSpotSwitch(const std::string &name, uint8_t brcPin) : switch_::Switch(name) + { + this->brcPin = brcPin; + } + + void write_state(bool state) override + { + this->publish_state(true); + roombaWakeup(this->brcPin); + roomba.spot(); + this->publish_state(false); + } +}; + +class RoombaDistanceSensor : public sensor::Sensor +{ + public: + RoombaDistanceSensor(const std::string &name) : sensor::Sensor(name) {} + std::string unit_of_measurement() override { return "mm"; } + int8_t accuracy_decimals() override { return 1; } +}; + +class RoombaVoltageSensor : public sensor::Sensor +{ + public: + RoombaVoltageSensor(const std::string &name) : sensor::Sensor(name) {} + std::string unit_of_measurement() override { return "mV"; } + int8_t accuracy_decimals() override { return 1; } +}; + +class RoombaCurrentSensor : public sensor::Sensor +{ + public: + RoombaCurrentSensor(const std::string &name) : sensor::Sensor(name) {} + std::string unit_of_measurement() override { return "mA"; } + int8_t accuracy_decimals() override { return 1; } +}; + +class RoombaChargeSensor : public sensor::Sensor +{ + public: + RoombaChargeSensor(const std::string &name) : sensor::Sensor(name) {} + std::string unit_of_measurement() override { return "mAh"; } + int8_t accuracy_decimals() override { return 1; } +}; + +class RoombaCapacitySensor : public sensor::Sensor +{ + public: + RoombaCapacitySensor(const std::string &name) : sensor::Sensor(name) {} + std::string unit_of_measurement() override { return "mAh"; } + int8_t accuracy_decimals() override { return 1; } +}; + +class RoombaChargingStateSensor : public binary_sensor::BinarySensor +{ + public: + RoombaChargingStateSensor(const std::string &name) : binary_sensor::BinarySensor(name) {} +}; + +class RoombaDockedStateSensor : public binary_sensor::BinarySensor +{ + public: + RoombaDockedStateSensor(const std::string &name) : binary_sensor::BinarySensor(name) {} +}; + +class RoombaCleaningStateSensor : public binary_sensor::BinarySensor +{ + public: + RoombaCleaningStateSensor(const std::string &name) : binary_sensor::BinarySensor(name) {} +}; + +class RoombaComponent : public PollingComponent +{ + protected: + uint8_t brcPin; + std::string commandTopic; + + public: + RoombaDistanceSensor *distance_sensor; + RoombaVoltageSensor *voltage_sensor; + RoombaCurrentSensor *current_sensor; + RoombaChargeSensor *charge_sensor; + RoombaCapacitySensor *capacity_sensor; + RoombaChargingStateSensor *chargingState_sensor; + RoombaDockedStateSensor *dockedState_sensor; + RoombaCleaningStateSensor *cleaningState_sensor; + RoombaOnSwitch *on_switch; + RoombaOffSwitch *off_switch; + RoombaDockSwitch *dock_switch; + RoombaLocateSwitch *locate_switch; + RoombaSpotSwitch *spot_switch; + + RoombaComponent( + const std::string &distanceName, + const std::string &voltageName, + const std::string ¤tName, + const std::string &chargeName, + const std::string &capacityName, + const std::string &chargingStateName, + const std::string &dockedStateName, + const std::string &cleaningStateName, + const std::string &onSwitchName, + const std::string &offSwitchName, + const std::string &dockSwitchName, + const std::string &locateSwitchName, + const std::string &spotSwitchName, + const std::string &commandTopic, + uint8_t brcPin, + uint32_t update_interval) + : PollingComponent(update_interval) + { + this->brcPin = brcPin; + this->distance_sensor = new RoombaDistanceSensor(distanceName); + this->voltage_sensor = new RoombaVoltageSensor(voltageName); + this->current_sensor = new RoombaCurrentSensor(currentName); + this->charge_sensor = new RoombaChargeSensor(chargeName); + this->capacity_sensor = new RoombaCapacitySensor(capacityName); + this->chargingState_sensor = new RoombaChargingStateSensor(chargingStateName); + this->dockedState_sensor = new RoombaDockedStateSensor(dockedStateName); + this->cleaningState_sensor = new RoombaCleaningStateSensor(cleaningStateName); + this->on_switch = new RoombaOnSwitch(onSwitchName, brcPin); + this->off_switch = new RoombaOffSwitch(offSwitchName, brcPin); + this->dock_switch = new RoombaDockSwitch(dockSwitchName, brcPin); + this->locate_switch = new RoombaLocateSwitch(locateSwitchName, brcPin, 1); + this->spot_switch = new RoombaSpotSwitch(spotSwitchName, brcPin); + + this->commandTopic = commandTopic; + } + + void setup() override + { + ESP_LOGD(TAG, "Setting up Roomba."); + pinMode(this->brcPin, OUTPUT); + digitalWrite(this->brcPin, HIGH); + + roomba.start(); + + ESP_LOGD(TAG, "Attempting to subscribe to MQTT."); + mqtt::global_mqtt_client->subscribe(this->commandTopic, [&](const std::string &payload) { + ESP_LOGD(TAG, "Got values %s", payload.c_str()); + if (payload == "turn_on") + { + this->on_switch->turn_on(); + } + else if (payload == "turn_off") + { + this->off_switch->turn_on(); + } + else if (payload == "dock") + { + this->dock_switch->turn_on(); + } + else if (payload == "locate") + { + this->locate_switch->turn_on(); + } + else if (payload == "spot") + { + this->spot_switch->turn_on(); + } + else + { + ESP_LOGW(TAG, "Received unknown status payload: %s", payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); + } + + void update() override + { + ESP_LOGD(TAG, "Attempting to update sensor values."); + + int16_t distance; + uint16_t voltage; + int16_t current; + uint16_t charge; + uint16_t capacity; + uint8_t charging; + bool cleaningState; + bool dockedState; + bool chargingState; + // Flush serial buffers + while (Serial.available()) + { + Serial.read(); + } + + uint8_t sensors[] = { + Roomba::SensorDistance, // 2 bytes, mm, signed + Roomba::SensorChargingState, // 1 byte + Roomba::SensorVoltage, // 2 bytes, mV, unsigned + Roomba::SensorCurrent, // 2 bytes, mA, signed + Roomba::SensorBatteryCharge, // 2 bytes, mAh, unsigned + Roomba::SensorBatteryCapacity // 2 bytes, mAh, unsigned + }; + uint8_t values[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + // Serial reading timeout -- https://community.home-assistant.io/t/add-wifi-to-an-older-roomba/23282/52 + bool success = roomba.getSensorsList(sensors, sizeof(sensors), values, sizeof(values)); + if (!success) + { + ESP_LOGD(TAG, "Unable to read sensors from the Roomba."); + return; + } + + distance = values[0] * 256 + values[1]; + voltage = values[3] * 256 + values[4]; + current = values[5] * 256 + values[6]; + charge = values[7] * 256 + values[8]; + capacity = values[9] * 256 + values[10]; + charging = values[2]; + + cleaningState = current < -300; + dockedState = current > -50; + chargingState = charging == Roomba::ChargeStateReconditioningCharging || charging == Roomba::ChargeStateFullChanrging || charging == Roomba::ChargeStateTrickleCharging; + + this->distance_sensor->publish_state(distance); + this->voltage_sensor->publish_state(voltage); + this->current_sensor->publish_state(current); + this->charge_sensor->publish_state(charge); + this->capacity_sensor->publish_state(capacity); + this->chargingState_sensor->publish_state(chargingState); + this->dockedState_sensor->publish_state(dockedState); + this->cleaningState_sensor->publish_state(cleaningState); + } +}; diff --git a/README.md b/README.md new file mode 100644 index 0000000..2572099 --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# Roomba Component for ESPHomeLib + +A barebones wrapper to enable control of a Roomba via ESPHomeLib. +Tested with ESPHomeLib 1.9.1, and a Roomba 650 w/a Wemos D1 Mini installed. + +## Setup/Use + +I've included my `roomba.yaml`, `roomba/src/main.cpp`, and `roomba/platformio.ini` files as examples. My changes are isolated between: + +* `; // Start Roomba Component Addition` +* `; // End Roomba Component Addition`. + +### roomba.yaml + +```yaml +esphomeyaml: + use_custom_code: True + name: Roomba + platform: ESP8266 + board: esp01_1m + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +mqtt: + broker: !secret mqtt_broker + discovery: true + +logger: + level: info + +ota: + +binary_sensor: + - platform: status + name: "Roomba Status" +``` + +### roomba/platformio.ini + +```ini +; Auto generated code by esphomeyaml + +[common] +; // Start Roomba Component Addition +lib_deps = + ESPHomeLibRoombaComponent=https://github.com/mannkind/ESPHomeLibRoombaComponent + Roomba=http://www.airspayce.com/mikem/arduino/Roomba/Roomba-1.4.zip +build_flags = +upload_flags = +; // End Roomba Component Addition + +; ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== +; ========== AUTO GENERATED CODE BEGIN =========== +[env:roomba] +platform = espressif8266@>=1.8.0 +board = esp01_1m +framework = arduino +lib_deps = + ArduinoOTA + ESP8266WiFi + ESP8266mDNS + Hash + https://github.com/OttoWinter/esphomelib.git#v1.9.1 + ${common.lib_deps} +build_flags = + -DESPHOMELIB_LOG_LEVEL=ESPHOMELIB_LOG_LEVEL_INFO + -DUSE_NEW_OTA + ${common.build_flags} +upload_speed = 115200 +; =========== AUTO GENERATED CODE END ============ +; ========= YOU CAN EDIT AFTER THIS LINE ========= +``` + +### roomba/src/main.cpp + +```c++ +// Auto generated code by esphomeyaml +#include "esphomelib/application.h" +; // Start Roomba Component Addition +#include "ESPHomeLibRoombaComponent.h" +; // End Roomba Component Addition + +using namespace esphomelib; + +void setup() { + // ===== DO NOT EDIT ANYTHING BELOW THIS LINE ===== + // ========== AUTO GENERATED CODE BEGIN =========== + App.set_name("Roomba"); + App.set_compilation_datetime(__DATE__ ", " __TIME__); + ::LogComponent *_logcomponent = App.init_log(115200); + _logcomponent->set_global_log_level(ESPHOMELIB_LOG_LEVEL_INFO); + ::WiFiComponent *_wificomponent = App.init_wifi(); + _wificomponent->set_sta(::WiFiAp{ + .ssid = "", + .password = "", + .channel = -1, + }); + ::OTAComponent *_otacomponent = App.init_ota(); + _otacomponent->start_safe_mode(); + mqtt::MQTTClientComponent *mqtt_mqttclientcomponent = App.init_mqtt("", 1883, "", ""); + ::Application::MakeStatusBinarySensor _application_makestatusbinarysensor = App.make_status_binary_sensor("Roomba Status"); + binary_sensor::StatusBinarySensor *binary_sensor_statusbinarysensor = _application_makestatusbinarysensor.status; + binary_sensor::MQTTBinarySensorComponent *binary_sensor_mqttbinarysensorcomponent = _application_makestatusbinarysensor.mqtt; + // =========== AUTO GENERATED CODE END ============ + // ========= YOU CAN EDIT AFTER THIS LINE ========= + ; // Start Roomba Component Addition + auto *custom_roomba = new RoombaComponent( + "Distance", + "Voltage", + "Current", + "Charge", + "Capacity", + "Charging", + "Docked", + "Cleaning", + "On", + "Off", + "Dock", + "Locate", + "Spot Clean", + "Roomba/command", /* command_topic */ + 4, /* BRC Pin */ + 5000 /* Update Interval */ + ); + + App.register_component(custom_roomba); + App.register_sensor(custom_roomba->distance_sensor); + App.register_sensor(custom_roomba->voltage_sensor); + App.register_sensor(custom_roomba->current_sensor); + App.register_sensor(custom_roomba->charge_sensor); + App.register_sensor(custom_roomba->capacity_sensor); + App.register_binary_sensor(custom_roomba->chargingState_sensor); + App.register_binary_sensor(custom_roomba->dockedState_sensor); + App.register_binary_sensor(custom_roomba->cleaningState_sensor); + App.register_switch(custom_roomba->on_switch); + App.register_switch(custom_roomba->off_switch); + App.register_switch(custom_roomba->dock_switch); + App.register_switch(custom_roomba->locate_switch); + App.register_switch(custom_roomba->spot_switch); + ; // End Roomba Component Addition + App.setup(); +} + +void loop() { + App.loop(); + delay(16); +} +```