diff --git a/README.md b/README.md
index 15b431c4df43dc19f9c758499429d592a7790a43..7f32052ab01d759c4a803be398770728f9fa0bb7 100644
--- a/README.md
+++ b/README.md
@@ -7,14 +7,14 @@ Home Grow(n) Monitoring
 * propbox: ESP32 Propagator Controller
     * BME280 Temperature and Humidity Sensor (3.3V, I2C)
     * Dallas DS18B20 Temperature Sensor
-    * YYAC-3S Triac (5V, PWM)
-* propbox-light: Nous A1T Smart Plug for Light
-* propbox-fan: Nous A1T Smart Plug for Fan
+    * 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)
     * BME280 Temperature and Humidity Sensor (3.3V, I2C)
-    * SCD40 CO2 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)
 
 ## Configuration
 Copy `packages/secrets.yaml.dist` to `packages/secrets.yaml` and adjust to your preferences.
diff --git a/docs/PK124WHSP-ECblue_pinout.png b/docs/PK124WHSP-ECblue_pinout.png
new file mode 100644
index 0000000000000000000000000000000000000000..92382d80491615df630ec832214c8b80b6636b46
Binary files /dev/null and b/docs/PK124WHSP-ECblue_pinout.png differ
diff --git a/docs/PK125WHSP-ECblue_PCB_bottom.jpg b/docs/PK125WHSP-ECblue_PCB_bottom.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..43bdc452a05a59a41c2ae3336d0393e9652e1804
Binary files /dev/null and b/docs/PK125WHSP-ECblue_PCB_bottom.jpg differ
diff --git a/docs/PK125WHSP-ECblue_PCB_top.jpg b/docs/PK125WHSP-ECblue_PCB_top.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..db9d0a59195357b1104257a966c09dd502c343d6
Binary files /dev/null and b/docs/PK125WHSP-ECblue_PCB_top.jpg differ
diff --git a/growbox.yaml b/growbox.yaml
index f7d51d0e736c24e9cb8360e11789b7a2d200d4a3..09ec990704fc6619ac199241668eafbd3885b3c3 100644
--- a/growbox.yaml
+++ b/growbox.yaml
@@ -10,4 +10,5 @@ packages:
   yieryi2178: !include packages/yieryi3178.yaml
   scd40:      !include packages/scd40.yaml
   mlx90614:   !include packages/mlx90614.yaml
+  gp8403:     !include packages/gp8403.yaml
   vpd:        !include packages/vpd.yaml
diff --git a/include/gp8403.h b/include/gp8403.h
new file mode 100644
index 0000000000000000000000000000000000000000..32a2e26f9b24294d779fb86cadd6c7e0438df078
--- /dev/null
+++ b/include/gp8403.h
@@ -0,0 +1,36 @@
+#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/packages/gp8403.yaml b/packages/gp8403.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d901d308de947b0a9008ceb1f3d11aa13ab015be
--- /dev/null
+++ b/packages/gp8403.yaml
@@ -0,0 +1,61 @@
+esphome:
+  includes:
+    - include/gp8403.h
+  libraries:
+    - Wire
+    - dfrobot/DFRobot_GP8403@^1.0.0
+
+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
+    - id: gp8403_ch1
+
+number:
+  - platform: template
+    id: dimmer_fan
+    name: "Dimmer: Fan"
+    min_value: 0
+    max_value: 100
+    step: 1
+    unit_of_measurement: '%'
+    optimistic: true
+    restore_value: true
+    on_value:
+      then:
+      - lambda: !lambda 'id(gp8403_ch0).set_level(x / 100);'
+  - platform: template
+    id: dimmer_light
+    name: "Dimmer: Light"
+    min_value: 0
+    max_value: 100
+    step: 1
+    unit_of_measurement: '%'
+    optimistic: true
+    restore_value: true
+    on_value:
+      then:
+      - lambda: !lambda 'id(gp8403_ch1).set_level(x / 100);'
+
+sensor:
+  - platform: template
+    id: fan_level
+    name: "Fan Level"
+    unit_of_measurement: '%'
+    accuracy_decimals: 0
+    lambda: !lambda 'return id(dimmer_fan).state;'
+    update_interval: 5s
+  - platform: template
+    id: light_level
+    name: "Light Level"
+    unit_of_measurement: '%'
+    accuracy_decimals: 0
+    lambda: !lambda 'return id(dimmer_light).state;'
+    update_interval: 5s