diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2996155..fffa5a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - main + - ratgdo32 pull_request: schedule: - cron: '0 4 * * 1' @@ -55,6 +55,24 @@ jobs: - file: v25iboard_drycontact.yaml name: V2.5i Board Dry Contact manifest_filename: v25iboard_drycontact-manifest.json + - file: v32board.yaml + name: V32 Board Security+ 2.0 + manifest_filename: v32board-manifest.json + - file: v32board_secplusv1.yaml + name: V32 Board Security+ 1.0 + manifest_filename: v32board_secplusv1-manifest.json + - file: v32board_drycontact.yaml + name: V32 Board Board Dry Contact + manifest_filename: v32board_drycontact-manifest.json + - file: v32disco.yaml + name: V32 Disco Board Security+ 2.0 + manifest_filename: v32disco-manifest.json + - file: v32disco_secplusv1.yaml + name: V32 Disco Board Security+ 1.0 + manifest_filename: v32disco_secplusv1-manifest.json + - file: v32disco_drycontact.yaml + name: V32 Disco Board Dry Contact + manifest_filename: v32disco_drycontact-manifest.json fail-fast: false steps: - name: Checkout source code @@ -79,7 +97,7 @@ jobs: consolidate: - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/ratgdo32' name: Consolidate firmwares runs-on: ubuntu-latest needs: build @@ -101,7 +119,7 @@ jobs: path: output deploy: - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/ratgdo32' name: Deploy to GitHub Pages runs-on: ubuntu-latest needs: consolidate diff --git a/README.md b/README.md index f71baa5..5669440 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,12 @@ # ratgdo for ESPHome -This is a port of the ratgdo software for the v2.0/v2.5 board to ESPHome. - -[Visit the github.io page to purchase boards](https://paulwieland.github.io/ratgdo/#order) +[Visit the ratcloud.llc to purchase boards](https://ratcloud.llc) ## Installation - Flash the ESPHome based firmware using the [Web Installer](https://ratgdo.github.io/esphome-ratgdo/) -It is no longer necessary to save the rolling code counter when switching between firmware. - ## First use after adding to Home Assistant The ESPHome firmware will allow you to open the door to any position after calibration. To calibrate the door, open and close it once without stopping. diff --git a/base.yaml b/base.yaml index 7511fe3..895610d 100644 --- a/base.yaml +++ b/base.yaml @@ -1,10 +1,13 @@ --- - external_components: - source: type: git url: https://github.com/ratgdo/esphome-ratgdo + ref: ratgdo32 refresh: 1s + # - source: + # type: local + # path: components safe_mode: diff --git a/base_drycontact.yaml b/base_drycontact.yaml index 84b172b..8265070 100644 --- a/base_drycontact.yaml +++ b/base_drycontact.yaml @@ -2,11 +2,13 @@ external_components: - source: - # type: local - # path: components type: git url: https://github.com/ratgdo/esphome-ratgdo + ref: ratgdo32 refresh: 1s + # - source: + # type: local + # path: components safe_mode: @@ -20,6 +22,7 @@ text_sensor: ratgdo: id: ${id_prefix} output_gdo_pin: ${uart_tx_pin} + input_gdo_pin: ${uart_rx_pin} input_obst_pin: ${input_obst_pin} dry_contact_open_sensor: ${id_prefix}_dry_contact_open dry_contact_close_sensor: ${id_prefix}_dry_contact_close diff --git a/base_secplusv1.yaml b/base_secplusv1.yaml index 4248cac..5266d0f 100644 --- a/base_secplusv1.yaml +++ b/base_secplusv1.yaml @@ -4,7 +4,11 @@ external_components: - source: type: git url: https://github.com/ratgdo/esphome-ratgdo + ref: ratgdo32 refresh: 1s + # - source: + # type: local + # path: components safe_mode: diff --git a/components/ratgdo/binary_sensor/__init__.py b/components/ratgdo/binary_sensor/__init__.py index 91a215c..7025eb8 100644 --- a/components/ratgdo/binary_sensor/__init__.py +++ b/components/ratgdo/binary_sensor/__init__.py @@ -18,6 +18,9 @@ TYPES = { "obstruction": SensorType.RATGDO_SENSOR_OBSTRUCTION, "motor": SensorType.RATGDO_SENSOR_MOTOR, "button": SensorType.RATGDO_SENSOR_BUTTON, + "vehicle_detected": SensorType.RATGDO_SENSOR_VEHICLE_DETECTED, + "vehicle_arriving": SensorType.RATGDO_SENSOR_VEHICLE_ARRIVING, + "vehicle_leaving": SensorType.RATGDO_SENSOR_VEHICLE_LEAVING, } diff --git a/components/ratgdo/binary_sensor/ratgdo_binary_sensor.cpp b/components/ratgdo/binary_sensor/ratgdo_binary_sensor.cpp index 456058c..2c50669 100644 --- a/components/ratgdo/binary_sensor/ratgdo_binary_sensor.cpp +++ b/components/ratgdo/binary_sensor/ratgdo_binary_sensor.cpp @@ -28,6 +28,22 @@ namespace ratgdo { this->parent_->subscribe_button_state([=](ButtonState state) { this->publish_state(state == ButtonState::PRESSED); }); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_DETECTED) { + this->publish_initial_state(false); + this->parent_->subscribe_vehicle_detected_state([=](VehicleDetectedState state) { + this->publish_state(state == VehicleDetectedState::YES); + this->parent_->presence_change(state == VehicleDetectedState::YES); + }); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_ARRIVING) { + this->publish_initial_state(false); + this->parent_->subscribe_vehicle_arriving_state([=](VehicleArrivingState state) { + this->publish_state(state == VehicleArrivingState::YES); + }); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_LEAVING) { + this->publish_initial_state(false); + this->parent_->subscribe_vehicle_leaving_state([=](VehicleLeavingState state) { + this->publish_state(state == VehicleLeavingState::YES); + }); } } @@ -42,6 +58,12 @@ namespace ratgdo { ESP_LOGCONFIG(TAG, " Type: Motor"); } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_BUTTON) { ESP_LOGCONFIG(TAG, " Type: Button"); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_DETECTED) { + ESP_LOGCONFIG(TAG, " Type: VehicleDetected"); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_ARRIVING) { + ESP_LOGCONFIG(TAG, " Type: VehicleArriving"); + } else if (this->binary_sensor_type_ == SensorType::RATGDO_SENSOR_VEHICLE_LEAVING) { + ESP_LOGCONFIG(TAG, " Type: VehicleLeaving"); } } diff --git a/components/ratgdo/binary_sensor/ratgdo_binary_sensor.h b/components/ratgdo/binary_sensor/ratgdo_binary_sensor.h index 9e3315a..1d738c3 100644 --- a/components/ratgdo/binary_sensor/ratgdo_binary_sensor.h +++ b/components/ratgdo/binary_sensor/ratgdo_binary_sensor.h @@ -12,7 +12,10 @@ namespace ratgdo { RATGDO_SENSOR_MOTION, RATGDO_SENSOR_OBSTRUCTION, RATGDO_SENSOR_MOTOR, - RATGDO_SENSOR_BUTTON + RATGDO_SENSOR_BUTTON, + RATGDO_SENSOR_VEHICLE_DETECTED, + RATGDO_SENSOR_VEHICLE_ARRIVING, + RATGDO_SENSOR_VEHICLE_LEAVING, }; class RATGDOBinarySensor : public binary_sensor::BinarySensor, public RATGDOClient, public Component { diff --git a/components/ratgdo/number/__init__.py b/components/ratgdo/number/__init__.py index 59bd7d2..2ffc1dc 100644 --- a/components/ratgdo/number/__init__.py +++ b/components/ratgdo/number/__init__.py @@ -16,6 +16,8 @@ TYPES = { "rolling_code_counter": NumberType.RATGDO_ROLLING_CODE_COUNTER, "opening_duration": NumberType.RATGDO_OPENING_DURATION, "closing_duration": NumberType.RATGDO_CLOSING_DURATION, + "closing_delay": NumberType.RATGDO_CLOSING_DELAY, + "target_distance_measurement": NumberType.RATGDO_TARGET_DISTANCE_MEASUREMENT, } diff --git a/components/ratgdo/number/ratgdo_number.cpp b/components/ratgdo/number/ratgdo_number.cpp index 68df7b5..40ab378 100644 --- a/components/ratgdo/number/ratgdo_number.cpp +++ b/components/ratgdo/number/ratgdo_number.cpp @@ -30,6 +30,10 @@ namespace ratgdo { ESP_LOGCONFIG(TAG, " Type: Opening Duration"); } else if (this->number_type_ == RATGDO_CLOSING_DURATION) { ESP_LOGCONFIG(TAG, " Type: Closing Duration"); + } else if (this->number_type_ == RATGDO_CLOSING_DELAY) { + ESP_LOGCONFIG(TAG, " Type: Closing Delay"); + } else if (this->number_type_ == RATGDO_TARGET_DISTANCE_MEASUREMENT) { + ESP_LOGCONFIG(TAG, " Type: Target Distance Measurement"); } } @@ -66,6 +70,14 @@ namespace ratgdo { this->parent_->subscribe_closing_duration([=](float value) { this->update_state(value); }); + } else if (this->number_type_ == RATGDO_CLOSING_DELAY) { + this->parent_->subscribe_closing_delay([=](uint32_t value) { + this->update_state(value); + }); + } else if (this->number_type_ == RATGDO_TARGET_DISTANCE_MEASUREMENT) { + // this->parent_->subscribe_target_distance_measurement([=](float value) { + // this->update_state(value); + // }); } } @@ -76,12 +88,20 @@ namespace ratgdo { this->traits.set_step(0.1); this->traits.set_min_value(0.0); this->traits.set_max_value(180.0); + } else if (this->number_type_ == RATGDO_CLOSING_DELAY) { + this->traits.set_step(1); + this->traits.set_min_value(0.0); + this->traits.set_max_value(60.0); } else if (this->number_type_ == RATGDO_ROLLING_CODE_COUNTER) { this->traits.set_max_value(0xfffffff); } else if (this->number_type_ == RATGDO_CLIENT_ID) { this->traits.set_step(0x1000); this->traits.set_min_value(0x539); this->traits.set_max_value(0x7ff539); + } else if (this->number_type_ == RATGDO_TARGET_DISTANCE_MEASUREMENT) { + this->traits.set_step(1); + this->traits.set_min_value(5); + this->traits.set_max_value(3500); } } @@ -102,9 +122,13 @@ namespace ratgdo { this->parent_->set_opening_duration(value); } else if (this->number_type_ == RATGDO_CLOSING_DURATION) { this->parent_->set_closing_duration(value); + } else if (this->number_type_ == RATGDO_CLOSING_DELAY) { + this->parent_->set_closing_delay(value); } else if (this->number_type_ == RATGDO_CLIENT_ID) { value = normalize_client_id(value); this->parent_->call_protocol(SetClientID { static_cast(value) }); + } else if (this->number_type_ == RATGDO_TARGET_DISTANCE_MEASUREMENT) { + this->parent_->set_target_distance_measurement(value); } this->update_state(value); } diff --git a/components/ratgdo/number/ratgdo_number.h b/components/ratgdo/number/ratgdo_number.h index e5f78f6..cd85130 100644 --- a/components/ratgdo/number/ratgdo_number.h +++ b/components/ratgdo/number/ratgdo_number.h @@ -13,6 +13,8 @@ namespace ratgdo { RATGDO_ROLLING_CODE_COUNTER, RATGDO_OPENING_DURATION, RATGDO_CLOSING_DURATION, + RATGDO_CLOSING_DELAY, + RATGDO_TARGET_DISTANCE_MEASUREMENT, }; class RATGDONumber : public number::Number, public RATGDOClient, public Component { diff --git a/components/ratgdo/output/__init__.py b/components/ratgdo/output/__init__.py new file mode 100644 index 0000000..957ac21 --- /dev/null +++ b/components/ratgdo/output/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import rtttl +from esphome.const import CONF_ID +CONF_RTTTL = "rtttl" +CONF_SONG = "song" + + +from .. import RATGDO_CLIENT_SCHMEA, ratgdo_ns, register_ratgdo_child + +DEPENDENCIES = ["esp32","ratgdo","rtttl"] + +RATGDOOutput = ratgdo_ns.class_("RATGDOOutput", cg.Component) +OutputType = ratgdo_ns.enum("OutputType") + +CONF_TYPE = "type" +TYPES = { + "beeper": OutputType.RATGDO_BEEPER +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(RATGDOOutput), + cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), + cv.Required(CONF_RTTTL): cv.use_id(rtttl), + cv.Required(CONF_SONG): cv.string + } +).extend(RATGDO_CLIENT_SCHMEA) + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + rtttl = await cg.get_variable(config[CONF_RTTTL]) + cg.add(var.set_rtttl(rtttl)) + cg.add(var.set_song(config[CONF_SONG])) + await register_ratgdo_child(var, config) diff --git a/components/ratgdo/output/ratgdo_output.cpp b/components/ratgdo/output/ratgdo_output.cpp new file mode 100644 index 0000000..ba389ba --- /dev/null +++ b/components/ratgdo/output/ratgdo_output.cpp @@ -0,0 +1,58 @@ +#include "ratgdo_output.h" +#include "../ratgdo_state.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ratgdo { + + static const char* TAG = "ratgdo.output"; + + void RATGDOOutput::setup() + { + ESP_LOGD(TAG, "Output was setup"); + + if (this->output_type_ == OutputType::RATGDO_BEEPER) { + this->beeper_->add_on_finished_playback_callback([=] { this->finished_playback(); }); + + this->parent_->subscribe_vehicle_arriving_state([=](VehicleArrivingState state) { + if (state == VehicleArrivingState::YES) { + this->play(); + } + }); + + this->parent_->subscribe_door_action_delayed([=](DoorActionDelayed state) { + if (state == DoorActionDelayed::YES) { + this->play(); + this->repeat_ = true; + } else if (state == DoorActionDelayed::NO) { + this->repeat_ = false; + } + }); + } + } + + void RATGDOOutput::play() + { + this->beeper_->play(this->rtttlSong_); + } + + void RATGDOOutput::finished_playback() + { + if (this->repeat_) + this->play(); + } + + void RATGDOOutput::dump_config() + { + if (this->output_type_ == OutputType::RATGDO_BEEPER) { + ESP_LOGCONFIG(TAG, " Type: Beeper"); + } + } + + void RATGDOOutput::set_output_type(OutputType output_type_) + { + this->output_type_ = output_type_; + } + +} // namespace ratgdo +} // namespace esphome diff --git a/components/ratgdo/output/ratgdo_output.h b/components/ratgdo/output/ratgdo_output.h new file mode 100644 index 0000000..f15a5d6 --- /dev/null +++ b/components/ratgdo/output/ratgdo_output.h @@ -0,0 +1,32 @@ +#pragma once + +#include "../ratgdo.h" +#include "esphome/components/rtttl/rtttl.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace ratgdo { + + enum OutputType { + RATGDO_BEEPER + }; + + class RATGDOOutput : public RATGDOClient, public Component { + public: + void setup() override; + void play(); + void finished_playback(); + void dump_config() override; + void set_output_type(OutputType output_type); + void set_song(std::string rtttlSong) { this->rtttlSong_ = rtttlSong; } + void set_rtttl(rtttl::Rtttl* output) { this->beeper_ = output; } + + protected: + OutputType output_type_; + rtttl::Rtttl* beeper_; + std::string rtttlSong_; + bool repeat_; + }; + +} // namespace ratgdo +} // namespace esphome diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp index 90cb713..309ff85 100644 --- a/components/ratgdo/ratgdo.cpp +++ b/components/ratgdo/ratgdo.cpp @@ -30,6 +30,10 @@ namespace ratgdo { static const char* const TAG = "ratgdo"; static const int SYNC_DELAY = 1000; + static const int CLEAR_PRESENCE = 60000; // how long to keep arriving/leaving active + static const int PRESENCE_DETECT_WINDOW = 300000; // how long to calculate presence after door state change + static const int MIN_DISTANCE = 20; // ignore bugs crawling on the distance sensor + void RATGDOComponent::setup() { this->output_gdo_pin_->setup(); @@ -39,7 +43,11 @@ namespace ratgdo { this->input_gdo_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); this->input_obst_pin_->setup(); +#ifdef USE_ESP32 + this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); +#else this->input_obst_pin_->pin_mode(gpio::FLAG_INPUT); +#endif this->input_obst_pin_->attach_interrupt(RATGDOStore::isr_obstruction, &this->isr_store_, gpio::INTERRUPT_FALLING_EDGE); this->protocol_->setup(this, &App.scheduler, this->input_gdo_pin_, this->output_gdo_pin_); @@ -51,6 +59,24 @@ namespace ratgdo { ESP_LOGD(TAG, "| -| | | | | | | | | | |"); ESP_LOGD(TAG, "|__|__|__|__| |_| |_____|____/|_____|"); ESP_LOGD(TAG, "https://paulwieland.github.io/ratgdo/"); + + this->subscribe_door_state([=](DoorState state, float position) { + static DoorState lastState = DoorState::UNKNOWN; + + if (lastState != DoorState::UNKNOWN && state != DoorState::CLOSED && !this->presence_detect_window_active_) { + this->presence_detect_window_active_ = true; + set_timeout("presence_detect_window", PRESENCE_DETECT_WINDOW, [=] { + this->presence_detect_window_active_ = false; + }); + } + + if (state == DoorState::CLOSED) { + this->presence_detect_window_active_ = false; + cancel_timeout("presence_detect_window"); + } + + lastState = state; + }); } // initializing protocol, this gets called before setup() because @@ -335,6 +361,70 @@ namespace ratgdo { this->closing_duration = duration; } + void RATGDOComponent::set_target_distance_measurement(int16_t distance) + { + this->target_distance_measurement = distance; + } + + void RATGDOComponent::set_distance_measurement(int16_t distance) + { + if (distance > 0 && distance < MIN_DISTANCE) + return; + + this->last_distance_measurement = distance; + + // current value = [0], last value = [1] + this->distance_measurement.insert(this->distance_measurement.begin(), distance); + this->distance_measurement.pop_back(); + this->calculate_presence(); + } + + void RATGDOComponent::calculate_presence() + { + bool all_in_range = true; + bool all_out_of_range = true; + // int16_t min = *this->target_distance_measurement - PRESENCE_DETECT_TOLERANCE; + // int16_t max = *this->target_distance_measurement + PRESENCE_DETECT_TOLERANCE; + + for (int16_t value : this->distance_measurement) { + // if (value < min || value > max || value == -1) { + if (value >= *this->target_distance_measurement || value == -1) { + all_in_range = false; + } + + if (value < *this->target_distance_measurement && value != -1) { + all_out_of_range = false; + } + } + + if (all_in_range) + this->vehicle_detected_state = VehicleDetectedState::YES; + if (all_out_of_range) + this->vehicle_detected_state = VehicleDetectedState::NO; + + // auto k = this->distance_measurement; + // ESP_LOGD(TAG,"measure: %i,%i,%i,%i,%i,%i,%i,%i,%i,%i; target: %i; all_in: %s; all_out: %s;", k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7],k[8],k[9], *this->target_distance_measurement, all_in_range ? "y" : "n", all_out_of_range ? "y" : "n"); + } + + void RATGDOComponent::presence_change(bool sensor_value) + { + if (this->presence_detect_window_active_) { + if (sensor_value) { + this->vehicle_arriving_state = VehicleArrivingState::YES; + this->vehicle_leaving_state = VehicleLeavingState::NO; + set_timeout(CLEAR_PRESENCE, [=] { + this->vehicle_arriving_state = VehicleArrivingState::NO; + }); + } else { + this->vehicle_arriving_state = VehicleArrivingState::NO; + this->vehicle_leaving_state = VehicleLeavingState::YES; + set_timeout(CLEAR_PRESENCE, [=] { + this->vehicle_leaving_state = VehicleLeavingState::NO; + }); + } + } + } + Result RATGDOComponent::call_protocol(Args args) { return this->protocol_->call(args); @@ -359,7 +449,7 @@ namespace ratgdo { if (current_millis - last_millis > CHECK_PERIOD) { // ESP_LOGD(TAG, "%ld: Obstruction count: %d, expected: %d, since asleep: %ld", - // current_millis, this->isr_store_.obstruction_low_count, PULSES_EXPECTED, + // current_millis, this->isr_store_.obstruction_low_count, PULSES_LOWER_LIMIT, // current_millis - last_asleep // ); @@ -369,7 +459,11 @@ namespace ratgdo { this->obstruction_sensor_detected_ = true; } else if (this->isr_store_.obstruction_low_count == 0) { // if there have been no pulses the line is steady high or low +#ifdef USE_ESP32 + if (this->input_obst_pin_->digital_read()) { +#else if (!this->input_obst_pin_->digital_read()) { +#endif // asleep last_asleep = current_millis; } else { @@ -494,7 +588,15 @@ namespace ratgdo { void RATGDOComponent::door_action(DoorAction action) { - this->protocol_->door_action(action); + if (*this->closing_delay > 0 && action == DoorAction::CLOSE) { + this->door_action_delayed = DoorActionDelayed::YES; + set_timeout("door_action", *this->closing_delay * 1000, [=] { + this->door_action_delayed = DoorActionDelayed::NO; + this->protocol_->door_action(DoorAction::CLOSE); + }); + } else { + this->protocol_->door_action(action); + } } void RATGDOComponent::door_move_to_position(float position) @@ -614,6 +716,10 @@ namespace ratgdo { { this->closing_duration.subscribe([=](float state) { defer("closing_duration", [=] { f(state); }); }); } + void RATGDOComponent::subscribe_closing_delay(std::function&& f) + { + this->closing_delay.subscribe([=](uint32_t state) { defer("closing_delay", [=] { f(state); }); }); + } void RATGDOComponent::subscribe_openings(std::function&& f) { this->openings.subscribe([=](uint16_t state) { defer("openings", [=] { f(state); }); }); @@ -640,11 +746,14 @@ namespace ratgdo { } void RATGDOComponent::subscribe_door_state(std::function&& f) { + static int num = 0; + auto name = "door_state" + std::to_string(num++); + this->door_state.subscribe([=](DoorState state) { - defer("door_state", [=] { f(state, *this->door_position); }); + defer(name, [=] { f(state, *this->door_position); }); }); this->door_position.subscribe([=](float position) { - defer("door_state", [=] { f(*this->door_state, position); }); + defer(name, [=] { f(*this->door_state, position); }); }); } void RATGDOComponent::subscribe_light_state(std::function&& f) @@ -679,6 +788,37 @@ namespace ratgdo { { this->learn_state.subscribe([=](LearnState state) { defer("learn_state", [=] { f(state); }); }); } + void RATGDOComponent::subscribe_door_action_delayed(std::function&& f) + { + static int num = 0; + auto name = "door_action_delayed" + std::to_string(num++); + + this->door_action_delayed.subscribe([=](DoorActionDelayed state) { defer(name, [=] { f(state); }); }); + } + void RATGDOComponent::subscribe_distance_measurement(std::function&& f) + { + static int num = 0; + auto name = "last_distance_measurement" + std::to_string(num++); + this->last_distance_measurement.subscribe([=](int16_t state) { defer(name, [=] { f(state); }); }); + } + void RATGDOComponent::subscribe_vehicle_detected_state(std::function&& f) + { + static int num = 0; + auto name = "vehicle_detected_state" + std::to_string(num++); + this->vehicle_detected_state.subscribe([=](VehicleDetectedState state) { defer(name, [=] { f(state); }); }); + } + void RATGDOComponent::subscribe_vehicle_arriving_state(std::function&& f) + { + static int num = 0; + auto name = "vehicle_arriving_state" + std::to_string(num++); + this->vehicle_arriving_state.subscribe([=](VehicleArrivingState state) { defer(name, [=] { f(state); }); }); + } + void RATGDOComponent::subscribe_vehicle_leaving_state(std::function&& f) + { + static int num = 0; + auto name = "vehicle_leaving_state" + std::to_string(num++); + this->vehicle_leaving_state.subscribe([=](VehicleLeavingState state) { defer(name, [=] { f(state); }); }); + } // dry contact methods void RATGDOComponent::set_dry_contact_open_sensor(esphome::binary_sensor::BinarySensor* dry_contact_open_sensor) diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h index 228d5bc..35a9b46 100644 --- a/components/ratgdo/ratgdo.h +++ b/components/ratgdo/ratgdo.h @@ -62,6 +62,11 @@ namespace ratgdo { observable opening_duration { 0 }; float start_closing { -1 }; observable closing_duration { 0 }; + observable closing_delay { 0 }; + + observable target_distance_measurement { -1 }; + std::vector distance_measurement { std::vector(10, -1) }; // the length of this vector determines how many in-range readings are required for presence detection to change states + observable last_distance_measurement { 0 }; observable openings { 0 }; // number of times the door has been opened observable paired_total { PAIRED_DEVICES_UNKNOWN }; @@ -72,6 +77,7 @@ namespace ratgdo { observable door_state { DoorState::UNKNOWN }; observable door_position { DOOR_POSITION_UNKNOWN }; + observable door_action_delayed { DoorActionDelayed::NO }; unsigned long door_start_moving { 0 }; float door_start_position { DOOR_POSITION_UNKNOWN }; @@ -84,6 +90,9 @@ namespace ratgdo { observable button_state { ButtonState::UNKNOWN }; observable motion_state { MotionState::UNKNOWN }; observable learn_state { LearnState::UNKNOWN }; + observable vehicle_detected_state { VehicleDetectedState::NO }; + observable vehicle_arriving_state { VehicleArrivingState::NO }; + observable vehicle_leaving_state { VehicleLeavingState::NO }; OnceCallbacks on_door_state_; @@ -127,9 +136,14 @@ namespace ratgdo { void set_door_position(float door_position) { this->door_position = door_position; } void set_opening_duration(float duration); void set_closing_duration(float duration); + void set_closing_delay(uint32_t delay) { this->closing_delay = delay; } void schedule_door_position_sync(float update_period = 500); void door_position_update(); void cancel_position_sync_callbacks(); + void set_target_distance_measurement(int16_t distance); + void set_distance_measurement(int16_t distance); + void calculate_presence(); + void presence_change(bool sensor_value); // light void light_toggle(); @@ -158,6 +172,7 @@ namespace ratgdo { void subscribe_rolling_code_counter(std::function&& f); void subscribe_opening_duration(std::function&& f); void subscribe_closing_duration(std::function&& f); + void subscribe_closing_delay(std::function&& f); void subscribe_openings(std::function&& f); void subscribe_paired_devices_total(std::function&& f); void subscribe_paired_remotes(std::function&& f); @@ -173,11 +188,17 @@ namespace ratgdo { void subscribe_motion_state(std::function&& f); void subscribe_sync_failed(std::function&& f); void subscribe_learn_state(std::function&& f); + void subscribe_door_action_delayed(std::function&& f); + void subscribe_distance_measurement(std::function&& f); + void subscribe_vehicle_detected_state(std::function&& f); + void subscribe_vehicle_arriving_state(std::function&& f); + void subscribe_vehicle_leaving_state(std::function&& f); protected: RATGDOStore isr_store_ {}; protocol::Protocol* protocol_; bool obstruction_sensor_detected_ { false }; + bool presence_detect_window_active_ { false }; InternalGPIOPin* output_gdo_pin_; InternalGPIOPin* input_gdo_pin_; diff --git a/components/ratgdo/ratgdo_state.h b/components/ratgdo/ratgdo_state.h index f71440a..679cc8b 100644 --- a/components/ratgdo/ratgdo_state.h +++ b/components/ratgdo/ratgdo_state.h @@ -26,6 +26,10 @@ namespace ratgdo { (OPENING, 4), (CLOSING, 5)) + ENUM(DoorActionDelayed, uint8_t, + (NO, 0), + (YES, 1)) + /// Enum for all states a the light can be in. ENUM(LightState, uint8_t, (OFF, 0), @@ -104,6 +108,18 @@ namespace ratgdo { (STOP, 3), (UNKNOWN, 4)) + ENUM(VehicleDetectedState, uint8_t, + (NO, 0), + (YES, 1)) + + ENUM(VehicleArrivingState, uint8_t, + (NO, 0), + (YES, 1)) + + ENUM(VehicleLeavingState, uint8_t, + (NO, 0), + (YES, 1)) + struct Openings { uint16_t count; uint8_t flag; diff --git a/components/ratgdo/sensor/__init__.py b/components/ratgdo/sensor/__init__.py index 5a593c7..d87d320 100644 --- a/components/ratgdo/sensor/__init__.py +++ b/components/ratgdo/sensor/__init__.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import CONF_ID +CONF_DISTANCE = "distance" from .. import RATGDO_CLIENT_SCHMEA, ratgdo_ns, register_ratgdo_child @@ -18,6 +19,7 @@ TYPES = { "paired_devices_keypads": RATGDOSensorType.RATGDO_PAIRED_KEYPADS, "paired_devices_wall_controls": RATGDOSensorType.RATGDO_PAIRED_WALL_CONTROLS, "paired_devices_accessories": RATGDOSensorType.RATGDO_PAIRED_ACCESSORIES, + "distance": RATGDOSensorType.RATGDO_DISTANCE } @@ -38,3 +40,15 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_ratgdo_sensor_type(config[CONF_TYPE])) await register_ratgdo_child(var, config) + + if config['type'] == 'distance': + cg.add_library( + name="Wire", + version=None + ) + cg.add_library( + name="vl53l4cx", + repository="https://github.com/stm32duino/VL53L4CX", + version=None, + ) + cg.add_define("USE_DISTANCE") diff --git a/components/ratgdo/sensor/ratgdo_sensor.cpp b/components/ratgdo/sensor/ratgdo_sensor.cpp index e28290a..e90017f 100644 --- a/components/ratgdo/sensor/ratgdo_sensor.cpp +++ b/components/ratgdo/sensor/ratgdo_sensor.cpp @@ -33,6 +33,22 @@ namespace ratgdo { this->parent_->subscribe_paired_accessories([=](uint16_t value) { this->publish_state(value); }); + } else if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { +#ifdef USE_DISTANCE + this->distance_sensor_.setI2cDevice(&I2C); + this->distance_sensor_.setXShutPin(32); + // I2C.begin(17,16); + I2C.begin(19, 18); + this->distance_sensor_.begin(); + this->distance_sensor_.VL53L4CX_Off(); + this->distance_sensor_.InitSensor(0x59); + this->distance_sensor_.VL53L4CX_SetDistanceMode(VL53L4CX_DISTANCEMODE_LONG); + this->distance_sensor_.VL53L4CX_StartMeasurement(); + + this->parent_->subscribe_distance_measurement([=](int16_t value) { + this->publish_state(value); + }); +#endif } } @@ -51,8 +67,46 @@ namespace ratgdo { ESP_LOGCONFIG(TAG, " Type: Paired Wall Controls"); } else if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_PAIRED_ACCESSORIES) { ESP_LOGCONFIG(TAG, " Type: Paired Accessories"); + } else if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { + ESP_LOGCONFIG(TAG, " Type: Distance"); } } + void RATGDOSensor::loop() + { +#ifdef USE_DISTANCE + if (this->ratgdo_sensor_type_ == RATGDOSensorType::RATGDO_DISTANCE) { + VL53L4CX_MultiRangingData_t distanceData; + VL53L4CX_MultiRangingData_t* pDistanceData = &distanceData; + uint8_t dataReady = 0; + int objCount = 0; + int16_t maxDistance = 0; + int status; + + if (this->distance_sensor_.VL53L4CX_GetMeasurementDataReady(&dataReady) == 0 && dataReady) { + status = this->distance_sensor_.VL53L4CX_GetMultiRangingData(pDistanceData); + objCount = pDistanceData->NumberOfObjectsFound; + + maxDistance = objCount == 0 ? -1 : pDistanceData->RangeData[objCount - 1].RangeMilliMeter; + /* if(maxDistance < 0) maxDistance = -1; + * if the sensor is pointed at glass, there are many error readings which will fill the + * vector with out of range data. The sensor should be sensitive enough to detect the floor + * in most situations, unless its mounted really far away. + * If this doesn't work, then the vector size will have to increase substantially + */ + if (maxDistance > 0) { + this->parent_->set_distance_measurement(maxDistance); + } + + // ESP_LOGD(TAG,"# obj found %d; distance %d",objCount, maxDistance); + + if (status == 0) { + status = this->distance_sensor_.VL53L4CX_ClearInterruptAndStartMeasurement(); + } + } + } +#endif + } + } // namespace ratgdo } // namespace esphome diff --git a/components/ratgdo/sensor/ratgdo_sensor.h b/components/ratgdo/sensor/ratgdo_sensor.h index 3a3517c..d7cfdaf 100644 --- a/components/ratgdo/sensor/ratgdo_sensor.h +++ b/components/ratgdo/sensor/ratgdo_sensor.h @@ -5,6 +5,12 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/core/component.h" +#ifdef USE_DISTANCE +#include "Wire.h" +#include "vl53l4cx_class.h" +#define I2C Wire +#endif + namespace esphome { namespace ratgdo { @@ -14,17 +20,23 @@ namespace ratgdo { RATGDO_PAIRED_REMOTES, RATGDO_PAIRED_KEYPADS, RATGDO_PAIRED_WALL_CONTROLS, - RATGDO_PAIRED_ACCESSORIES + RATGDO_PAIRED_ACCESSORIES, + RATGDO_DISTANCE }; class RATGDOSensor : public sensor::Sensor, public RATGDOClient, public Component { public: void dump_config() override; void setup() override; + void loop() override; void set_ratgdo_sensor_type(RATGDOSensorType ratgdo_sensor_type_) { this->ratgdo_sensor_type_ = ratgdo_sensor_type_; } protected: RATGDOSensorType ratgdo_sensor_type_; + +#ifdef USE_DISTANCE + VL53L4CX distance_sensor_; +#endif }; } // namespace ratgdo diff --git a/components/ratgdo/switch/__init__.py b/components/ratgdo/switch/__init__.py index 9028cca..c13265b 100644 --- a/components/ratgdo/switch/__init__.py +++ b/components/ratgdo/switch/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import switch -from esphome.const import CONF_ID +from esphome import pins +from esphome.const import CONF_ID, CONF_PIN from .. import RATGDO_CLIENT_SCHMEA, ratgdo_ns, register_ratgdo_child @@ -13,6 +14,7 @@ SwitchType = ratgdo_ns.enum("SwitchType") CONF_TYPE = "type" TYPES = { "learn": SwitchType.RATGDO_LEARN, + "led": SwitchType.RATGDO_LED } @@ -21,6 +23,7 @@ CONFIG_SCHEMA = ( .extend( { cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), + cv.Optional(CONF_PIN): pins.gpio_output_pin_schema, } ) .extend(RATGDO_CLIENT_SCHMEA) @@ -33,3 +36,6 @@ async def to_code(config): await cg.register_component(var, config) cg.add(var.set_switch_type(config[CONF_TYPE])) await register_ratgdo_child(var, config) + if CONF_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_PIN]) + cg.add(var.set_pin(pin)) diff --git a/components/ratgdo/switch/ratgdo_switch.cpp b/components/ratgdo/switch/ratgdo_switch.cpp index b0f911c..11b85dd 100644 --- a/components/ratgdo/switch/ratgdo_switch.cpp +++ b/components/ratgdo/switch/ratgdo_switch.cpp @@ -21,6 +21,11 @@ namespace ratgdo { this->parent_->subscribe_learn_state([=](LearnState state) { this->publish_state(state == LearnState::ACTIVE); }); + } else if (this->switch_type_ == SwitchType::RATGDO_LED) { + this->pin_->setup(); + this->parent_->subscribe_vehicle_arriving_state([=](VehicleArrivingState state) { + this->write_state(state == VehicleArrivingState::YES); + }); } } @@ -32,6 +37,9 @@ namespace ratgdo { } else { this->parent_->inactivate_learn(); } + } else if (this->switch_type_ == SwitchType::RATGDO_LED) { + this->pin_->digital_write(state); + this->publish_state(state); } } diff --git a/components/ratgdo/switch/ratgdo_switch.h b/components/ratgdo/switch/ratgdo_switch.h index 3640bec..f4631c4 100644 --- a/components/ratgdo/switch/ratgdo_switch.h +++ b/components/ratgdo/switch/ratgdo_switch.h @@ -9,7 +9,8 @@ namespace esphome { namespace ratgdo { enum SwitchType { - RATGDO_LEARN + RATGDO_LEARN, + RATGDO_LED }; class RATGDOSwitch : public switch_::Switch, public RATGDOClient, public Component { @@ -19,9 +20,11 @@ namespace ratgdo { void set_switch_type(SwitchType switch_type_) { this->switch_type_ = switch_type_; } void write_state(bool state) override; + void set_pin(GPIOPin* pin) { pin_ = pin; } protected: SwitchType switch_type_; + GPIOPin* pin_; }; } // namespace ratgdo diff --git a/static/index.html b/static/index.html index 77e016c..d1d96b5 100644 --- a/static/index.html +++ b/static/index.html @@ -177,7 +177,7 @@ />

ESPHome ratgdo

- In order to install the firmware, first pick your door opener control protocol, then pick your ratgdo control board version. + In order to install the firmware, first pick your door opener control protocol, then pick your ratgdo control board version. No programming or other software required.

@@ -209,33 +209,49 @@

Choose your ratgdo control board:

+ + + + +
- + Show Legacy Hardware » - +

@@ -244,7 +260,7 @@

Wiring Diagram

- Security + 1 and 2 wiring diagram + Security + 1 and 2 wiring diagram

Documentation

    @@ -255,12 +271,10 @@

    Drivers

    If you can't connect to your ratgdo board make sure you have the right driver installed for the type of board you have.

    -

    Watch the driver and firmware installation [video on YouTube].

    - - +

    Watch the v2.5i driver and firmware installation [video on YouTube].

    Advanced Users

      @@ -284,17 +298,31 @@