From d7d066571331aefa15de6780bfa489d95e264378 Mon Sep 17 00:00:00 2001 From: Paul Wieland
Date: Wed, 17 Apr 2024 11:20:31 -0400
Subject: [PATCH] Revert "Dry contact support (#255)"
This reverts commit 7362f46aca8c13e91cdb848d5448dd9f9de2e09f.
---
base.yaml | 10 +-
base_drycontact.yaml | 95 ------------------
components/ratgdo/__init__.py | 50 +--------
components/ratgdo/dry_contact.cpp | 71 +------------
components/ratgdo/dry_contact.h | 27 -----
components/ratgdo/protocol.h | 5 -
components/ratgdo/ratgdo.cpp | 29 ------
components/ratgdo/ratgdo.h | 11 --
static/index.html | 9 +-
.../wiring_diagrams/dry_contact_diagram.png | Bin 1179424 -> 512151 bytes
v25iboard_drycontact.yaml | 53 ----------
11 files changed, 19 insertions(+), 341 deletions(-)
delete mode 100644 base_drycontact.yaml
delete mode 100644 v25iboard_drycontact.yaml
diff --git a/base.yaml b/base.yaml
index ddf6272..de1b250 100644
--- a/base.yaml
+++ b/base.yaml
@@ -73,7 +73,7 @@ switch:
id: "${id_prefix}_status_door"
internal: true
pin:
- number: ${status_door_pin} # output door status, HIGH for open, LOW for closed
+ number: ${status_door_pin} # D0 output door status, HIGH for open, LOW for closed
mode:
output: true
name: "Status door"
@@ -82,7 +82,7 @@ switch:
id: "${id_prefix}_status_obstruction"
internal: true
pin:
- number: ${status_obstruction_pin} # output for obstruction status, HIGH for obstructed, LOW for clear
+ number: ${status_obstruction_pin} # D8 output for obstruction status, HIGH for obstructed, LOW for clear
mode:
output: true
name: "Status obstruction"
@@ -128,7 +128,7 @@ binary_sensor:
- platform: gpio
id: "${id_prefix}_dry_contact_open"
pin:
- number: ${dry_contact_open_pin} # dry contact for opening door
+ number: ${dry_contact_open_pin} # D5 dry contact for opening door
inverted: true
mode:
input: true
@@ -146,7 +146,7 @@ binary_sensor:
- platform: gpio
id: "${id_prefix}_dry_contact_close"
pin:
- number: ${dry_contact_close_pin} # dry contact for closing door
+ number: ${dry_contact_close_pin} # D6 dry contact for closing door
inverted: true
mode:
input: true
@@ -164,7 +164,7 @@ binary_sensor:
- platform: gpio
id: "${id_prefix}_dry_contact_light"
pin:
- number: ${dry_contact_light_pin} # dry contact for triggering light (no discrete light commands, so toggle only)
+ number: ${dry_contact_light_pin} # D3 dry contact for triggering light (no discrete light commands, so toggle only)
inverted: true
mode:
input: true
diff --git a/base_drycontact.yaml b/base_drycontact.yaml
deleted file mode 100644
index 60ab11f..0000000
--- a/base_drycontact.yaml
+++ /dev/null
@@ -1,95 +0,0 @@
----
-
-external_components:
- - source:
- # type: local
- # path: components
- type: git
- url: https://github.com/ratgdo/esphome-ratgdo
- refresh: 1s
-
-preferences:
- flash_write_interval: 1min
-
-ratgdo:
- id: ${id_prefix}
- output_gdo_pin: ${uart_tx_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
- discrete_open_pin: ${discrete_open_pin}
- discrete_close_pin: ${discrete_close_pin}
- protocol: drycontact
-
-binary_sensor:
- - platform: ratgdo
- type: obstruction
- id: ${id_prefix}_obstruction
- ratgdo_id: ${id_prefix}
- name: "Obstruction"
- device_class: problem
- - platform: gpio
- id: "${id_prefix}_dry_contact_open"
- pin:
- number: ${dry_contact_open_pin}
- inverted: true
- mode:
- input: true
- pullup: true
- name: "Open limit switch"
- entity_category: diagnostic
- filters:
- - delayed_on_off: 500ms
- - platform: gpio
- id: "${id_prefix}_dry_contact_close"
- pin:
- number: ${dry_contact_close_pin}
- inverted: true
- mode:
- input: true
- pullup: true
- name: "Close limit switch"
- entity_category: diagnostic
- filters:
- - delayed_on_off: 500ms
-
-number:
- - platform: ratgdo
- id: ${id_prefix}_opening_duration
- type: opening_duration
- entity_category: config
- ratgdo_id: ${id_prefix}
- name: "Opening duration"
- unit_of_measurement: "s"
-
- - platform: ratgdo
- id: ${id_prefix}_closing_duration
- type: closing_duration
- entity_category: config
- ratgdo_id: ${id_prefix}
- name: "Closing duration"
- unit_of_measurement: "s"
-
-cover:
- - platform: ratgdo
- id: ${id_prefix}_garage_door
- device_class: garage
- name: "Door"
- ratgdo_id: ${id_prefix}
-
-button:
- - platform: restart
- id: ${id_prefix}_restart
- name: "Restart"
- - platform: safe_mode
- id: ${id_prefix}_safe_mode
- name: "Safe mode boot"
- entity_category: diagnostic
-
- - platform: template
- id: ${id_prefix}_toggle_door
- name: "Toggle door"
- on_press:
- then:
- lambda: !lambda |-
- id($id_prefix).door_toggle();
\ No newline at end of file
diff --git a/components/ratgdo/__init__.py b/components/ratgdo/__init__.py
index 121a004..6b52ddc 100644
--- a/components/ratgdo/__init__.py
+++ b/components/ratgdo/__init__.py
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
import voluptuous as vol
from esphome import automation, pins
from esphome.const import CONF_ID, CONF_TRIGGER_ID
-from esphome.components import binary_sensor
DEPENDENCIES = ["preferences"]
MULTI_CONF = True
@@ -26,9 +25,6 @@ DEFAULT_INPUT_GDO = (
CONF_INPUT_OBST = "input_obst_pin"
DEFAULT_INPUT_OBST = "D7" # D7 black obstruction sensor terminal
-CONF_DISCRETE_OPEN_PIN = "discrete_open_pin"
-CONF_DISCRETE_CLOSE_PIN = "discrete_close_pin"
-
CONF_RATGDO_ID = "ratgdo_id"
CONF_ON_SYNC_FAILED = "on_sync_failed"
@@ -40,22 +36,7 @@ PROTOCOL_SECPLUSV2 = "secplusv2"
PROTOCOL_DRYCONTACT = "drycontact"
SUPPORTED_PROTOCOLS = [PROTOCOL_SECPLUSV1, PROTOCOL_SECPLUSV2, PROTOCOL_DRYCONTACT]
-CONF_DRY_CONTACT_OPEN_SENSOR = "dry_contact_open_sensor"
-CONF_DRY_CONTACT_CLOSE_SENSOR = "dry_contact_close_sensor"
-CONF_DRY_CONTACT_SENSOR_GROUP = "dry_contact_sensor_group"
-
-def validate_protocol(config):
- print("Validation")
- if config.get(CONF_PROTOCOL, None) == PROTOCOL_DRYCONTACT and (CONF_DRY_CONTACT_CLOSE_SENSOR not in config or CONF_DRY_CONTACT_OPEN_SENSOR not in config):
- raise cv.Invalid("dry_contact_close_sensor and dry_contact_open_sensor are required when using protocol drycontact")
- if config.get(CONF_PROTOCOL, None) != PROTOCOL_DRYCONTACT and (CONF_DRY_CONTACT_CLOSE_SENSOR in config or CONF_DRY_CONTACT_OPEN_SENSOR in config):
- raise cv.Invalid("dry_contact_close_sensor and dry_contact_open_sensor are only valid when using protocol drycontact")
-# if config.get(CONF_PROTOCOL, None) == PROTOCOL_DRYCONTACT and CONF_DRY_CONTACT_OPEN_SENSOR not in config:
-# raise cv.Invalid("dry_contact_open_sensor is required when using protocol drycontact")
- return config
-
-CONFIG_SCHEMA = cv.All(
- cv.Schema(
+CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(RATGDO),
cv.Optional(
@@ -67,24 +48,16 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INPUT_OBST, default=DEFAULT_INPUT_OBST): cv.Any(
cv.none, pins.gpio_input_pin_schema
),
- cv.Optional(CONF_DISCRETE_OPEN_PIN): pins.gpio_output_pin_schema,
- cv.Optional(CONF_DISCRETE_CLOSE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_ON_SYNC_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SyncFailed),
}
),
- cv.Optional(CONF_PROTOCOL, default=PROTOCOL_SECPLUSV2): cv.All(vol.In(
+ cv.Optional(CONF_PROTOCOL, default=PROTOCOL_SECPLUSV2): vol.In(
SUPPORTED_PROTOCOLS
- )),
- # cv.Inclusive(CONF_DRY_CONTACT_OPEN_SENSOR,CONF_DRY_CONTACT_SENSOR_GROUP): cv.use_id(binary_sensor.BinarySensor),
- # cv.Inclusive(CONF_DRY_CONTACT_CLOSE_SENSOR,CONF_DRY_CONTACT_SENSOR_GROUP): cv.use_id(binary_sensor.BinarySensor),
- cv.Optional(CONF_DRY_CONTACT_OPEN_SENSOR): cv.use_id(binary_sensor.BinarySensor),
- cv.Optional(CONF_DRY_CONTACT_CLOSE_SENSOR): cv.use_id(binary_sensor.BinarySensor),
+ ),
}
- ).extend(cv.COMPONENT_SCHEMA),
- validate_protocol,
-)
+).extend(cv.COMPONENT_SCHEMA)
RATGDO_CLIENT_SCHMEA = cv.Schema(
{
@@ -109,14 +82,6 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_INPUT_OBST])
cg.add(var.set_input_obst_pin(pin))
- if CONF_DRY_CONTACT_OPEN_SENSOR in config and config[CONF_DRY_CONTACT_OPEN_SENSOR]:
- dry_contact_open_sensor = await cg.get_variable(config[CONF_DRY_CONTACT_OPEN_SENSOR])
- cg.add(var.set_dry_contact_open_sensor(dry_contact_open_sensor))
-
- if CONF_DRY_CONTACT_CLOSE_SENSOR in config and config[CONF_DRY_CONTACT_CLOSE_SENSOR]:
- dry_contact_close_sensor = await cg.get_variable(config[CONF_DRY_CONTACT_CLOSE_SENSOR])
- cg.add(var.set_dry_contact_close_sensor(dry_contact_close_sensor))
-
for conf in config.get(CONF_ON_SYNC_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
@@ -139,10 +104,3 @@ async def to_code(config):
elif config[CONF_PROTOCOL] == PROTOCOL_DRYCONTACT:
cg.add_define("PROTOCOL_DRYCONTACT")
cg.add(var.init_protocol())
-
- if CONF_DISCRETE_OPEN_PIN in config and config[CONF_DISCRETE_OPEN_PIN]:
- pin = await cg.gpio_pin_expression(config[CONF_DISCRETE_OPEN_PIN])
- cg.add(var.set_discrete_open_pin(pin))
- if CONF_DISCRETE_CLOSE_PIN in config and config[CONF_DISCRETE_CLOSE_PIN]:
- pin = await cg.gpio_pin_expression(config[CONF_DISCRETE_CLOSE_PIN])
- cg.add(var.set_discrete_close_pin(pin))
\ No newline at end of file
diff --git a/components/ratgdo/dry_contact.cpp b/components/ratgdo/dry_contact.cpp
index 9860e16..5dc5a9d 100644
--- a/components/ratgdo/dry_contact.cpp
+++ b/components/ratgdo/dry_contact.cpp
@@ -5,7 +5,6 @@
#include "esphome/core/gpio.h"
#include "esphome/core/log.h"
#include "esphome/core/scheduler.h"
-#include "esphome/components/gpio/binary_sensor/gpio_binary_sensor.h"
namespace esphome {
namespace ratgdo {
@@ -19,12 +18,6 @@ namespace ratgdo {
this->scheduler_ = scheduler;
this->tx_pin_ = tx_pin;
this->rx_pin_ = rx_pin;
-
- this->open_limit_reached_ = 0;
- this->last_open_limit_ = 0;
- this->close_limit_reached_ = 0;
- this->last_close_limit_ = 0;
- this->door_state_ = DoorState::UNKNOWN;
}
void DryContact::loop()
@@ -38,43 +31,6 @@ namespace ratgdo {
void DryContact::sync()
{
- ESP_LOG1(TAG, "Ignoring sync action");
- }
-
- void DryContact::set_open_limit(bool state)
- {
- ESP_LOGD(TAG, "Set open_limit_reached to %d", state);
- this->last_open_limit_ = this->open_limit_reached_;
- this->last_close_limit_ = false;
- this->open_limit_reached_ = state;
- this->send_door_state();
- }
-
- void DryContact::set_close_limit(bool state)
- {
- ESP_LOGD(TAG, "Set close_limit_reached to %d", state);
- this->last_close_limit_ = this->close_limit_reached_;
- this->last_open_limit_ = false;
- this->close_limit_reached_ = state;
- this->send_door_state();
- }
-
- void DryContact::send_door_state(){
- if(this->open_limit_reached_){
- this->door_state_ = DoorState::OPEN;
- }else if(this->close_limit_reached_){
- this->door_state_ = DoorState::CLOSED;
- }else if(!this->close_limit_reached_ && !this->open_limit_reached_){
- if(this->last_close_limit_){
- this->door_state_ = DoorState::OPENING;
- }
-
- if(this->last_open_limit_){
- this->door_state_ = DoorState::CLOSING;
- }
- }
-
- this->ratgdo_->received(this->door_state_);
}
void DryContact::light_action(LightAction action)
@@ -91,33 +47,14 @@ namespace ratgdo {
void DryContact::door_action(DoorAction action)
{
- if (action == DoorAction::OPEN && this->door_state_ != DoorState::CLOSED) {
- ESP_LOGW(TAG, "The door is not closed. Ignoring door action: %s", DoorAction_to_string(action));
+ if (action != DoorAction::TOGGLE) {
+ ESP_LOG1(TAG, "Ignoring door action: %s", DoorAction_to_string(action));
return;
}
- if (action == DoorAction::CLOSE && this->door_state_ != DoorState::OPEN) {
- ESP_LOGW(TAG, "The door is not open. Ignoring door action: %s", DoorAction_to_string(action));
- return;
- }
-
ESP_LOG1(TAG, "Door action: %s", DoorAction_to_string(action));
- if (action == DoorAction::OPEN){
- this->discrete_open_pin_->digital_write(1);
- this->scheduler_->set_timeout(this->ratgdo_, "", 500, [=] {
- this->discrete_open_pin_->digital_write(0);
- });
- }
-
- if (action == DoorAction::CLOSE){
- this->discrete_close_pin_->digital_write(1);
- this->scheduler_->set_timeout(this->ratgdo_, "", 500, [=] {
- this->discrete_close_pin_->digital_write(0);
- });
- }
-
- this->tx_pin_->digital_write(1); // Single button control
- this->scheduler_->set_timeout(this->ratgdo_, "", 500, [=] {
+ this->tx_pin_->digital_write(1);
+ this->scheduler_->set_timeout(this->ratgdo_, "", 200, [=] {
this->tx_pin_->digital_write(0);
});
}
diff --git a/components/ratgdo/dry_contact.h b/components/ratgdo/dry_contact.h
index 09362b6..99dd024 100644
--- a/components/ratgdo/dry_contact.h
+++ b/components/ratgdo/dry_contact.h
@@ -2,8 +2,6 @@
#include "SoftwareSerial.h" // Using espsoftwareserial https://github.com/plerup/espsoftwareserial
#include "esphome/core/optional.h"
-#include "esphome/core/gpio.h"
-#include "esphome/components/gpio/binary_sensor/gpio_binary_sensor.h"
#include "callbacks.h"
#include "observable.h"
@@ -19,7 +17,6 @@ namespace ratgdo {
namespace dry_contact {
using namespace esphome::ratgdo::protocol;
- using namespace esphome::gpio;
class DryContact : public Protocol {
public:
@@ -32,21 +29,6 @@ namespace ratgdo {
void light_action(LightAction action);
void lock_action(LockAction action);
void door_action(DoorAction action);
- void set_open_limit(bool state);
- void set_close_limit(bool state);
- void send_door_state();
-
- void set_discrete_open_pin(InternalGPIOPin* pin) {
- this->discrete_open_pin_ = pin;
- this->discrete_open_pin_->setup();
- this->discrete_open_pin_->pin_mode(gpio::FLAG_OUTPUT);
- }
-
- void set_discrete_close_pin(InternalGPIOPin* pin) {
- this->discrete_close_pin_ = pin;
- this->discrete_close_pin_->setup();
- this->discrete_close_pin_->pin_mode(gpio::FLAG_OUTPUT);
- }
Result call(Args args);
@@ -57,18 +39,9 @@ namespace ratgdo {
InternalGPIOPin* tx_pin_;
InternalGPIOPin* rx_pin_;
- InternalGPIOPin* discrete_open_pin_;
- InternalGPIOPin* discrete_close_pin_;
RATGDOComponent* ratgdo_;
Scheduler* scheduler_;
-
- DoorState door_state_;
- bool open_limit_reached_;
- bool last_open_limit_;
- bool close_limit_reached_;
- bool last_close_limit_;
-
};
} // namespace secplus1
diff --git a/components/ratgdo/protocol.h b/components/ratgdo/protocol.h
index 325c6ea..8202079 100644
--- a/components/ratgdo/protocol.h
+++ b/components/ratgdo/protocol.h
@@ -102,11 +102,6 @@ namespace ratgdo {
virtual void dump_config();
virtual void sync();
- virtual void set_open_limit(bool);
- virtual void set_close_limit(bool);
-
- virtual void set_discrete_open_pin(InternalGPIOPin* pin);
- virtual void set_discrete_close_pin(InternalGPIOPin* pin);
virtual const Traits& traits() const;
diff --git a/components/ratgdo/ratgdo.cpp b/components/ratgdo/ratgdo.cpp
index eb26bf1..f65ae25 100644
--- a/components/ratgdo/ratgdo.cpp
+++ b/components/ratgdo/ratgdo.cpp
@@ -420,16 +420,6 @@ namespace ratgdo {
this->protocol_->sync();
}
- void RATGDOComponent::set_open_limit(bool state)
- {
- this->protocol_->set_open_limit(state);
- }
-
- void RATGDOComponent::set_close_limit(bool state)
- {
- this->protocol_->set_close_limit(state);
- }
-
void RATGDOComponent::door_open()
{
if (*this->door_state == DoorState::OPENING) {
@@ -683,24 +673,5 @@ namespace ratgdo {
this->learn_state.subscribe([=](LearnState state) { defer("learn_state", [=] { f(state); }); });
}
- void RATGDOComponent::set_dry_contact_open_sensor(esphome::gpio::GPIOBinarySensor* dry_contact_open_sensor)
- {
- dry_contact_open_sensor_ = dry_contact_open_sensor;
- dry_contact_open_sensor_->add_on_state_callback([this](bool sensor_value)
- {
- this->set_open_limit(sensor_value);
- }
- );
- }
- void RATGDOComponent::set_dry_contact_close_sensor(esphome::gpio::GPIOBinarySensor* dry_contact_close_sensor)
- {
- dry_contact_close_sensor_ = dry_contact_close_sensor;
- dry_contact_close_sensor_->add_on_state_callback([this](bool sensor_value)
- {
- this->set_close_limit(sensor_value);
- }
- );
- }
-
} // namespace ratgdo
} // namespace esphome
diff --git a/components/ratgdo/ratgdo.h b/components/ratgdo/ratgdo.h
index 2c9ac61..510f636 100644
--- a/components/ratgdo/ratgdo.h
+++ b/components/ratgdo/ratgdo.h
@@ -16,7 +16,6 @@
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/preferences.h"
-#include "esphome/components/gpio/binary_sensor/gpio_binary_sensor.h"
#include "callbacks.h"
#include "macros.h"
@@ -92,12 +91,6 @@ namespace ratgdo {
void set_input_gdo_pin(InternalGPIOPin* pin) { this->input_gdo_pin_ = pin; }
void set_input_obst_pin(InternalGPIOPin* pin) { this->input_obst_pin_ = pin; }
- void set_dry_contact_open_sensor(esphome::gpio::GPIOBinarySensor* dry_contact_open_sensor_);
- void set_dry_contact_close_sensor(esphome::gpio::GPIOBinarySensor* dry_contact_close_sensor_);
-
- void set_discrete_open_pin(InternalGPIOPin* pin){ this->protocol_->set_discrete_open_pin(pin); }
- void set_discrete_close_pin(InternalGPIOPin* pin){ this->protocol_->set_discrete_close_pin(pin); }
-
Result call_protocol(Args args);
void received(const DoorState door_state);
@@ -152,8 +145,6 @@ namespace ratgdo {
void query_status();
void query_openings();
void sync();
- void set_open_limit(bool);
- void set_close_limit(bool);
// children subscriptions
void subscribe_rolling_code_counter(std::function 78|#
zmDs1U4cOE)r9s=NaG}|f1WGB|>TDyylArVn2w;?M
z8$azpnT7`~OCCo;=1(|`zzMr85c9%Hx%Y}1$U;itlkTsFzRmq}PLcTljRWKi0x?{v
z3Z|buXIE_y$?LRhwNmDy!ojQgKA30pSyjHTgqLXtuM2xSM&pKWni}#OW^NhmPWCf3
z7;sR-#>S22bUutIcyDWT*C$T=njBmbXF+h=Hw!1b`4HC5{jte|P)P5dSYB)aW+;C2
z98yfGi+|06=u|J&Q0Gygy)$4bnx;>Jd!Pyv%GhRBeWdX3+to%m(
zgl%M=(40$nb?HyyL2R*TT3Nyevl)e0O*|fYFxgPm5{ZPjg5q>yiZFPA^ida63%&vv
zO`yOG$6mY)vdoJx05$j#+l|>w5t!jqjDxon9}yCv#_T0Cfx(Cpn3qnCBi)dKAp2%8
z*-!-)k)Ki;GthxDg98%+ssyY^FoWj}9ytWZ!T*FYh)R_QgNx5%oRw)Hl>0Hmi;C_&
zftg^yMYKBQc7qt9Oe-{IXg+}zF*>9<(==unV$iQBW;z*yXbbESpNY?I$U+=Zpa92t
zqL-O27d9G%2^;N5roYUEnL%Wp$*he35Ff`;nRhCg+u~%N0_B*@32TmuB|+wBW_-SW
zGA9reW3DE1O#`HL08Qrc%xE$n&;e4xH}M?+Unn+UriUOI=8TR3GLP#39{f(C9iUg{
z(2wr`=$4x6>o#rKy<)|3f@?Zbo#
oplZ@q7>M~rr0`~
z$QIGYRI|Hrj;ZDAv#i-!ExPV53Zm+imZ)~h3Hwi@0|S|LhlGyRuCthwhELEP?2rW*
zX0)ajZf)MP&t05$(r&$l7We2`7}*_gq=BHTk*ez1x&tn<1llWWl=aiB0
zCD{?5E&jZJ-Z%>J+2YUp6^XwUf8H-!{AGN$`1ANYoX6i9?0lF-`eloMv5$~)oWKTD
z5;vsA*U~Rv{H?*xhgqaww)o3oQjYIJ!z`kGj~=YqzjDc|`T2!JS;QzXs=O>aCyObE
zb^H6P>ex0XFE^V|N4OIa9TXIZKP-W-h?@ZT0Vv}k?)7-x9#o9%1C63fwikZv>3_C1
zyKor(%j++WIeJ!YtE(_Ko!~jQefO@HA9^^ysHCl }{c?U^XRod755%o#ty|6
zOrz-1hh6M9AowgkJI{qn^^jtPlAnOCc&xNDynCK4c2J9Fc~$I;XWI+hG`4K630)qrkN6kbuIq
zzO@}qePd%YJ4NzvM843#!H$6_Ev@;+FBj$IA9vEpA7c)b=O>5YD*2;5Q#tK-XxaKD
z(!^e8c3w~JNkch=sB~Q|w^DOP&$$!5p;fJ2an2FFBLou873nDDcmfI866%lUQiKz-
zC8bO@Rf+U=&PBeT#*tjApGM7xUB~|rs|js8B7imUjiPVRfiO8y2`8$f49u0l!SF7I
zBr;@%;nb6a2%pgeT>1Kf1=!9B