diff --git a/.gitignore b/.gitignore
index 4e4bfb3e21aab785c81430ccf1a28fec2392890b..a3f580867229530c26bdce4a9ac672b9ab249a9b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
 /.esphome/
 **/secrets.yaml
 /testbox.yaml
+/*.code-workspace
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1053d71b5d9e238db648a337326b7e3baa7c275d..e1f69e964d0d9a585a75379d856f1ace8150d1c5 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,25 +1,40 @@
 stages:
   - validate
+  - build
   - deploy
 
 image:
-  name: esphome/esphome:2022.11.3
+  name: esphome/esphome:2023.9.3
   entrypoint: [""]
 
 validate_configs:
   stage: validate
-  before_script:
-    - cat "${SECRETS_YAML}" > "packages/secrets.yaml"
   script:
+    - cat ${SECRETS_YAML} > packages/secrets.yaml
     - for yml in *.yaml; do esphome config ${yml} >/dev/null; done
+  artifacts:
+    expire_in: 1 hour
+    paths:
+      - packages/secrets.yaml
+  tags:
+    - docker
+
+build_configs:
+  stage: build
+  before_script:
+    - mkdir build
+  script:
+    - for yml in *.yaml; do esphome config --show-secrets ${yml} > build/${yml}; done
+  artifacts:
+    expire_in: 1 hour
+    paths:
+      - build/*.yaml
   tags:
     - docker
 
 deploy_configs:
   stage: deploy
-  before_script:
-    - cat "${SECRETS_YAML}" > "packages/secrets.yaml"
   script:
-    - cp -r *.yaml packages include /srv/esphome/groovy-industries
+    - cp -rv build/*.yaml /srv/esphome/groovy-industries/
   tags:
     - shell
diff --git a/README.md b/README.md
index 7f32052ab01d759c4a803be398770728f9fa0bb7..e326750db41533bfc8ee81221035d95109ee16cd 100644
--- a/README.md
+++ b/README.md
@@ -4,17 +4,25 @@ Home Grow(n) Monitoring
 
 ## Components
 
-* propbox: ESP32 Propagator Controller
-    * BME280 Temperature and Humidity Sensor (3.3V, I2C)
-    * Dallas DS18B20 Temperature Sensor
-    * YYAC-3S Triac for Fan Control (5V, PWM)
-* propbox-(light|fan): Nous A1T Smart Plug for Light & Fan Power Usage Monitoring
-* growbox: ESP32 Grow Tent Monitor
-    * Yieryi 3178 EC/pH Monitor (RS485 via XY-485 board)
+* growbox: ESP32-S3 Grow Tent Monitor
+    * 230V to 5V/2A Power Supply
+    * Adafruit LTC4311 I2C Extender (3,3V)
     * BME280 Temperature and Humidity Sensor (3.3V, I2C)
     * SCD40 CO2 Sensor (5V, I2C)
     * MLX90614-DCI IR Temperature Sensor (3.3V, I2C)
     * GP8403 0-10V DAC for Fan & LED Control (5V, I2C)
+    * DFR0553 ADS1115 ADC for EC & pH Sensors (5V, I2C)
+    * DFR0300 Analog Electrical Conductivity Sensor V2 (K=1)
+    * SEN0161-V2 Analog pH meter V2
+    * DFR0504 Analog Signal Isolator (5V) [min.1 for EC, better 2 for EC and pH]
+    * STS35 Temperature Sensor (Waterproof Probe version, 3,3V, I2C) [seems to be not available anymore, try SHT35]
+    
+* propbox: ESP32 Propagator Controller
+    * BME280 Temperature and Humidity Sensor (3.3V, I2C)
+    * Dallas DS18B20 Temperature Sensor
+    * YYAC-3S Triac for Fan Control (5V, PWM)
+    
+* (growbox|propbox)-(light|fan): Nous A1T Smart Plug for Light & Fan Power Usage Monitoring
 
 ## Configuration
 Copy `packages/secrets.yaml.dist` to `packages/secrets.yaml` and adjust to your preferences.
diff --git a/growbox.yaml b/growbox.yaml
index fda0e21b156e9db932f3273c2166cc94b248630d..be6d0211961e5646bb4e4617209a1bcd1626720e 100644
--- a/growbox.yaml
+++ b/growbox.yaml
@@ -11,7 +11,7 @@ substitutions:
 
 packages:
   base:           !include packages/base.yaml
-  esp32:          !include packages/esp32.yaml
+  esp32:          !include packages/esp32s3.yaml
   time:           !include packages/time_schedule.yaml
   bme280:         !include packages/bme280.yaml
   scd40:          !include packages/scd40.yaml
diff --git a/include/gp8403.h b/include/gp8403.h
deleted file mode 100644
index 32a2e26f9b24294d779fb86cadd6c7e0438df078..0000000000000000000000000000000000000000
--- a/include/gp8403.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#include "esphome.h"
-#include "DFRobot_GP8403.h"
-
-class GP8403 : public Component, public FloatOutput {
-  public:
-
-    DFRobot_GP8403 dac;
-    int channel;
-    float factor;
-
-    GP8403(int c, float f = 1.0) {
-      channel = c;
-      factor = f;
-    }
-
-    float get_setup_priority() const override { return esphome::setup_priority::BUS; }
-
-    void setup() override {
-      while(dac.begin()!=0){
-        ESP_LOGD("gp8403", "Initializing GP8403...");
-        delay(1000);
-      }
-      ESP_LOGD("gp8403", "GP8403 Initialized!");
-      dac.setDACOutRange(dac.eOutputRange10V);
-    }
-
-    void write_state(float state) override {
-      ESP_LOGV("gp8403", "Received state %f", state);
-      // state is the percentage this output should be on, from 0.0 to 1.0
-      // we need to convert it to millivolts (0-10000 for 0-10V)
-      int millivolts = (float)state * 10000;
-      dac.setDACOutVoltage(millivolts * factor, channel);
-      dac.store();
-      ESP_LOGD("gp8403", "Channel %d set to %.2fV (%dmV, %g%%)", channel, millivolts/1000.0, millivolts, state*100.0);
-    }
-};
diff --git a/include/mlx90614.h b/include/mlx90614.h
deleted file mode 100644
index a1467dc11f5912c2da1342f3ebfb521122bebd46..0000000000000000000000000000000000000000
--- a/include/mlx90614.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#include "esphome.h"
-#include "Adafruit_MLX90614.h"
-
-class MLX90614 : public PollingComponent {
-  public:
-
-    Adafruit_MLX90614 mlx90614 = Adafruit_MLX90614();
-    Sensor *ambient_temperature = new Sensor();
-    Sensor *object_temperature = new Sensor();
-
-    MLX90614( uint32_t update_interval ) : PollingComponent(update_interval) {} 
-
-    void setup() override {
-      mlx90614.begin();
-    }
-
-    void update() override {
-      float amb_temperature = 0.0;
-      amb_temperature = mlx90614.readAmbientTempC(); // reading twice seemed to eliminate intermittent invalid data reads (shows as 1037.5 C)
-      amb_temperature = mlx90614.readAmbientTempC(); 
-      ambient_temperature->publish_state(amb_temperature);
-
-      float obj_temperature = 0.0;
-      obj_temperature = mlx90614.readObjectTempC(); // only had invalid reads for ambient teperature but taking two readings of object temperature as well fwiw
-      obj_temperature = mlx90614.readObjectTempC();
-      object_temperature->publish_state(obj_temperature);
-    }
-};
diff --git a/include/yieryi_3178.h b/include/yieryi_3178.h
deleted file mode 100644
index edf7d19065684f00fb19f2e1c8df1393358865e6..0000000000000000000000000000000000000000
--- a/include/yieryi_3178.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#include "esphome.h"
-#include "esphome/core/helpers.h"
-
-class Yieryi3178 : public UARTDevice, public PollingComponent {
-  public:
-
-    Sensor *ec   = new Sensor();
-    Sensor *ph   = new Sensor();
-    Sensor *rh   = new Sensor();
-    Sensor *temp = new Sensor();
-    Sensor *bat  = new Sensor();
-
-    Yieryi3178(UARTComponent *parent, uint32_t update_interval) : UARTDevice(parent), PollingComponent(update_interval) {}
-
-    std::vector<uint8_t> bytes;
-    byte request[8] = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x04, 0x44, 0x09 };
-
-    typedef union {
-      unsigned char Byte[2];
-      uint16_t UInt16;
-    } TwoByte;
-
-    void update() override {
-      write_array(request, sizeof(request));
-      flush();
-      delay(10);
-      
-      while(available() > 0) {
-        bytes.push_back(read());
-
-        if(bytes.size() < 16)
-          continue;  
-
-        if(bytes.size() == 16) {
-          ESP_LOGVV("yieryi3178", "Bytes received: %s", format_hex_pretty(bytes).c_str());
-
-          TwoByte data_ec;
-          data_ec.Byte[0] = bytes[5];
-          data_ec.Byte[1] = bytes[4];  
-          ec->publish_state(data_ec.UInt16 * 0.001);
-          ESP_LOGV("yieryi3178", "EC: %.3f", data_ec.UInt16 * 0.001);
-
-          TwoByte data_ph;
-          data_ph.Byte[0] = bytes[7];
-          data_ph.Byte[1] = bytes[6];  
-          ph->publish_state(data_ph.UInt16 * 0.01);
-          ESP_LOGV("yieryi3178", "pH: %.2f", data_ph.UInt16 * 0.01);
-
-          TwoByte data_rh;
-          data_rh.Byte[0] = bytes[9];
-          data_rh.Byte[1] = bytes[8];  
-          rh->publish_state(data_rh.UInt16);
-          ESP_LOGV("yieryi3178", "RH: %i", data_rh.UInt16);
-
-          TwoByte data_temp;
-          data_temp.Byte[0] = bytes[11];
-          data_temp.Byte[1] = bytes[10];  
-          temp->publish_state(data_temp.UInt16 * 0.1);
-          ESP_LOGV("yieryi3178", "Temp: %.1f", data_temp.UInt16 * 0.1);
-
-          TwoByte data_bat;
-          data_bat.Byte[0] = bytes[13];
-          data_bat.Byte[1] = bytes[12];  
-          bat->publish_state(data_bat.UInt16 / 100 / 1.5);
-          ESP_LOGV("yieryi3178", "Bat: %.1f", data_bat.UInt16 / 100 / 1.5);
-
-          bytes.clear();
-
-          while(available() > 0)
-            read();
-        }
-      }
-    }
-};
\ No newline at end of file
diff --git a/packages/base.yaml b/packages/base.yaml
index 71172bcd393d8b7851411f41da88e18647b6b0e0..24a11024e5e7502a6f7263ffe6016fad2d39c4d4 100644
--- a/packages/base.yaml
+++ b/packages/base.yaml
@@ -3,7 +3,7 @@ esphome:
   comment: $comment
   project:
     name: "groovy.industries"
-    version: "1.0"
+    version: "2.0"
   on_boot:
     priority: -100
     then:
@@ -16,7 +16,7 @@ substitutions:
   fan_script: "dummy"
 
 preferences:
-  flash_write_interval: 5s
+  flash_write_interval: 15s
 
 logger:
   # level: INFO
@@ -31,8 +31,9 @@ ota:
 wifi:
   ssid: !secret wifi_ssid
   password: !secret wifi_password
-  #fast_connect: on
+  fast_connect: on
   reboot_timeout: 1min
+  domain: .${domain}
   use_address: ${devicename}.${domain}
 
 web_server:
diff --git a/packages/ec.yaml b/packages/ec.yaml
index 6490c10cd615ccd35f733e1ed87fa5b94968696d..c5b740e69ec4c620d04ccc253de787f2b970faaf 100644
--- a/packages/ec.yaml
+++ b/packages/ec.yaml
@@ -26,7 +26,7 @@ sensor:
   #   update_interval: 5s
   #   lambda: !lambda return id(ec_k);
   - platform: template
-    name: "EC:"
+    name: "EC"
     id: ec
     unit_of_measurement: "mS"
     accuracy_decimals: 3
diff --git a/packages/esp32.yaml b/packages/esp32.yaml
index c6c105d392fe787f275f4b0c9be17f102284cd4b..c0f6e298c818d03b5fc3806b31c21e8002e31b61 100644
--- a/packages/esp32.yaml
+++ b/packages/esp32.yaml
@@ -1,5 +1,9 @@
 esp32:
   board: esp32dev
+  framework:
+    type: arduino
+    version: latest
+    platform_version: 6.4.0
 
 i2c:
   sda: 21
diff --git a/packages/esp32s3.yaml b/packages/esp32s3.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4923f99b0759136af5a003c36da77c11915b8b8b
--- /dev/null
+++ b/packages/esp32s3.yaml
@@ -0,0 +1,28 @@
+esp32:
+  board: seeed_xiao_esp32s3
+  variant: esp32s3
+  framework:
+    type: arduino
+    version: latest
+    platform_version: 6.4.0
+
+esphome:
+  platformio_options:
+    board_build.flash_mode: dio
+
+psram:
+  mode: octal
+  speed: 80MHz
+
+status_led:
+  pin:
+    number: GPIO21
+    inverted: True
+
+i2c:
+  sda: 5
+  scl: 6
+  frequency: 10khz
+
+wifi:
+  power_save_mode: none
diff --git a/packages/fan_power.yaml b/packages/fan_power.yaml
index 83d0d84ca688b7a50009dbca9596bf963a910563..24d00f0e856f1d5c21ce16ec7d8999cabcf38bb6 100644
--- a/packages/fan_power.yaml
+++ b/packages/fan_power.yaml
@@ -2,6 +2,8 @@ esphome:
   on_boot:
     priority: -200
     then:
+      - wait_until:
+          wifi.connected:
       - lambda: |-
           id(fan_power_state).execute();
 
@@ -15,25 +17,17 @@ switch:
     turn_off_action:
       - http_request.post: http://${hostname_fan}/switch/power/turn_off
 
-interval:
-  - interval: 1min
-    then:
-      - script.execute: fan_power_state
-
 script:
   - id: fan_power_state
-    then:
-      - lambda: |-
-          HTTPClient http;
-          bool state = false;
-          http.begin("http://${hostname_fan}/switch/power");
-          if (http.GET() == 200) {
-              DynamicJsonDocument doc(200);
-              deserializeJson(doc, http.getString());
-              state = (doc["state"] == "ON") ? true : false;
-          }
-          id(power_fan).publish_state(state);
-          http.end();
+    then: 
+      - http_request.get:
+          url: "http://${hostname_fan}/switch/power"
+          on_response:
+            then:
+              - lambda: |-
+                  json::parse_json(id(http_request_data).get_string(), [](JsonObject root) {
+                    id(power_fan).publish_state(root["value"]);
+                  });
 
 prometheus:
   relabel:
diff --git a/packages/gp8403.yaml b/packages/gp8403.yaml
index ba46c84514ba9542a3ba3ae98c44aee190fc6c6b..d6a5e0ca590716af88bf6f8c7bb23675404d058c 100644
--- a/packages/gp8403.yaml
+++ b/packages/gp8403.yaml
@@ -1,39 +1,33 @@
-esphome:
-  includes:
-    - include/gp8403.h
-  libraries:
-    - Wire
-    - dfrobot/DFRobot_GP8403@^1.0.0
+gp8403:
+  id: dac_10v
+  voltage: 10V
 
 output:
-- platform: custom
-  type: float
-  lambda: |-
-    auto gp8403_ch0 = new GP8403(0, 0.919);
-    auto gp8403_ch1 = new GP8403(1, 0.919);
-    App.register_component(gp8403_ch0);
-    App.register_component(gp8403_ch1);
-    return {gp8403_ch0, gp8403_ch1};
-  outputs:
-    - id: gp8403_ch0
-      min_power: 0.10
-      max_power: 1.0
-      zero_means_zero: true
-    - id: gp8403_ch1
-      min_power: 0.10
-      max_power: 1.0
-      zero_means_zero: true
+  - platform: gp8403
+    id: gp8403_ch0
+    gp8403_id: dac_10v
+    channel: 0
+    min_power: 0.10
+    max_power: 1.0
+    zero_means_zero: true
+  - platform: gp8403
+    id: gp8403_ch1
+    gp8403_id: dac_10v
+    channel: 1
+    min_power: 0.10
+    max_power: 1.0
+    zero_means_zero: true
 
 fan:
   - platform: speed
     output: gp8403_ch0
     id: fan_speed
-    name: "Fan: Speed"
+    name: "Fan"
 
 light:
   - platform: monochromatic
     id: light_level
-    name: "Light: Level"
+    name: "Light"
     output: gp8403_ch1
     default_transition_length: 0s
     gamma_correct: 0
diff --git a/packages/light_power.yaml b/packages/light_power.yaml
index 93066fa3bac0c673d809abf167fcc22d65725473..62db1d456a1e213ab5b2389ebe9c8d270c04826b 100644
--- a/packages/light_power.yaml
+++ b/packages/light_power.yaml
@@ -2,6 +2,8 @@ esphome:
   on_boot:
     priority: -200
     then:
+      - wait_until:
+          wifi.connected:
       - lambda: |-
           id(light_power_state).execute();
 
@@ -15,25 +17,17 @@ switch:
     turn_off_action:
       - http_request.post: http://${hostname_light}/switch/power/turn_off
 
-interval:
-  - interval: 1min
-    then:
-      - script.execute: light_power_state
-
 script:
   - id: light_power_state
     then:
-      - lambda: |-
-          HTTPClient http;
-          bool state = false;
-          http.begin("http://${hostname_light}/switch/power");
-          if (http.GET() == 200) {
-              DynamicJsonDocument doc(200);
-              deserializeJson(doc, http.getString());
-              state = (doc["state"] == "ON") ? true : false;
-          }
-          id(power_light).publish_state(state);
-          http.end();
+      - http_request.get:
+          url: "http://${hostname_light}/switch/power"
+          on_response:
+            then:
+              - lambda: |-
+                  json::parse_json(id(http_request_data).get_string(), [](JsonObject root) {
+                    id(power_light).publish_state(root["value"]);
+                  });
 
 prometheus:
   relabel:
diff --git a/packages/mlx90614.yaml b/packages/mlx90614.yaml
index 8feccb9454850f963fc9d903a65045aa46d5dadb..dd008f6d06601452466374172d445ab15480c367 100644
--- a/packages/mlx90614.yaml
+++ b/packages/mlx90614.yaml
@@ -1,29 +1,18 @@
-esphome:
-  includes:
-    - include/mlx90614.h
-  libraries:
-    - Wire
-    - SPI
-    - https://github.com/adafruit/Adafruit_BusIO @ 1.14.1
-    - https://github.com/adafruit/Adafruit-MLX90614-Library @ 2.1.3
-
 sensor:
-- platform: custom 
-  lambda: |-
-    auto mlx90614 = new MLX90614(5000);   
-    App.register_component(mlx90614);
-    return {mlx90614->ambient_temperature, mlx90614->object_temperature};
-  sensors:
-  - id: mlx90614_temperature_ambient
-    name: "IR: Ambient Temperature"
-    unit_of_measurement: °C
-    accuracy_decimals: 2
-    <<: !include filter.yaml
-  - id: mlx90614_temperature_object
-    name: "IR: Object Temperature"
-    unit_of_measurement: °C
-    accuracy_decimals: 2
-    <<: !include filter.yaml
+  - platform: mlx90614
+    update_interval: 5s
+    ambient:
+      id: mlx90614_temperature_ambient
+      name: "IR: Ambient Temperature"
+      unit_of_measurement: °C
+      accuracy_decimals: 2
+      <<: !include filter.yaml
+    object:
+      id: mlx90614_temperature_object
+      name: "IR: Object Temperature"
+      unit_of_measurement: °C
+      accuracy_decimals: 2
+      <<: !include filter.yaml
 
 prometheus:
   relabel:
diff --git a/packages/ph.yaml b/packages/ph.yaml
index ffd1f3cc1fcb55fa849959d3b76d26a74c2f2200..84a982dbc2c6dbc95c7ca299ca27a9a0f0626819 100644
--- a/packages/ph.yaml
+++ b/packages/ph.yaml
@@ -56,7 +56,7 @@ sensor:
   #   update_interval: 5s
   #   lambda: !lambda return id(ph918_raw);
   - platform: template
-    name: "pH:"
+    name: "pH"
     id: ph
     unit_of_measurement: "pH"
     accuracy_decimals: 2
diff --git a/packages/time_schedule.yaml b/packages/time_schedule.yaml
index 82d0a3d81fe955ec40ad8f8578e37f1e93eca16b..6f287d10fc0ffd9df21751a09079b2a3308e5644 100644
--- a/packages/time_schedule.yaml
+++ b/packages/time_schedule.yaml
@@ -28,4 +28,11 @@ time:
               if (id(schedule).state == "Bloom") {
                 id(power_light).turn_off();
               }
-
+      - seconds: 0
+        minutes: /1
+        then:
+          - script.execute: light_power_state
+      - seconds: 0
+        minutes: /1
+        then:
+          - script.execute: fan_power_state
diff --git a/packages/yieryi3178.yaml b/packages/yieryi3178.yaml
deleted file mode 100644
index fb96446be1da4255103653d1a28ed49dddd23d35..0000000000000000000000000000000000000000
--- a/packages/yieryi3178.yaml
+++ /dev/null
@@ -1,55 +0,0 @@
-esphome:
-  includes:
-    - include/yieryi_3178.h
-
-uart:
-  id: uart_rs485
-  tx_pin: 19 # TXD
-  rx_pin: 18 # RXD
-  baud_rate: 9600
-
-sensor:
-- platform: custom
-  lambda: |-
-    auto yieryi3178 = new Yieryi3178(id(uart_rs485), 5000);
-    App.register_component(yieryi3178);
-    return {yieryi3178->ec, yieryi3178->ph, yieryi3178->rh, yieryi3178->temp, yieryi3178->bat};
-  sensors:
-  - id: yieryi3178_ec
-    name: "Water: EC"
-    unit_of_measurement: "mS"
-    accuracy_decimals: 3
-  - id: yieryi3178_ph
-    name: "Water: pH"
-    unit_of_measurement: "pH"
-    accuracy_decimals: 2
-  - id: yieryi3178_humidity
-    name: "Water: Humidity"
-    unit_of_measurement: "%"
-    accuracy_decimals: 0
-  - id: yieryi3178_temperature
-    name: "Water: Temperature"
-    unit_of_measurement: "°C"
-    accuracy_decimals: 1
-  - id: yieryi3178_battery
-    name: "Water: Battery Level"
-    unit_of_measurement: "%"
-    accuracy_decimals: 1
-
-prometheus:
-  relabel:
-    yieryi3178_ec:
-      id: ec
-      name: "EC"
-    yieryi3178_ph:
-      id: ph
-      name: "pH"
-    yieryi3178_humidity:
-      id: humidity_water
-      name: "Humidity (EC/pH Monitor)"
-    yieryi3178_temperature:
-      id: temperature_water
-      name: "Water Temperature"
-    yieryi3178_battery:
-      id: battery_water
-      name: "Battery Level"