diff --git a/.github/workflows/buf-checks.yml b/.github/workflows/buf-checks.yml
deleted file mode 100644
index c9d1f4f9..00000000
--- a/.github/workflows/buf-checks.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: Linting and Backwards-Compatibility Checks
-on: [pull_request]
-jobs:
-  build:
-    runs-on: ubuntu-latest
-    steps:
-      - name: checkout
-        if: success()
-        uses: actions/checkout@v1
-        with:
-          ref: master
-      - name: checkout-master
-        if: success()
-        run: git checkout master
-      - name: checkout
-        if: success()
-        uses: actions/checkout@v1
-      - name: make local
-        if: success()
-        run: make local
diff --git a/.github/workflows/generate-local-arduino-artifacts.yml b/.github/workflows/generate-local-arduino-artifacts.yml
new file mode 100644
index 00000000..5a2a3db5
--- /dev/null
+++ b/.github/workflows/generate-local-arduino-artifacts.yml
@@ -0,0 +1,47 @@
+name: Generate new Arduino Protocol Buffer wrapper files and upload them as artifacts
+
+on: [pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.13.2"
+      - name: Install Protoc
+        uses: arduino/setup-protoc@v1
+        with:
+          version: "3.13.0"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - name: Install Python dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip3 install scons protobuf==5.29.4 grpcio-tools
+      - name: Self Checkout
+        uses: actions/checkout@v4
+        with:
+          path: protobuf-checkout
+          submodules: true
+      - name: Configure Git
+        run: |
+          git config --global user.name adafruitio
+          git config --global user.email adafruitio@adafruit.com
+      - name: Generate Arduino files from .proto Files
+        env:
+          PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
+        run: |
+          cd protobuf-checkout/
+          mkdir ./arduino_out
+          protoc --plugin=nanopb/generator/protoc-gen-nanopb --proto_path=./proto/wippersnapper --proto_path=nanopb/generator/proto ./proto/wippersnapper/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-v
+          # Local (non-CI) build command is commented out below, use this to compile on your machine:
+          # protoc --plugin=nanopb/generator/protoc-gen-nanopb --proto_path=./proto/wippersnapper --proto_path=nanopb/generator/proto ./proto/wippersnapper/*.proto  --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-t
+      - name: Checkout Arduino Repo
+        uses: actions/checkout@v4
+        with:
+          path: arduino-repo
+      - name: Upload Arduino Wrappers Artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: build-arduino-wrappers
+          path: ./protobuf-checkout/arduino_out
diff --git a/.github/workflows/generate-local-python-artifacts.yml b/.github/workflows/generate-local-python-artifacts.yml
new file mode 100644
index 00000000..f1c6963c
--- /dev/null
+++ b/.github/workflows/generate-local-python-artifacts.yml
@@ -0,0 +1,40 @@
+name: Generate new CPython Protocol Buffer wrapper files and upload them as artifacts
+on: [ pull_request ]
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/setup-python@v4
+      with:
+        python-version: '3.x'
+    - name: Install Protoc
+      uses: arduino/setup-protoc@v1
+    - name: Install Python dependencies
+      run : |
+        python -m pip install --upgrade pip
+        pip3 install scons protobuf grpcio-tools
+    - name: Self Checkout
+      uses: actions/checkout@v3
+      with:
+        path: protobuf-checkout
+        submodules: true
+    - name: Configure Git
+      run: |
+        git config --global user.name adafruitio
+        git config --global user.email adafruitio@adafruit.com
+    - name: Generate python wrapper files from pull request's .proto files
+      env:
+        PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
+      run: |
+        mkdir ./python_out
+        mkdir ./python_nanopb_out
+        protoc --proto_path=./protobuf-checkout/proto/wippersnapper --python_out=./python_out ./protobuf-checkout/proto/wippersnapper/*.proto
+        protoc ./protobuf-checkout/nanopb/generator/proto/nanopb.proto --python_out=./python_nanopb_out
+    - uses: actions/upload-artifact@v4
+      with:
+          name: build-python-wrappers
+          path: ./python_out 
+    - uses: actions/upload-artifact@v4
+      with:
+          name: build-python-nanopb-out
+          path: ./python_nanopb_out 
\ No newline at end of file
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..aff24aa9
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,20 @@
+name: "lint protobuf"
+on: pull_request
+
+jobs:
+  pr-check:
+    runs-on: ubuntu-latest
+    steps:
+    - name: Self Checkout
+      uses: actions/checkout@v3
+      with:
+          path: protobuf-checkout
+          submodules: false
+    - name: list file directory structure
+      run: ls -R
+    - name: reviewdog protolint
+      uses: yoheimuta/action-protolint@v1
+      with:
+        workdir: 'protobuf-checkout'
+        fail_on_error: true
+        protolint_flags: 'lint -config_dir_path . proto/wippersnapper'
diff --git a/.github/workflows/protoc-wrapper-generation.yml b/.github/workflows/protoc-wrapper-generation.yml
index 27987a7b..8c0dea46 100644
--- a/.github/workflows/protoc-wrapper-generation.yml
+++ b/.github/workflows/protoc-wrapper-generation.yml
@@ -13,45 +13,152 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
+      # Setup
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.x"
+      - name: Install Protoc
+        uses: arduino/setup-protoc@v1
+        with:
+          version: "3.13.0"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - name: Install Python dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install protobuf setuptools
+      - name: Self Checkout
+        uses: actions/checkout@v4
+        with:
+          path: protobuf-checkout
+          submodules: true
+      - name: Configure Git
+        run: |
+          git config --global user.name adafruitio
+          git config --global user.email adafruitio@adafruit.com
 
-    # Setup
-    - uses: actions/setup-python@v5
-      with:
-        python-version: '3.x'
-    - name: Install Protoc
-      uses: arduino/setup-protoc@v1
-      with:
-        version: '3.13.0'
-        repo-token: ${{ secrets.GITHUB_TOKEN }}
-    - name: Install Python dependencies
-      run : |
-        python -m pip install --upgrade pip
-        pip install protobuf setuptools
-    - name: Self Checkout
-      uses: actions/checkout@v4
-      with:
-        path: protobuf-checkout
-        submodules: true
-    - name: Configure Git
-      run: |
-        git config --global user.name adafruitio
-        git config --global user.email adafruitio@adafruit.com
+      # Python Wrappers
+      - name: Generate Python and JS Wrapper Files from .proto Files
+        run: |
+          mkdir ./python_out
+          mkdir ./python_nanopb_out
+          mkdir ./js_out
+          protoc --proto_path=./protobuf-checkout/proto/wippersnapper ./protobuf-checkout/proto/wippersnapper/*.proto --js_out=import_style=commonjs,binary:js_out --python_out=./python_out
+          protoc ./protobuf-checkout/nanopb/generator/proto/nanopb.proto --js_out=import_style=commonjs,binary:js_out --python_out=./python_nanopb_out
+      - name: Checkout Python Repo
+        uses: actions/checkout@v4
+        with:
+          repository: adafruit/Adafruit_Wippersnapper_Python
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: python-checkout
+      - name: Copy Python Files
+        run: |
+          rm -rf ./python-checkout/src/Adafruit_Wippersnapper_Python/proto
+          cp -r ./python_out/ ./python-checkout/src/Adafruit_Wippersnapper_Python/proto/
+          mv ./python_nanopb_out/protobuf_checkout/nanopb/generator/proto/nanopb_pb2.py ./python-checkout/src/Adafruit_Wippersnapper_Python/proto/nanopb_pb2.py
+      - name: Open Python Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd python-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git add adafruit_wippersnapper
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
 
-    # Arduino wrappers for the microcontrollers
-    - name: Generate Arduino files from .proto Files
-      env:
-        PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
-      run: |
-        mkdir ./arduino_out
-        mkdir ./doc
-        echo "Installing protobuf-gen-doc"
-        go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@latest
-        cp ~/go/bin/protoc-gen-doc .
-        echo "Generating docs..."
-        protoc --plugin=protobuf-checkout/nanopb/generator/protoc-gen-nanopb -Iprotobuf-checkout/nanopb/generator/proto --proto_path=./protobuf-checkout/proto ./protobuf-checkout/proto/wippersnapper/*/*/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./protobuf-checkout/proto --nanopb_opt=-t --plugin=protoc-gen-doc=./protoc-gen-doc --doc_out=./doc --doc_opt=html,index.html
-        # Local (non-CI) build command:
-        # protoc --plugin=nanopb/generator/protoc-gen-nanopb -Inanopb/generator/proto --proto_path=./proto ./proto/wippersnapper/*/*/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./proto --nanopb_opt=-t --plugin=protoc-gen-doc=./protoc-gen-doc --doc_out=./doc --doc_opt=html,index.html
+      # Arduino Wrappers
+      - name: Generate Arduino files from .proto Files
+        env:
+          PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
+        run: |
+          mkdir ./arduino_out
+          protoc --plugin=protobuf-checkout/nanopb/generator/protoc-gen-nanopb --proto_path=./protobuf-checkout/proto/wippersnapper --proto_path=nanopb/generator/proto ./protobuf-checkout/proto/wippersnapper/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-t
+          # Local (non-CI) build command is commented out below, use this to compile on your machine:
+          # protoc --plugin=nanopb/generator/protoc-gen-nanopb --proto_path=./proto/wippersnapper --proto_path=nanopb/generator/proto ./proto/wippersnapper/*.proto  --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-t
+      - name: Checkout Arduino Repo
+        uses: actions/checkout@v4
+        with:
+          repository: adafruit/Adafruit_Wippersnapper_Arduino
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: arduino-checkout
+      - name: Copy Generated Arduino Files to Arduino Repo
+        run: |
+          rm -rf ./arduino-checkout/src/wippersnapper/
+          mkdir ./arduino-checkout/src/wippersnapper/
+          cp -a ./arduino_out/. ./arduino-checkout/src/
+      - name: Open Arduino Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd arduino-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git status
+          git add src
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --base main --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
+
+<<<<<<< HEAD
+      # NodeJS wrappers for the mqtt broker
+      - name: Generate JS Wrapper Files from .proto Files
+        run: |
+          mkdir ./js_out
+          protoc --proto_path=./protobuf-checkout/proto ./protobuf-checkout/proto/wippersnapper/*/*/*.proto --js_out=import_style=commonjs,binary:js_out
+          protoc ./protobuf-checkout/nanopb/generator/proto/nanopb.proto --js_out=import_style=commonjs,binary:js_out
+      - name: Checkout io-node Repo
+        uses: actions/checkout@v4
+        with:
+          repository: AdafruitInternalDev/io-node
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: js-checkout
+          ref: main
+      - name: Copy JS Files
+        run: |
+          rm -rf ./js-checkout/lib/wprsnpr/protobufs/wippersnapper/
+          cp -r ./js_out/wippersnapper/ ./js-checkout/lib/wprsnpr/protobufs/wippersnapper/
+          rm -rf ./js-checkout/lib/wprsnpr/protobufs/nanopb/
+          mkdir ./js-checkout/lib/wprsnpr/protobufs/nanopb/
+          mv ./js_out/protobuf-checkout/nanopb/generator/proto/nanopb_pb.js ./js-checkout/lib/wprsnpr/protobufs/nanopb/nanopb_pb.js
+      - name: Open Node Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd js-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git add lib/wprsnpr/protobufs/
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --base main --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
 
+      # Docs
+      - name: Checkout doc branch
+        uses: actions/checkout@v4
+        with:
+          ref: gh-pages
+          path: protobuf-docs-checkout
+      - name: Commit and push generated HTML Docs file
+        run: |
+          cd protobuf-docs-checkout
+          cp -r ../doc ./doc
+          git add doc/
+          git commit -m "Add generated HTML documentation for $GITHUB_SHA" || exit 0 # quit cleanly if nothing to commit
+          git push
+      - name: Setup Pages
+        uses: actions/configure-pages@v3
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v2
+        with:
+          path: "protobuf-docs-checkout/doc"
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v2
+=======
     - name: Checkout Arduino Repo
       uses: actions/checkout@v4
       with:
@@ -132,3 +239,4 @@ jobs:
     - name: Deploy to GitHub Pages
       id: deployment
       uses: actions/deploy-pages@v4
+>>>>>>> master
diff --git a/.github/workflows/test-wrapper-arduino.yml b/.github/workflows/test-wrapper-arduino.yml
index d658488a..dc492442 100644
--- a/.github/workflows/test-wrapper-arduino.yml
+++ b/.github/workflows/test-wrapper-arduino.yml
@@ -1,44 +1,159 @@
-name: Generate new proto wrapper files and upload artifacts
-on: [ pull_request ]
+name: Generate .proto Wrapper Files
+on:
+  push:
+    branches: master
+
+# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+permissions:
+  contents: write
+  pages: write
+  id-token: write
+
 jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/setup-python@v4
-      with:
-        python-version: '3.x'
-    - name: Install Protoc
-      uses: arduino/setup-protoc@v1
-      with:
-        version: '3.13.0'
-    - name: Install Python dependencies
-      run : |
-        python -m pip install --upgrade pip
-        pip3 install scons protobuf grpcio-tools
-    - name: Self Checkout
-      uses: actions/checkout@v3
-      with:
-        path: protobuf-checkout
-        submodules: true
-    - name: Configure Git
-      run: |
-        git config --global user.name adafruitio
-        git config --global user.email adafruitio@adafruit.com
-    - name: Generate Arduino files from .proto Files
-      env:
-        PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
-      run: |
-        mkdir ./arduino_out
-        mkdir ./doc
-        echo "Installing protobuf-gen-doc"
-        go install github.com/pseudomuto/protoc-gen-doc/cmd/protoc-gen-doc@latest
-        cp ~/go/bin/protoc-gen-doc .
-        echo "Generating docs..."
-        protoc --plugin=protobuf-checkout/nanopb/generator/protoc-gen-nanopb -Iprotobuf-checkout/nanopb/generator/proto --proto_path=./protobuf-checkout/proto ./protobuf-checkout/proto/wippersnapper/*/*/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./protobuf-checkout/proto --nanopb_opt=-t --plugin=protoc-gen-doc=./protoc-gen-doc --doc_out=./doc --doc_opt=html,index.html
-        # Local (non-CI) build command:
-        # protoc --plugin=nanopb/generator/protoc-gen-nanopb -Inanopb/generator/proto --proto_path=./proto ./proto/wippersnapper/*/*/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./proto --nanopb_opt=-t --plugin=protoc-gen-doc=./protoc-gen-doc --doc_out=./doc --doc_opt=html,index.html
-        cp ./doc/index.html ./arduino_out
-    - uses: actions/upload-artifact@v4
-      with:
-          name: build-artifact-arduino-wrapper
-          path: ./arduino_out 
\ No newline at end of file
+      # Setup
+      - uses: actions/setup-python@v3
+        with:
+          python-version: "3.x"
+      - name: Install Protoc
+        uses: arduino/setup-protoc@v1
+        with:
+          version: "3.13.0"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - name: Install Python dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install protobuf setuptools
+      - name: Self Checkout
+        uses: actions/checkout@v4
+        with:
+          path: protobuf-checkout
+          submodules: true
+      - name: Configure Git
+        run: |
+          git config --global user.name adafruitio
+          git config --global user.email adafruitio@adafruit.com
+
+      # Python Wrappers
+      - name: Generate Python and JS Wrapper Files from .proto Files
+        run: |
+          mkdir ./python_out
+          mkdir ./python_nanopb_out
+          mkdir ./js_out
+          protoc --proto_path=./protobuf-checkout/proto/wippersnapper ./protobuf-checkout/proto/wippersnapper/*.proto --js_out=import_style=commonjs,binary:js_out --python_out=./python_out
+          protoc ./protobuf-checkout/nanopb/generator/proto/nanopb.proto --js_out=import_style=commonjs,binary:js_out --python_out=./python_nanopb_out
+      - name: Checkout Python Repo
+        uses: actions/checkout@v4
+        with:
+          repository: adafruit/Adafruit_Wippersnapper_Python
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: python-checkout
+      - name: Copy Python Files
+        run: |
+          rm -rf ./python-checkout/src/Adafruit_Wippersnapper_Python/proto
+          cp -r ./python_out/ ./python-checkout/src/Adafruit_Wippersnapper_Python/proto/
+          mv ./python_nanopb_out/protobuf_checkout/nanopb/generator/proto/nanopb_pb2.py ./python-checkout/src/Adafruit_Wippersnapper_Python/proto/nanopb_pb2.py
+      - name: Open Python Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd python-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git add adafruit_wippersnapper
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
+
+      # Arduino Wrappers
+      - name: Generate Arduino files from .proto Files
+        env:
+          PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION: python
+        run: |
+          mkdir ./arduino_out
+          protoc --plugin=protobuf-checkout/nanopb/generator/protoc-gen-nanopb --proto_path=./protobuf-checkout/proto/wippersnapper --proto_path=nanopb/generator/proto ./protobuf-checkout/proto/wippersnapper/*.proto --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-t
+          # Local (non-CI) build command is commented out below, use this to compile on your machine:
+          # protoc --plugin=nanopb/generator/protoc-gen-nanopb --proto_path=./proto/wippersnapper --proto_path=nanopb/generator/proto ./proto/wippersnapper/*.proto  --nanopb_out=./arduino_out --nanopb_opt=-I./proto/wippersnapper --nanopb_opt=-t
+      - name: Checkout Arduino Repo
+        uses: actions/checkout@v4
+        with:
+          repository: adafruit/Adafruit_Wippersnapper_Arduino
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: arduino-checkout
+      - name: Copy Generated Arduino Files to Arduino Repo
+        run: |
+          rm -rf ./arduino-checkout/src/wippersnapper/
+          mkdir ./arduino-checkout/src/wippersnapper/
+          cp -a ./arduino_out/. ./arduino-checkout/src/
+      - name: Open Arduino Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd arduino-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git status
+          git add src
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --base main --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
+
+      # NodeJS wrappers for the mqtt broker
+      - name: Generate JS Wrapper Files from .proto Files
+        run: |
+          mkdir ./js_out
+          protoc --proto_path=./protobuf-checkout/proto ./protobuf-checkout/proto/wippersnapper/*/*/*.proto --js_out=import_style=commonjs,binary:js_out
+          protoc ./protobuf-checkout/nanopb/generator/proto/nanopb.proto --js_out=import_style=commonjs,binary:js_out
+      - name: Checkout io-node Repo
+        uses: actions/checkout@v4
+        with:
+          repository: AdafruitInternalDev/io-node
+          token: ${{ secrets.IO_BOT_PAT }}
+          path: js-checkout
+          ref: main
+      - name: Copy JS Files
+        run: |
+          rm -rf ./js-checkout/lib/wprsnpr/protobufs/wippersnapper/
+          cp -r ./js_out/wippersnapper/ ./js-checkout/lib/wprsnpr/protobufs/wippersnapper/
+          rm -rf ./js-checkout/lib/wprsnpr/protobufs/nanopb/
+          mkdir ./js-checkout/lib/wprsnpr/protobufs/nanopb/
+          mv ./js_out/protobuf-checkout/nanopb/generator/proto/nanopb_pb.js ./js-checkout/lib/wprsnpr/protobufs/nanopb/nanopb_pb.js
+      - name: Open Node Pull Request
+        env:
+          GITHUB_USER: adafruitio
+          GITHUB_TOKEN: ${{ secrets.IO_BOT_PAT }}
+          PROTOBUF_BRANCH_NAME: protobuf-update
+        run: |
+          cd js-checkout
+          git checkout -b $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          git add lib/wprsnpr/protobufs/
+          git commit -m "Updating protobuf wrappers" || exit 0 # quit cleanly if nothing to commit
+          hub push origin $PROTOBUF_BRANCH_NAME-$GITHUB_SHA
+          hub pull-request --base main --message ".proto file wrappers updated" --message "Auto-generated by [GitHub Actions on Protobuf Repo][1]" --message "[1]: https://github.com/adafruit/Wippersnapper_Protobuf/blob/master/.github/workflows/protoc-wrapper-generation.yml"
+
+      # Docs
+      - name: Checkout doc branch
+        uses: actions/checkout@v4
+        with:
+          ref: gh-pages
+          path: protobuf-docs-checkout
+      - name: Commit and push generated HTML Docs file
+        run: |
+          cd protobuf-docs-checkout
+          cp -r ../doc ./doc
+          git add doc/
+          git commit -m "Add generated HTML documentation for $GITHUB_SHA" || exit 0 # quit cleanly if nothing to commit
+          git push
+      - name: Setup Pages
+        uses: actions/configure-pages@v3
+      - name: Upload artifact
+        uses: actions/upload-pages-artifact@v2
+        with:
+          path: "protobuf-docs-checkout/doc"
+      - name: Deploy to GitHub Pages
+        id: deployment
+        uses: actions/deploy-pages@v2
diff --git a/.gitignore b/.gitignore
index 920fdfa2..ae57188d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@
 __pycache__/
 *.py[cod]
 *$py.class
+.DS_Store
+
+.vscode/
 
 # C extensions
 *.so
@@ -139,4 +142,7 @@ dmypy.json
 # Cython debug symbols
 cython_debug/
 
-.DS_Store
\ No newline at end of file
+arduino_out/
+
+nanopb/
+.DS_Store
diff --git a/README.md b/README.md
index aed9575c..e82dfd4b 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,8 @@ If you would like to request a component or feature to be added into WipperSnapp
 
 This repository includes an automated script for generating the required protocol buffer wrapper messages.
 
-Nanopb header files for Arduino may also be generated by installing `protoc`, cloning this repository, and invoking:
+Nanopb header files for Arduino may also be generated by installing `protoc`, cloning this repository, and invoking 
+
 ```
 git submodule init
 git submodule update
diff --git a/buf.yaml b/buf.yaml
deleted file mode 100644
index 10acfeed..00000000
--- a/buf.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-version: v1beta1
-build:
-  roots:
-    - proto
-lint:
-  use:
-    - DEFAULT
-  ignore:
-    - nanopb/nanopb.proto
-# TODO: Note that we've turned off breaking change detection
-# while we develop the protobuffs, re-enable this in the repo
-# for accepting changes to finalized protobufs.
-#breaking:
-#  use:
-#    - FILE
diff --git a/nanopb b/nanopb
index 049485ff..6cfe48d6 160000
--- a/nanopb
+++ b/nanopb
@@ -1 +1 @@
-Subproject commit 049485ff557178f646d573eca3bd647f543b760b
+Subproject commit 6cfe48d6f1593f8fa5c0f90437f5e6522587745e
diff --git a/proto/wippersnapper/analogio.options b/proto/wippersnapper/analogio.options
new file mode 100644
index 00000000..26bdf683
--- /dev/null
+++ b/proto/wippersnapper/analogio.options
@@ -0,0 +1,4 @@
+# analogio.options
+wippersnapper.analogio.AnalogIOAdd.pin_name     max_size: 64
+wippersnapper.analogio.AnalogIORemove.pin_name  max_size: 64
+wippersnapper.analogio.AnalogIOEvent.pin_name   max_size: 64
\ No newline at end of file
diff --git a/proto/wippersnapper/analogio.proto b/proto/wippersnapper/analogio.proto
new file mode 100644
index 00000000..6abc7df2
--- /dev/null
+++ b/proto/wippersnapper/analogio.proto
@@ -0,0 +1,30 @@
+// SPDX-FileCopyrightText: 2023 Brent Rubell, Loren Norman, Tyeth Gundry for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.analogio;
+import "sensor.proto";
+
+/**
+* AnalogIOAdd adds an analog pin to the device.
+*/
+message AnalogIOAdd {
+  string pin_name                           = 1; /** Name of the pin. */
+  float period                              = 2; /** Time between reads, in seconds. */
+  wippersnapper.sensor.SensorType read_mode = 3; /** Desired read mode for the pin. */
+}
+
+/**
+* AnalogIORemove removes an analog pin from the device.
+*/
+message AnalogIORemove {
+  string pin_name = 1; /** Name of the pin. */
+}
+
+
+/**
+* AnalogIOEvent is contains a value, sent when an analog pin is read.
+*/
+message AnalogIOEvent {
+  string pin_name                                = 1; /** Name of the pin. */
+  wippersnapper.sensor.SensorEvent sensor_event  = 2; /** Reading(s) from an analog pin. */
+}
diff --git a/proto/wippersnapper/checkin.options b/proto/wippersnapper/checkin.options
new file mode 100644
index 00000000..bf5e0f31
--- /dev/null
+++ b/proto/wippersnapper/checkin.options
@@ -0,0 +1,3 @@
+# checkin.options
+wippersnapper.checkin.CheckinRequest.hardware_uid max_size: 64
+wippersnapper.checkin.CheckinRequest.firmware_version max_size: 25
\ No newline at end of file
diff --git a/proto/wippersnapper/checkin.proto b/proto/wippersnapper/checkin.proto
new file mode 100644
index 00000000..40c1e5b6
--- /dev/null
+++ b/proto/wippersnapper/checkin.proto
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: 2020-2024 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+// Messages for registering new and existing hardware with the Adafruit.io MQTT Broker
+syntax = "proto3";
+package wippersnapper.checkin;
+
+/**
+* CheckinRequest notifies the MQTT broker that a new/existing device is requesting to connect.
+*/
+message CheckinRequest {
+  string hardware_uid = 1; /** Identifies the client's physical hardware (board name + last 3 of NIC's MAC address). */
+  string firmware_version = 2; /** Identifies the client's firmware version. */
+}
+
+/**
+* CheckinResponse sends a broker response to the client's CheckinRequest.
+*/
+message CheckinResponse {
+  Response response        = 1; /** Specifies if the hardware definition exists on the server. */
+  int32 total_gpio_pins    = 2; /** Specifies the number of GPIO pins on the device. */
+  int32 total_analog_pins  = 3; /** Specifies the number of analog pins on the device. */
+  float reference_voltage  = 4; /** Specifies the hardware's default reference voltage. */
+
+  /**
+   * Response. Specifies if the hardware definiton is within the database.
+   */
+  enum Response {
+    RESPONSE_UNSPECIFIED     = 0; /** Invalid response from server */
+    RESPONSE_OK              = 1; /** Board found within definition index */
+    RESPONSE_BOARD_NOT_FOUND = 2; /** Board not found in definition index */
+  }
+}
diff --git a/proto/wippersnapper/description/v1/description.proto b/proto/wippersnapper/description/v1/description.proto
deleted file mode 100644
index 0bbd9751..00000000
--- a/proto/wippersnapper/description/v1/description.proto
+++ /dev/null
@@ -1,77 +0,0 @@
-// SPDX-FileCopyrightText: 2020-2021 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-// Messages for describing hardware over the description topic
-syntax = "proto3";
-
-package wippersnapper.description.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* CreateDescriptionRequest identifies a device with Adafruit.io WipperSnapper.
-*/
-message CreateDescriptionRequest {
-  string machine_name    = 1 [(nanopb).max_size = 64]; /** Identifies client's physical hardware */
-  int32 mac_addr         = 2; /** Client's UID, last 3 bytes of MAC address */
-  int32 usb_vid          = 3; /** Optional, USB Vendor ID */
-  int32 usb_pid          = 4; /** Optional, USB Product ID */
-  Version version        = 5 [deprecated = true, (nanopb).type = FT_IGNORE]; /** Client's library version. */
-  int32 ver_major        = 10 [deprecated = true, (nanopb).type = FT_IGNORE];
-  int32 ver_minor        = 11 [deprecated = true, (nanopb).type = FT_IGNORE];
-  int32 ver_patch        = 12 [deprecated = true, (nanopb).type = FT_IGNORE];
-  string ver_pre_release = 13 [deprecated = true, (nanopb).type = FT_IGNORE, (nanopb).max_size = 6];
-  int32 ver_build        = 14 [deprecated = true, (nanopb).type = FT_IGNORE];
-  string str_version     = 15 [(nanopb).max_size = 20]; /** Library version, as a string */
-
-
-  message Version {
-    uint64 major           = 1 [deprecated = true, (nanopb).type = FT_IGNORE];
-    uint64 minor           = 2 [deprecated = true, (nanopb).type = FT_IGNORE];
-    uint64 micro           = 3 [deprecated = true, (nanopb).type = FT_IGNORE];
-    string label           = 4 [deprecated = true, (nanopb).type = FT_IGNORE];
-    int32 ver_major        = 5 [deprecated = true, (nanopb).type = FT_IGNORE];
-    int32 ver_minor        = 6 [deprecated = true, (nanopb).type = FT_IGNORE];
-    int32 ver_patch        = 7 [deprecated = true, (nanopb).type = FT_IGNORE];
-    string ver_pre_release = 8 [deprecated = true, (nanopb).type = FT_IGNORE];
-    int32 ver_build        = 9 [deprecated = true, (nanopb).type = FT_IGNORE];
-  }
-}
-
-/**
-* CreateDescriptionResponse represents a device's specifications.
-*/
-message CreateDescriptionResponse {
-  Response response        = 1; /** Specifies if the hardware definition exists on the server. */
-  int32 total_gpio_pins    = 2; /** Specifies the number of GPIO pins on the client's physical hardware. */
-  int32 total_analog_pins  = 3; /** Specifies the number of analog pins on the client's physical hardware. */
-  float reference_voltage  = 4; /** Specifies the hardware's default reference voltage. */
-  int32 total_i2c_ports    = 5; /** Specifies the number of hardware's I2C ports (i2cPorts[]). */
-
-  /**
-   * Response. Specifies if the hardware definiton is within the database.
-   */
-  enum Response {
-    RESPONSE_UNSPECIFIED     = 0; /** Invalid response from server */
-    RESPONSE_OK              = 1; /** Board found within definition index */
-    RESPONSE_BOARD_NOT_FOUND = 2; /** Board not found in definition index */
-  }
-}
-
-/**
-* RegistrationComplete Specifies if the device finished configuring
-* its components and is ready for configuration messages.
-*/
-message RegistrationComplete {
-  bool is_complete = 1; /** True if device successfully configured its components, False otherwise. */
-}
-
-// Request the board definition JSON from a device
-// MQTT Topic: `device/ID/description/get`
-message GetDefinitionRequest {
-  string data = 1 [deprecated = true, (nanopb).type = FT_IGNORE]; // Request may be any UTF-8 string value
-}
-
-// Response from Adafruit IO with the JSON board definition as a string
-// MQTT Topic: `device/ID/description/get`
-message GetDefinitionResponse {
-  string board_definition = 1 [deprecated = true, (nanopb).type = FT_IGNORE]; // Response is JSON data encoded as a string
-}
diff --git a/proto/wippersnapper/digitalio.options b/proto/wippersnapper/digitalio.options
new file mode 100644
index 00000000..635086ed
--- /dev/null
+++ b/proto/wippersnapper/digitalio.options
@@ -0,0 +1,5 @@
+# digitalio.options
+wippersnapper.digitalio.DigitalIOAdd.pin_name max_size: 64
+wippersnapper.digitalio.DigitalIORemove.pin_name max_size: 64
+wippersnapper.digitalio.DigitalIOEvent.pin_name max_size: 64
+wippersnapper.digitalio.DigitalIOWrite.pin_name max_size: 64
\ No newline at end of file
diff --git a/proto/wippersnapper/digitalio.proto b/proto/wippersnapper/digitalio.proto
new file mode 100644
index 00000000..118d995b
--- /dev/null
+++ b/proto/wippersnapper/digitalio.proto
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: 2024 Brent Rubell, Loren Norman, Tyeth Gundry for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.digitalio;
+import "sensor.proto";
+
+/**
+* DigitalIOSampleMode specifies the pin's sample mode.
+*/
+enum DigitalIOSampleMode {
+  DIGITAL_IO_SAMPLE_MODE_UNSPECIFIED = 0; /** Invalid Sample Mode from Broker. */
+  DIGITAL_IO_SAMPLE_MODE_TIMER       = 1; /** Periodically sample the pin's value. */
+  DIGITAL_IO_SAMPLE_MODE_EVENT       = 2; /** Sample the pin's value when an event occurs. */
+}
+
+/**
+* DigitalIODirection specifies the pin's direction, INPUT/INPUT_PULL_UP/OUTPUT.
+*/
+enum DigitalIODirection {
+  DIGITAL_IO_DIRECTION_UNSPECIFIED   = 0; /** Invalid Direction from Broker. */
+  DIGITAL_IO_DIRECTION_INPUT         = 1; /** Set the pin to behave as an input. */
+  DIGITAL_IO_DIRECTION_INPUT_PULL_UP = 2; /** Set the pin to behave as an input. */
+  DIGITAL_IO_DIRECTION_OUTPUT        = 3; /** Set the pin to behave as an output. */
+}
+
+/**
+* DigitalIOAdd adds a digital pin to the device.
+*/
+message DigitalIOAdd {
+  string pin_name                   = 1; /** The pin's name. */
+  DigitalIODirection gpio_direction = 2; /** The pin's direction. */
+  DigitalIOSampleMode sample_mode   = 3; /** Specifies the pin's sample mode. */
+  float period                      = 4; /** Time between measurements in seconds, if MODE_TIMER. */
+  bool value                        = 5; /** Re-sync only - send the pin's value. */
+}
+
+/**
+* DigitalIORemove removes a digital pin from the device.
+*/
+message DigitalIORemove {
+  string pin_name = 1; /** The pin's name. */
+}
+
+/**
+* DigitalIOEvent is sent from the device to the broker when a digital pin's value changes.
+*/
+message DigitalIOEvent {
+  string pin_name                        = 1; /** The pin's name. */
+  wippersnapper.sensor.SensorEvent value = 2; /** The pin's value. */
+}
+
+/**
+* DigitalIOWrite writes a boolean value to a digital pin.
+*/
+message DigitalIOWrite {
+  string pin_name                        = 1; /** The pin's name. */
+  wippersnapper.sensor.SensorEvent value = 2; /** The pin's value. */
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/display/v1/display.md b/proto/wippersnapper/display/v1/display.md
deleted file mode 100644
index e44f5b32..00000000
--- a/proto/wippersnapper/display/v1/display.md
+++ /dev/null
@@ -1,83 +0,0 @@
-
-# display.proto
-
-This file details the WipperSnapper messaging API for interfacing with a display.
-
-## WipperSnapper Components
-
-The following WipperSnapper Components may utilize `display.proto`:
-
-* E-Ink/E-Paper Displays
-* TFT Displays
-* OLED Displays
-* 7-Segment Displays
-* Alphanumeric Displays
-* LCD Character Displays
-
-## Sequence Diagrams
-
-### Attaching a Display Component to a device running WipperSnapper
-
-```mermaid
-sequenceDiagram
-autonumber
-
-IO-->>Device: DisplayAddOrReplace
-Note over IO, Device: DisplayType field dictates which
display we are using (LED, E-Ink, etc)
-
-Device->>ws_display controller: DisplayAddOrReplace
-
-ws_display controller->>ws_display hardware: DisplayAddOrReplace
-
-ws_display hardware->>ws_display driver: Driver Configure Request
-
-ws_display driver->>ws_display hardware:  Driver Configure Response
-
-ws_display hardware->>ws_display controller: Hardware Response
-
-ws_display controller-->>Device: DisplayAddedorReplaced
-
-Device-->>IO: DisplayAddedorReplaced
-```
-
-### Removing a Display Component from a device running WipperSnapper
-
-```mermaid
-sequenceDiagram
-autonumber
-
-IO-->>Device: DisplayRemove
-Note over IO, Device: name field dictates which
display we are removing
-
-Device->>ws_display controller: DisplayRemove
-
-ws_display controller->>ws_display hardware: Delete hardware instance
-
-ws_display hardware->>ws_display driver: Delete driver instance
-
-ws_display driver->>ws_display hardware: Deletion Result
-
-ws_display hardware->>ws_display controller: Deletion Result
-
-ws_display controller-->>Device: DisplayRemoved
-
-Device-->>IO: DisplayRemoved
-```
-
-### Writing to a Display from IO
-
-The display message is set by the component's feed value, which is a string. The message is sent to the display driver.
-
-```mermaid
-sequenceDiagram
-autonumber
-
-IO-->>Device: DisplayWrite
-Note over IO, Device: name field dictates which
display we are writing to
-
-Device->>ws_display controller: handleDisplayWrite()
-
-ws_display controller->>ws_display hardware: Get display hardware
-
-ws_display hardware->>ws_display driver: Execute writeX()
-```
\ No newline at end of file
diff --git a/proto/wippersnapper/display/v1/display.proto b/proto/wippersnapper/display/v1/display.proto
deleted file mode 100644
index cbe09152..00000000
--- a/proto/wippersnapper/display/v1/display.proto
+++ /dev/null
@@ -1,148 +0,0 @@
-// SPDX-FileCopyrightText: 2025 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-// Display API for Adafruit WipperSnapper
-
-syntax = "proto3";
-
-package wippersnapper.display.v1;
-import "nanopb/nanopb.proto";
-
-// ============================================================================
-// Enums
-// ============================================================================
-
-/**
- * DisplayType determines the type of display
- * and selects which driver should be used.
- */
-enum DisplayType {
-  DISPLAY_TYPE_UNSPECIFIED = 0; /** Unspecified display type. */
-  DISPLAY_TYPE_EPD         = 1; /** EPD display type */
-  DISPLAY_TYPE_TFT         = 2; /** TFT display type */
-}
-
-/**
- * EPDMode defines the mode of the EPD display.
- * This affects how the display renders images.
- */
-enum EPDMode {
-  EPD_MODE_UNSPECIFIED = 0; /** Unspecified EPD mode */
-  EPD_MODE_GRAYSCALE4  = 1; /** Grayscale 4 EPD mode */
-  EPD_MODE_MONO        = 2; /** Monochrome EPD mode */
-}
-
-/**
- * DisplayDriver determines the specific driver to use for the display.
- */
-enum DisplayDriver {
-  DISPLAY_DRIVER_UNSPECIFIED = 0; /** Unspecified display driver */
-  DISPLAY_DRIVER_EPD_SSD1680 = 1; /** EPD SSD1680 driver */
-  DISPLAY_DRIVER_EPD_ILI0373 = 2; /** EPD SSD167 driver */
-  DISPLAY_DRIVER_TFT_ST7789  = 3; /** TFT ST7789 driver */
-}
-
-// ============================================================================
-// Configuration Messages
-// ============================================================================
-
-/**
- * EpdSpiConfig contains the configuration for SPI-based EPD displays.
- * Includes the SPI bus number and pin assignments.
- */
-message EpdSpiConfig {
-  int32 bus          = 1; /** The SPI bus to use */
-  string pin_dc      = 2[(nanopb).max_size = 6]; /** Pin for data/command */
-  string pin_rst     = 3[(nanopb).max_size = 6]; /** Pin for reset */
-  string pin_cs      = 4[(nanopb).max_size = 6]; /** Pin for chip select */
-  string pin_sram_cs = 5[(nanopb).max_size = 6]; /** Pin for SRAM chip select */
-  string pin_busy    = 6[(nanopb).max_size = 6]; /** Pin for busy signal */
-}
-
-/**
- * EPDConfig contains the configuration for an EPD display.
- * It includes the mode, dimensions, and panel type.
- */
-message EPDConfig {
-  EPDMode mode    = 1; /** The mode of the EPD display */
-  int32 width     = 2;   /** Width of the EPD display, in pixels */
-  int32 height    = 3;   /** Height of the EPD display, in pixels */
-  int32 text_size = 4; /** Scale factor for text (1 = 6x8px, 2 = 12x16px,...) */
-}
-
-/**
- * TftSpiConfig contains the configuration for SPI TFT displays.
- * Includes the SPI bus number and pin assignments.
- */
-message TftSpiConfig {
-  int32 bus          = 1; /** The SPI bus to use */
-  string pin_cs      = 2[(nanopb).max_size = 6]; /** Pin for chip select */
-  string pin_dc      = 3[(nanopb).max_size = 6]; /** Pin for data/command */
-  string pin_mosi    = 4[(nanopb).max_size = 6]; /** Pin for MOSI */
-  string pin_sck     = 5[(nanopb).max_size = 6]; /** Pin for SCK */
-  string pin_rst     = 6[(nanopb).max_size = 6]; /** Pin for reset */
-  string pin_miso    = 7[(nanopb).max_size = 6]; /** Pin for MISO */
-}
-
-message TftConfig {
-  int32 width     = 1;   /** Width of the TFT display in pixels */
-  int32 height    = 2;   /** Height of the TFT display in pixels */
-  int32 rotation  = 3; /** Rotation of the display (0-3) */
-  int32 text_size = 4; /** Scale factor for text (1 = 6x8px, 2 = 12x16px,...) */
-}
-
-// ============================================================================
-// Request Messages
-// ============================================================================
-
-/**
- * DisplayAddOrReplace adds a new display or replaces an existing one.
- */
-message DisplayAddOrReplace {
-  DisplayType type       = 1; /** The type of display */
-  DisplayDriver driver   = 2; /** The specific driver for the display */
-  string name = 3[(nanopb).max_size = 64]; /** Identifies the display. */
-  oneof interface_type {
-    EpdSpiConfig spi_epd = 4; /** SPI configuration for EPD. */
-    TftSpiConfig spi_tft = 5; /** SPI configuration for TFT. */
-  }
-  oneof config {
-    EPDConfig config_epd = 6; /** Configuration for EPD. */
-    TftConfig config_tft = 7; /** Configuration for TFT. */
-  }
-}
-
-/**
- * DisplayRemove removes an existing display.
- */
-message DisplayRemove {
-  string name = 1[(nanopb).max_size = 64]; /** Identifier for the display. */
-}
-
-/**
- * DisplayWrite writes content to a display.
- */
-message DisplayWrite {
-  string name     = 1[(nanopb).max_size = 64]; /** Identifies the display. */
-  string message  = 2[(nanopb).max_size = 1024]; /** Message to write. */
-}
-
-// ============================================================================
-// Response Messages
-// ============================================================================
-
-/**
- * DisplayAddedOrReplaced confirms a display was added or replaced.
- */
-message DisplayAddedOrReplaced {
-  string name  = 1[(nanopb).max_size = 64]; /** Identifier for the display. */
-  bool did_add = 2; /** Indicates if a display was added */
-}
-
-/**
- * DisplayRemoved confirms a display was removed.
- */
-message DisplayRemoved {
-  string name     = 1[(nanopb).max_size = 64]; /** Identifies the display. */
-  bool did_remove = 2; /** Indicates if a display was removed */
-}
-
diff --git a/proto/wippersnapper/docs/analogio.md b/proto/wippersnapper/docs/analogio.md
new file mode 100644
index 00000000..a3c39771
--- /dev/null
+++ b/proto/wippersnapper/docs/analogio.md
@@ -0,0 +1 @@
+# TODO!
\ No newline at end of file
diff --git a/proto/wippersnapper/description/v1/description.md b/proto/wippersnapper/docs/checkin.md
similarity index 51%
rename from proto/wippersnapper/description/v1/description.md
rename to proto/wippersnapper/docs/checkin.md
index 5c60801f..c41f7a82 100644
--- a/proto/wippersnapper/description/v1/description.md
+++ b/proto/wippersnapper/docs/checkin.md
@@ -19,42 +19,24 @@ sequenceDiagram
 
 autonumber
 
-Device-->>IO: CreateDescriptionRequest
+Device-->>IO: CheckinRequest
 Note over Device,IO: This message is sent over the general MQTT 
topic: /:user/wprsnpr/status
 IO->>Storage (DB): Check if machine_name
exists as a JSON definition 
 within the Boards repo
 Storage (DB)->>IO: Return RESPONSE_OK
or RESPONSE_BOARD_NOT_FOUND
-IO-->>Device: CreateDescriptionResponse 
+IO-->>Device: CheckinResponse 
 Note over IO,Device: Contains metadata about the physical hardware
from the JSON definition file.
 ```
 
 
 ### Process: Hardware Configuration
 
-Where it can, the WipperSnapper firmware avoids dynamic allocation. If the `CreateDescriptionResponse` message contains a valid response( `RESPONSE_OK` ), fields from the `CreateDescriptionResponse` message are parsed and used to configure some hardware-specific classes:
+Where it can, the WipperSnapper firmware avoids dynamic allocation. If the `CheckinResponse` message contains a valid response( `RESPONSE_OK` ), fields from the `CheckinResponse` message are parsed and used to configure some hardware-specific classes:
   
 ```mermaid
 sequenceDiagram
 autonumber
-Device->>App: Parse CreateDescriptionResponse
+Device->>App: Parse CheckinResponse
 App->>Digital IO Class: Configure total_gpio_pins 
 App->>Analog IO Class: Configure total_analog_pins and reference_voltage 
 App->>I2C Class: Configure total_i2c_ports
-```
-
-### Process: Hardware Sync
-
-The hardware sync process described by this API is outdated and will be depreciated in a future API version. It only exists for use with the Digital IO class.  
-
-After the broker sends the `CreateDescriptionResponse` message, it sends the values and states for any digital IO pins configured on the device. Then, it waits for a `RegistrationComplete` response from the device. The `RegistrationComplete` message confirms that the hardware has completed configuring its state and values.
-  
-```mermaid
-sequenceDiagram
-autonumber
-IO-->>Device: CreateDescriptionResponse
-IO-->>Device: ConfigurePinRequests
-Note over IO,Device: This message contains a list of
ConfigurePinRequest messages, one per 
digital GPIO pin.
-Device-->>DigitalGPIO: Decode and configure
 each ConfigurePinRequest message
-Device-->>IO: RegistrationComplete
-Note over Device,IO: After the ConfigurePinRequests have completed, 
tell the broker that the device
 is ready to accept MQTT commands.
-```
-  
+```
\ No newline at end of file
diff --git a/proto/wippersnapper/docs/digitalio.md b/proto/wippersnapper/docs/digitalio.md
new file mode 100644
index 00000000..d2037505
--- /dev/null
+++ b/proto/wippersnapper/docs/digitalio.md
@@ -0,0 +1,23 @@
+
+# digitalio.proto
+
+  This file details the WipperSnapper messaging API for interfacing with digital I/O pins.
+
+## WipperSnapper Components
+
+The following WipperSnapper components utilize `servo.proto`:
+* [pin](https://github.com/adafruit/Wippersnapper_Components/tree/main/components/pin)
+
+
+## Sequence Diagrams
+
+### Add a Digital Pin
+
+```mermaid
+sequenceDiagram
+autonumber
+IO->>Device: DeviceToBroker
+Device->>App: DeviceToBroker
+App->>Encoder/Decoder: DeviceToBroker
+Encoder/Decoder->>Digital IO Class: DigitalIOAdd
+```
diff --git a/proto/wippersnapper/ds18x20/v1/ds18x20.md b/proto/wippersnapper/docs/ds18x20.md
similarity index 90%
rename from proto/wippersnapper/ds18x20/v1/ds18x20.md
rename to proto/wippersnapper/docs/ds18x20.md
index 7627f842..5cac421d 100644
--- a/proto/wippersnapper/ds18x20/v1/ds18x20.md
+++ b/proto/wippersnapper/docs/ds18x20.md
@@ -24,8 +24,8 @@ sequenceDiagram
 
 autonumber
 
-IO-->>Device: Ds18x20InitRequest
-Device->>IO: Ds18x20InitResponse
+IO-->>Device: Ds18x20Add
+Device->>IO: Ds18x20Added
 ```
 
 ### Delete: DS18x20
@@ -40,7 +40,7 @@ sequenceDiagram
 
 autonumber
 
-IO->>Device: Ds18x20DeInitRequest
+IO->>Device: Ds18x20Remove
 ```
 
 ### Sensor Event: DS18x20
@@ -53,6 +53,6 @@ sequenceDiagram
 
 autonumber
 
-Device->>IO: Ds18x20DeviceEvent
+Device->>IO: Ds18x20Event
 Note over Device,IO: This message contains i2c.SensorEvent,
which contains the sensor's value unit. 
 Since a device may have >1DS18X20 
sensor, it also contains the sensor's pin 
for addressing
 ```
diff --git a/proto/wippersnapper/i2c/v1/i2c.md b/proto/wippersnapper/docs/i2c.md
similarity index 76%
rename from proto/wippersnapper/i2c/v1/i2c.md
rename to proto/wippersnapper/docs/i2c.md
index 8b11e123..4d65c7b9 100644
--- a/proto/wippersnapper/i2c/v1/i2c.md
+++ b/proto/wippersnapper/docs/i2c.md
@@ -40,15 +40,15 @@ Device->>IO: I2CBusScanResponse
 sequenceDiagram
 autonumber
 
-IO->>Device: I2CDeviceInitRequest
(contains I2CBusInitRequest)
+IO->>Device: I2CInit
(contains I2CBusInitRequest)
 Device->>App: I2CBusInitRequest
-App->>Device: BusResponse
-Device->>App: I2CDeviceInitRequest
-App->>I2C Class: i2c_device_address, i2c_device_name,
i2c_device_properties
-Note over App,I2C Class: At this point, the I2C sensor is configured 
and ready to send data to Adafruit IO.
-I2C Class->>App: I2CDeviceInitResponse
-App->>Device: I2CDeviceInitResponse
-Device->>IO: I2CDeviceInitResponse
+App->>I2C Class: I2CBusInitRequest
+Device->>App: I2CInit
+App->>I2C Class: i2c_device_address, i2c_device_name,
i2c_device_period, i2c_device_sensor_types
+Note over App,I2C Class: At this point, the I2C sensor is configured 
and ready to send data back to Adafruit IO.
+I2C Class->>App: I2CAdded
+App->>Device: I2CAdded
+Device->>IO: I2CAdded
 ```
 
 ### Update an existing I2C device
@@ -57,13 +57,12 @@ Device->>IO: I2CDeviceInitResponse
 sequenceDiagram
 autonumber
 
-IO->>Device: I2CDeviceUpdateRequest
-Device->>App: I2CDeviceUpdateRequest
-App->>I2C Class: I2CDeviceUpdateRequest
-Note over App,I2C Class: Update the properties of the "sub-sensors" 
 specified within i2c_device_properties array.
-I2C Class->>App: I2CDeviceUpdateResponse
-App->>Device: I2CDeviceUpdateResponse
-Device->>IO: I2CDeviceUpdateResponse
+IO->>Device: I2CInit
+Device->>App: I2CInit
+App->>I2C Class: I2CInit
+I2C Class->>App: I2CUpdateResponse
+App->>Device: I2CUpdateResponse
+Device->>IO: I2CUpdateResponse
 ```
 
 ### Sending data from an I2C component
@@ -76,7 +75,7 @@ While the sequence diagram for this type of message looks simple, the process in
 sequenceDiagram
 autonumber
 
-Device->>IO: I2CDeviceEvent
+Device->>IO: I2CEvent
 ```
 
 
@@ -88,8 +87,8 @@ The process of deleting an I2C device is straightforward and only requires the d
 sequenceDiagram
 autonumber
 
-IO->>Device: I2CDeviceDeinitRequest
-Device->>IO: I2CDeviceDeinitResponse
+IO->>Device: I2CRemove
+Device->>IO: I2CRemoved
 ```
 
 
diff --git a/proto/wippersnapper/pixels/v1/pixels.md b/proto/wippersnapper/docs/pixels.md
similarity index 86%
rename from proto/wippersnapper/pixels/v1/pixels.md
rename to proto/wippersnapper/docs/pixels.md
index 8cf851b3..b2d3331c 100644
--- a/proto/wippersnapper/pixels/v1/pixels.md
+++ b/proto/wippersnapper/docs/pixels.md
@@ -15,9 +15,9 @@ The following component definitions reference `pixels.proto`:
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` according to form 
`pixels_pin_neopixel` according to form
 `pixels_pin_dotstar_data` is unused
`pixels_pin_dotstar_clock` is unused
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
 ```
 
@@ -26,7 +26,7 @@ Note over Device,IO: `is_success`, true if init'd OK
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsWriteRequest
+IO->>Device: PixelsWrite
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_pin_data` according to DB
`pixels_color` according to picker
 ```
 
@@ -35,11 +35,11 @@ Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pix
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsDeleteRequest
+IO->>Device: PixelsRemove
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
pixels_pin_data according to DB
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` of 0
`pixels_pin_neopixel` according to form
 `pixels_pin_dotstar_data` is unused
`pixels_pin_dotstar_clock` is unused
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
 ```
 
@@ -48,7 +48,7 @@ Note over Device,IO: `is_success`, true if init'd OK
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsDeleteRequest
+IO->>Device: PixelsRemove
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_pin_data` according to DB
 ```
 
@@ -56,11 +56,11 @@ Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pix
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` according to form 
`pixels_pin_neopixel` according to form
 `pixels_pin_dotstar_data` is unused
`pixels_pin_dotstar_clock` is unused
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
-IO->>Device: PixelsWriteRequest
+IO->>Device: PixelsWrite
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_pin_data` according to DB
`pixels_color` according to feed's last_value
 ```
 
@@ -70,9 +70,9 @@ Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pix
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` according to form 
`pixels_pin_neopixel` unused
 `pixels_pin_dotstar_data` according to form
`pixels_pin_dotstar_clock` according to form
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
 ```
 
@@ -81,7 +81,7 @@ Note over Device,IO: `is_success`, true if init'd OK
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsWriteRequest
+IO->>Device: PixelsWrite
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixels_pin_data` according to DB
`pixels_color` according to picker
 ```
 
@@ -90,11 +90,11 @@ Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixe
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsDeleteRequest
+IO->>Device: PixelsRemove
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_DOTSTAR
pixels_pin_data according to DB
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` of 0
`pixels_pin_neopixel` is unused
 `pixels_pin_dotstar_data` according to form
`pixels_pin_dotstar_clock` according to form
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
 ```
 
@@ -103,7 +103,7 @@ Note over Device,IO: `is_success`, true if init'd OK
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PixelsDeleteRequest
+IO->>Device: PixelsRemove
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixels_pin_data` according to DB
 ```
 
@@ -111,10 +111,10 @@ Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixe
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: PixelsCreateRequest
+IO-->>Device: PixelsAdd
 Note over IO, Device: Contains:
 `pixels_type` of PIXELS_TYPE_DOTSTAR
`pixels_num` according to form
`pixels_ordering` according to form
 `pixels_brightness` according to form 
`pixels_pin_neopixel` is unused
 `pixels_pin_dotstar_data` according to form
`pixels_pin_dotstar_clock` according to form
-Device->>IO: PixelsCreateResponse
+Device->>IO: PixelsAdded
 Note over Device,IO: `is_success`, true if init'd OK
-IO->>Device: PixelsWriteRequest
+IO->>Device: PixelsWrite
 Note over IO, Device: Contains
 `pixels_type` of PIXELS_TYPE_NEOPIXEL
`pixels_pin_data` according to DB
`pixels_color` according to feed's last_value
 ```
diff --git a/proto/wippersnapper/pwm/v1/pwm.md b/proto/wippersnapper/docs/pwm.md
similarity index 73%
rename from proto/wippersnapper/pwm/v1/pwm.md
rename to proto/wippersnapper/docs/pwm.md
index 206de648..3d869e6c 100644
--- a/proto/wippersnapper/pwm/v1/pwm.md
+++ b/proto/wippersnapper/docs/pwm.md
@@ -22,11 +22,11 @@ The following WipperSnapper components utilize `pwm.proto`:
 sequenceDiagram
 autonumber
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to form
`frequency` of 5000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 ```
 
 
@@ -34,7 +34,7 @@ Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachReques
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMWriteDutyCycleRequest
+IO->>Device: PWMWriteDutyCycle
 Note over IO, Device: The duty_cycle (0->255) from the 
IO slider widget is written to the `pin`.
 ```
 
@@ -42,21 +42,21 @@ Note over IO, Device: The duty_cycle (0->255) from the 
IO slider widget is w
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMDetachRequest
+IO->>Device: PWMRemove
 Note over IO, Device: Detaches GPIO pin from a timer
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to form
`frequency` of 5000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 ```
 
 ### Delete: Dimmable LED
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMDetachRequest
+IO->>Device: PWMRemove
 Note over IO, Device: Detaches GPIO pin from a timer
 ```
 
@@ -65,13 +65,13 @@ Note over IO, Device: Detaches GPIO pin from a timer
 sequenceDiagram
 autonumber
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to DB
`frequency` of 5000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 
-IO->>Device: PWMWriteDutyCycleRequest
+IO->>Device: PWMWriteDutyCycle
 Note over IO, Device: duty_cycle (0->255) from IO feed's last_value.
 ```
 
@@ -81,11 +81,11 @@ Note over IO, Device: duty_cycle (0->255) from IO feed's last_value.
 sequenceDiagram
 autonumber
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to form
`frequency` of 1000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 ```
 
 
@@ -93,7 +93,7 @@ Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachReques
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMWriteFrequencyRequest
+IO->>Device: PWMWriteFrequency
 Note over IO, Device: Any frequency > 0Hz to play a tone, 0Hz to turn off
 ```
 
@@ -101,21 +101,21 @@ Note over IO, Device: Any frequency > 0Hz to play a tone, 0Hz to turn off
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMDetachRequest
+IO->>Device: PWMRemove
 Note over IO, Device: Detaches GPIO pin from a timer
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to form
`frequency` of 1000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 ```
 
 ### Delete: Piezo Buzzer
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: PWMDetachRequest
+IO->>Device: PWMRemove
 Note over IO, Device: Detaches GPIO pin from a timer
 ```
 
@@ -124,12 +124,12 @@ Note over IO, Device: Detaches GPIO pin from a timer
 sequenceDiagram
 autonumber
 
-IO-->>Device: PWMAttachRequest
+IO-->>Device: PWMAdd
 Note over IO, Device: Contains:
 `pin` according to DB
`frequency` of 1000Hz
 `resolution` of 12 bits
 
-Device->>IO: PWMAttachResponse
-Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAttachRequest msg. 
`did_attach` True if attached.
+Device->>IO: PWMAdded
+Note over IO, Device: Contains:
 `pin` from 
corresponding PWMAdd msg. 
`did_attach` True if attached.
 
-IO->>Device: PWMWriteFrequencyRequest
+IO->>Device: PWMWriteFrequency
 Note over IO, Device: frequency, in Hz, from IO feed's last_value.
 ```
diff --git a/proto/wippersnapper/servo/v1/servo.md b/proto/wippersnapper/docs/servo.md
similarity index 84%
rename from proto/wippersnapper/servo/v1/servo.md
rename to proto/wippersnapper/docs/servo.md
index a828f027..73e53480 100644
--- a/proto/wippersnapper/servo/v1/servo.md
+++ b/proto/wippersnapper/docs/servo.md
@@ -1,29 +1,24 @@
-
 # servo.proto
 
   This file details the WipperSnapper messaging API for interfacing with servo output components.
 
 ## WipperSnapper Components
 
-  
-
 The following WipperSnapper components utilize `servo.proto`:
 * [Generic Servo](https://github.com/adafruit/Wippersnapper_Components/tree/main/components/servo/servo)
   
 
 ## Sequence Diagrams
 
-  
-
 ### Create: Servo
 
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: ServoAttachRequest
+IO-->>Device: ServoAdd
 Note over IO, Device: Contains:
 `servo_pin` from form
`servo_freq` of 50Hz
 `min_pulse_width` from form 
 `max_pulse_width` from form
 
-Device->>IO: ServoAttachResponse
+Device->>IO: ServoAdded
 Note over IO, Device: Contains: Success code and servo's pin
 ```
 
@@ -33,7 +28,7 @@ Note over IO, Device: Contains: Success code and servo's pin
 sequenceDiagram
 autonumber
 
-IO->>Device: ServoWriteRequest
+IO->>Device: ServoWrite
 Note over IO, Device: Position is sent from Adafruit IO as a pulse width
 between 500uS and 2500uS. 
 The client application must convert pulse width to duty cycle
with fixed freq of 50Hz prior to writing to the servo pin.
 ```
 
@@ -45,11 +40,11 @@ Note over IO, Device: Position is sent from Adafruit IO as a pulse width
 bet
 sequenceDiagram
 autonumber
 
-IO->>Device: ServoDetachRequest
+IO->>Device: ServoRemove
 Note over IO, Device: Deinits servo object, releases gpio pin
-IO-->>Device: ServoAttachRequest
+IO-->>Device: ServoAdd
 Note over IO, Device: Contains:
 `servo_pin` from form
`servo_freq` of 50Hz
 `min_pulse_width` from form 
 `max_pulse_width` from form
-Device->>IO: ServoAttachResponse
+Device->>IO: ServoAdded
 Note over IO, Device: Contains: Success code and servo's pin
 ```
 
@@ -60,7 +55,7 @@ Note over IO, Device: Contains: Success code and servo's pin
 ```mermaid
 sequenceDiagram
 autonumber
-IO->>Device: ServoDetachRequest
+IO->>Device: ServoRemove
 Note over IO, Device: Contains:
 `servo_pin` from DB.
 ```
 
@@ -71,9 +66,9 @@ Note over IO, Device: Contains:
 `servo_pin` from DB.
 ```mermaid
 sequenceDiagram
 autonumber
-IO-->>Device: ServoAttachRequest
+IO-->>Device: ServoAdd
 Note over IO, Device: Contains:
 `servo_pin` from form
`servo_freq` of 50Hz
 `min_pulse_width` from form 
 `max_pulse_width` from form
 
-Device->>IO: ServoAttachResponse
+Device->>IO: ServoAdded
 Note over IO, Device: Contains: Success code and servo's pin
 ```
\ No newline at end of file
diff --git a/proto/wippersnapper/docs/signal.md b/proto/wippersnapper/docs/signal.md
new file mode 100644
index 00000000..9f3296ae
--- /dev/null
+++ b/proto/wippersnapper/docs/signal.md
@@ -0,0 +1,44 @@
+# signal.proto
+
+This file details the `signal.proto` message used to communicate between WipperSnapper clients. 
+
+## Message Format
+The signal file contains two messages. Both messages contain `oneof` "payload" message, which is a union of all the possible messages that can be sent from a device to the broker (and visa-versa)
+
+### Payload Message Naming Conventions
+
+Message fields within `signal.proto` generally follow the naming convention:
+* `_add`: Message contains a command payload for configuring and adding a component to a device.
+  * `_added`: Message contains a response payload from an `_add` message 
+* `_remove`: Message contains a payload for releasing a component's resources and "removing" it from the device.
+  * `_removed`: Message contains a response payload from an `_remove` message
+* `_write`: Message contains a payload for transmitting or "writing" data to a component connected to a device.
+  * Within `pwm.proto`, there are multiple types of `_write` such as `_write_duty`, `_write_freq`, etc...
+*  `_event`: Message is a payload containing sensor data and metadata. Data may be "packed", containing multiple sensor events.
+
+Some message fields do not follow this naming convention because their API is more involved than the general fields above:
+* `_scan`: Message contains a command payload for performing a scan of I2C components connected to a device
+* `_response`: Message contains a response payload for an action not compatible with any of the above field names.
+
+
+## Sequence Diagrams
+
+### High-Level Operation
+
+When a message is sent from a broker to a device, the `BrokerToDevice` message is utilized. When a message is sent from a device to a broker, the `DeviceToBroker` message is sent.
+
+```mermaid
+sequenceDiagram
+autonumber
+
+IO Broker->>Device Client: BrokerToDevice
+Note over IO Broker,Device Client: /:username/wprsnpr/:clientId
+Device Client->>App: BrokerToDevice
+App->>(nanopb) Encoder/Decoder: BrokerToDevice
+(nanopb) Encoder/Decoder->>Component Class: ServoAdd 
+Component Class->>(nanopb) Encoder/Decoder: Result of ServoAdd, servo_added
+(nanopb) Encoder/Decoder->>App: DeviceToBroker
+App->>Device Client: DeviceToBroker
+Device Client->>IO Broker: DeviceToBroker
+Note over IO Broker,Device Client: /:username/wprsnpr/:clientId
+```
\ No newline at end of file
diff --git a/proto/wippersnapper/uart/v1/uart.md b/proto/wippersnapper/docs/uart.md
similarity index 81%
rename from proto/wippersnapper/uart/v1/uart.md
rename to proto/wippersnapper/docs/uart.md
index 2e697d6e..4c6254f5 100644
--- a/proto/wippersnapper/uart/v1/uart.md
+++ b/proto/wippersnapper/docs/uart.md
@@ -19,9 +19,9 @@ The following WipperSnapper components utilize `uart.proto`:
 sequenceDiagram
 autonumber
 
-IO-->>WS Device: UARTDeviceAttachRequest
+IO-->>WS Device: UARTAdd
 
-WS Device-->>WS Device Decoder: UARTDeviceAttachRequest
+WS Device-->>WS Device Decoder: UARTAdd
 
 WS Device Decoder-->>WS Device UART: UARTBusData
 Note over WS Device Decoder, WS Device UART: Initialize UART bus using configuration (UARTBusData).
@@ -29,9 +29,9 @@ Note over WS Device Decoder, WS Device UART: Initialize UART bus using configura
 WS Device Decoder-->>WS Device UART: device_id, polling_interval
 Note over WS Device Decoder, WS Device UART: Initialize UART device on the UART bus and associate it with a driver and a polling period.
 
-WS Device UART-->>WS Device: UARTDeviceAttachResponse
+WS Device UART-->>WS Device: UARTAdded
 
-WS Device-->>IO: UARTDeviceAttachResponse
+WS Device-->>IO: UARTAdded
 Note over WS Device, IO: Returns true if successful, False if not.
 ```
 
@@ -41,7 +41,7 @@ Note over WS Device, IO: Returns true if successful, False if not.
 sequenceDiagram
 autonumber
 
-Device-->>IO Broker: UARTDeviceEvent
+Device-->>IO Broker: UARTEvent
 IO Broker -->>IO Backend: Parse out repeated sensor_event into apropriate feeds for device_id
 ```
 
@@ -51,7 +51,7 @@ IO Broker -->>IO Backend: Parse out repeated sensor_event into apropriate feeds
 sequenceDiagram
 autonumber
 
-IO Broker --> Device: UARTDeviceDetachRequest
+IO Broker --> Device: UARTRemove
 Device --> UART Class: Detach UART device from UART bus according to device_id.
 ```
 
diff --git a/proto/wippersnapper/ds18x20.options b/proto/wippersnapper/ds18x20.options
new file mode 100644
index 00000000..70d9bfdf
--- /dev/null
+++ b/proto/wippersnapper/ds18x20.options
@@ -0,0 +1,7 @@
+# ds18x20.options
+wippersnapper.ds18x20.Ds18x20Add.onewire_pin      max_size:5
+wippersnapper.ds18x20.Ds18x20Added.onewire_pin    max_size:5
+wippersnapper.ds18x20.Ds18x20Remove.onewire_pin   max_size:5
+wippersnapper.ds18x20.Ds18x20Event.onewire_pin    max_size:5
+wippersnapper.ds18x20.Ds18x20Add.sensor_types     max_count:2
+wippersnapper.ds18x20.Ds18x20Event.sensor_events  max_count:2
\ No newline at end of file
diff --git a/proto/wippersnapper/ds18x20.proto b/proto/wippersnapper/ds18x20.proto
new file mode 100644
index 00000000..4f35d662
--- /dev/null
+++ b/proto/wippersnapper/ds18x20.proto
@@ -0,0 +1,43 @@
+// SPDX-FileCopyrightText: 2023 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+// WipperSnapper's Protobuf communication API for DS18X20 Maxim Temperature ICs
+syntax = "proto3";
+package wippersnapper.ds18x20;
+import "sensor.proto";
+
+/**
+* Ds18x20Add represents a  to initialize
+* a DS18X20 Maxim temperature sensor, from the broker.
+* NOTE: This API currently only supports ONE device per OneWire bus.
+*/
+message Ds18x20Add {
+  string onewire_pin                                      = 1; /** The desired pin to use as a OneWire bus. */
+  int32  sensor_resolution                                = 2; /** The desired sensor resolution (9, 10, 11, or 12 bits). */
+  float period                                            = 3; /** The desired period to read the sensor, in seconds. */
+  repeated wippersnapper.sensor.SensorType  sensor_types  = 4; /** SI types used by the DS18x20 sensor. */
+}
+
+/**
+* Ds18x20AddDs18x20AddedResponse represents a device's response
+* to a Ds18x20Add message.
+*/
+message Ds18x20Added {
+  bool is_initialized  = 1; /** True if the 1-wire bus has been initialized successfully, False otherwise. */
+  string onewire_pin   = 2; /** The pin being used as a OneWire bus. */
+}
+
+/**
+* Ds18x20Remove represents a  to de-initialize a DS18X20
+* Maxim temperature sensor, from the broker.
+*/
+message Ds18x20Remove {
+  string onewire_pin  = 1; /** The desired onewire bus to de-initialize a DS18x sensor on and release. */
+}
+
+/**
+* Ds18x20Event event represents data from **one** DS18X20 sensor.
+*/
+message Ds18x20Event {
+  string onewire_pin                                         = 1; /** The desired pin to use as a OneWire bus. */
+  repeated wippersnapper.sensor.SensorEvent sensor_events = 2; /** The DS18X20's SensorEvent. */
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/ds18x20/v1/ds18x20.proto b/proto/wippersnapper/ds18x20/v1/ds18x20.proto
deleted file mode 100644
index 76e65a59..00000000
--- a/proto/wippersnapper/ds18x20/v1/ds18x20.proto
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-// WipperSnapper's Protobuf communication API for DS18X20 Maxim Temperature ICs
-
-syntax = "proto3";
-
-package wippersnapper.ds18x20.v1;
-import "nanopb/nanopb.proto";
-import "wippersnapper/i2c/v1/i2c.proto";
-
-/**
-* Ds18x20InitRequest represents a request to initialize
-* a DS18X20 Maxim temperature sensor, from the broker.
-* NOTE: This API only supports ONE DS18X20 device PER OneWire bus.
-*/
-message Ds18x20InitRequest {
-  string onewire_pin                                                             = 1 [(nanopb).max_size = 5]; /** The desired pin to use as a OneWire bus. */
-  int32  sensor_resolution                                                       = 2; /** The desired sensor resolution (9, 10, 11, or 12 bits). */
-  repeated wippersnapper.i2c.v1.I2CDeviceSensorProperties i2c_device_properties  = 3[(nanopb).max_count = 2]; /** Properties for the DS18x20 sensor. */
-}
-
-/**
-* ds18x20InitDs18x20InitResponseResponse represents a device's response
-* to a ds18x20InitRequest message.
-*/
-message Ds18x20InitResponse {
-  bool is_initialized  = 1; /** True if the 1-wire bus has been initialized successfully, False otherwise. */
-  string onewire_pin   = 2 [(nanopb).max_size = 5]; /** The pin being used as a OneWire bus. */
-}
-
-/**
-* Ds18x20DeInitRequest represents a request to de-initialize a DS18X20
-* Maxim temperature sensor, from the broker.
-*/
-message Ds18x20DeInitRequest {
-  string onewire_pin  = 1 [(nanopb).max_size = 5]; /** The desired onewire bus to de-initialize a DS18x sensor on and release. */
-}
-
-/**
-* Ds18x20DeviceEvent event represents data from **one** DS18X20 sensor.
-*/
-message Ds18x20DeviceEvent {
-  string onewire_pin                                      = 1 [(nanopb).max_size = 5]; /** The desired pin to use as a OneWire bus. */
-  repeated wippersnapper.i2c.v1.SensorEvent sensor_event  = 2 [(nanopb).max_count = 2]; /** The DS18X20's SensorEvent. */
-}
\ No newline at end of file
diff --git a/proto/wippersnapper/error.proto b/proto/wippersnapper/error.proto
new file mode 100644
index 00000000..9cb03ed9
--- /dev/null
+++ b/proto/wippersnapper/error.proto
@@ -0,0 +1,19 @@
+// SPDX-FileCopyrightText: 2023 Brent Rubell, Loren Norman, Tyeth Gundry for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.error;
+import "nanopb.proto";
+
+// TODO: Add error handling on the device-side to parse error
+// message and either delay on throttle, or disconnect + exponential retry+jitter
+// on ban time
+
+// Jitter should be unique to each device, possibly derived from its clientID
+
+message Error {
+  option (nanopb_msgopt).submsg_callback = true; // TODO: Do we need this option field?
+  oneof payload {
+    int32 ban_time = 1;      // Account ban time, in seconds
+    int32 throttle_time = 2; // Account time, in seconds
+  }
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/gps.options b/proto/wippersnapper/gps.options
new file mode 100644
index 00000000..8221c0bb
--- /dev/null
+++ b/proto/wippersnapper/gps.options
@@ -0,0 +1,21 @@
+# gps.options
+wippersnapper.gps.GPSConfig.commands_pmtks   max_size:90
+wippersnapper.gps.GPSConfig.commands_pmtks   max_count:8
+wippersnapper.gps.GPSConfig.commands_ubxes   max_size:128
+wippersnapper.gps.GPSConfig.commands_ubxes   max_count:8
+wippersnapper.gps.GPSRMCResponse.fix_status  max_size:2
+wippersnapper.gps.GPSRMCResponse.lat         max_size:12
+wippersnapper.gps.GPSRMCResponse.lat_dir     max_size:6
+wippersnapper.gps.GPSRMCResponse.lon         max_size:12
+wippersnapper.gps.GPSRMCResponse.lon_dir     max_size:6
+wippersnapper.gps.GPSRMCResponse.speed       max_size:8
+wippersnapper.gps.GPSRMCResponse.angle       max_size:7
+wippersnapper.gps.GPGGAResponse.lat          max_size:12
+wippersnapper.gps.GPGGAResponse.lat_dir      max_size:6
+wippersnapper.gps.GPGGAResponse.lon          max_size:12
+wippersnapper.gps.GPGGAResponse.lon_dir      max_size:6
+wippersnapper.gps.GPGGAResponse.hdop         max_size:6
+wippersnapper.gps.GPGGAResponse.altitude     max_size:8
+wippersnapper.gps.GPGGAResponse.geoid_height max_size:10
+wippersnapper.gps.GPSEvent.rmc_responses     max_count:10
+wippersnapper.gps.GPSEvent.gga_responses     max_count:10
\ No newline at end of file
diff --git a/proto/wippersnapper/gps.proto b/proto/wippersnapper/gps.proto
new file mode 100644
index 00000000..a1ff04a4
--- /dev/null
+++ b/proto/wippersnapper/gps.proto
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2025 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.gps;
+
+/**
+ * GPSConfig represents a message containing configuration data to set up and configure a GPS.
+ * Since GPS devices can output lots of data, this message allows users to select which data they want to receive
+ * and a resulting command string to initialize the GPS device with the selected options will be generated.
+ */
+message GPSConfig {
+  // NOTE:  Baud rate is not included here as it is included in the UartAdd->UartSerialConfig message.
+  repeated string commands_pmtks = 1; /** List of PMTK commands in string format. **/
+  repeated bytes commands_ubxes  = 2; /** List of UBX commands in bytes format. **/
+  int32 period                   = 3; /** Desired period to poll the GPS module, in milliseconds */
+}
+
+/** GPSDateTime represents the date and time information from a GPRMC/GPGGA string **/
+message GPSDateTime {
+  int32 hour = 1; /** Hour of the day (0-23) **/
+  int32 minute = 2; /** Minute of the hour (0-59) **/
+  int32 seconds = 3; /** Seconds of the minute (0-59) **/
+  int32 milliseconds = 4; /** Milliseconds (0-999) **/
+  int32 day = 5; /** Day of the month (1-31) **/
+  int32 month = 6; /** Month of the year (1-12) **/
+  int32 year = 7; /** Year (e.g., 2023) **/
+}
+
+/** GPSRMCResponse represents the response from a GPS RMC (Recommended Minimum Specific GPS/Transit Data) message. **/
+message GPSRMCResponse {
+  GPSDateTime datetime = 1; /** Date and time of the GPS data **/
+  string fix_status = 2; /** Fix status: 'A' for active, 'V' for void **/
+  string lat = 3; /** Latitude in decimal degrees **/
+  string lat_dir = 4; /** Latitude direction: 'North' or 'South' **/
+  string lon = 5; /** Longitude in decimal degrees **/
+  string lon_dir = 6; /** Longitude direction: 'East' or 'West' **/
+  string speed = 7; /** Speed, in knots **/
+  string angle = 8; /** Course/heading angle, in degrees **/
+}
+
+/** GPGGAResponse represents the response from a GPS GGA (Global Positioning System Fix Data) message. **/
+message GPGGAResponse {
+  GPSDateTime datetime = 1; /** Date and time of the GPS data **/
+  string lat = 2; /** Latitude in decimal degrees **/
+  string lat_dir = 3; /** Latitude direction: 'N' or 'S' **/
+  string lon = 4; /** Longitude in decimal degrees **/
+  string lon_dir = 5; /** Longitude direction: 'E' or 'W' **/
+  int32 fix_quality = 6; /** Quality of the GPS fix (0-3) **/
+  int32 num_satellites = 7; /** Number of satellites in use **/
+  string hdop = 8; /** HDOP- horizontal dilution of precision **/
+  string altitude = 9; /** Altitude in meters above MSL **/
+  string geoid_height = 10; /** Diff between geoid height and WGS84 height **/
+}
+
+/** GPSEvent represents a collection of GPS event responses, including RMC and GGA data. **/
+message GPSEvent {
+  repeated GPSRMCResponse rmc_responses = 1; /** List of RMC responses **/
+  repeated GPGGAResponse gga_responses  = 2; /** List of GGA responses **/
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/i2c.options b/proto/wippersnapper/i2c.options
new file mode 100644
index 00000000..21a128de
--- /dev/null
+++ b/proto/wippersnapper/i2c.options
@@ -0,0 +1,10 @@
+# i2c.options
+wippersnapper.i2c.I2cBusScanned.i2c_bus_found_devices            max_count:120
+wippersnapper.i2c.I2cDeviceAddOrReplace.i2c_device_name          max_size:15
+wippersnapper.i2c.I2cDeviceAddOrReplace.i2c_device_sensor_types  max_count:15
+wippersnapper.i2c.I2cDeviceEvent.i2c_device_events               max_count:15
+wippersnapper.i2c.I2cDeviceDescriptor.i2c_bus_sda                max_size:15
+wippersnapper.i2c.I2cDeviceDescriptor.i2c_bus_scl                max_size:15
+wippersnapper.i2c.I2cBusScan.i2c_mux_descriptors                 max_count: 2
+wippersnapper.i2c.I2cBusDescriptor.i2c_bus_sda                   max_size:15
+wippersnapper.i2c.I2cBusDescriptor.i2c_bus_scl                   max_size:15
\ No newline at end of file
diff --git a/proto/wippersnapper/i2c.proto b/proto/wippersnapper/i2c.proto
new file mode 100644
index 00000000..7fb1f818
--- /dev/null
+++ b/proto/wippersnapper/i2c.proto
@@ -0,0 +1,135 @@
+// SPDX-FileCopyrightText: 2021-2024 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.i2c;
+import "gps.proto";
+import "i2c_output.proto";
+import "sensor.proto";
+
+/**
+* I2cBusStatus represents the status of a board's I2C bus
+*/
+enum I2cBusStatus {
+  I2C_BUS_STATUS_UNSPECIFIED   = 0; /** Unspecified error occurred. **/
+  I2C_BUS_STATUS_SUCCESS       = 1; /** I2C bus successfully initialized. **/
+  I2C_BUS_STATUS_ERROR_HANG    = 2; /** I2C Bus hang, user should reset their board if this persists. **/
+  I2C_BUS_STATUS_ERROR_PULLUPS = 3; /** I2C bus failed to initialize - SDA or SCL needs a pull up. **/
+  I2C_BUS_STATUS_ERROR_WIRING  = 4; /** I2C bus failed to communicate - Please check your wiring. **/
+  I2C_BUS_STATUS_ERROR_INVALID_CHANNEL = 5; /** I2C MUX failed - Output channel must be within range 0-7. **/
+}
+
+/**
+* I2cDeviceStatus represents the state of an I2C device/peripheral
+*/
+enum I2cDeviceStatus {
+  I2C_DEVICE_STATUS_UNSPECIFIED             = 0; /** Unspecified error occurred. **/
+  I2C_DEVICE_STATUS_SUCCESS                 = 1; /** I2C device successfully initialized. **/
+  I2C_DEVICE_STATUS_FAIL_INIT               = 2; /** I2C device failed to initialize. **/
+  I2C_DEVICE_STATUS_FAIL_DEINIT             = 3; /** I2C device failed to deinitialize. **/
+  I2C_DEVICE_STATUS_FAIL_UNSUPPORTED_SENSOR = 4; /** WipperSnapper version is outdated and does not include this device. **/
+  I2C_DEVICE_STATUS_NOT_FOUND               = 5; /** I2C device not found on the bus. **/
+}
+
+/**
+* I2cDeviceDescriptor represents the I2c device's address and related metadata.
+*/
+message I2cDeviceDescriptor {
+  string i2c_bus_sda        = 1; /** Optional SDA pin for an alt. i2c bus.**/
+  string i2c_bus_scl        = 2; /** Optional SCL pin for an alt. i2c bus.**/
+  uint32 i2c_device_address = 3; /** I2C Device's Address. **/
+  uint32 i2c_mux_address    = 4; /** Optional I2C multiplexer address. **/
+  uint32 i2c_mux_channel    = 5; /** Optional I2C multiplexer channel. **/
+}
+
+/**
+* I2cBusDescriptor represents the I2c bus' SDA and SCL pins.
+*/
+message I2cBusDescriptor {
+  string i2c_bus_sda = 1; /** SDA pin for an i2c bus.**/
+  string i2c_bus_scl = 2; /** SCL pin for an i2c bus.**/
+}
+
+/**
+* I2cBusScan represents a command for a device to perform an i2c scan.
+*/
+message I2cBusScan {
+  bool scan_default_bus                         = 1; /** Default - Scan for i2c devices on the hardware's default I2C bus.**/
+  bool scan_alt_bus                             = 2; /** Optional - Scan for i2c devices on an alternative I2C bus.**/
+  I2cBusDescriptor i2c_alt_bus_descriptor       = 3; /** Optional - Metadata to optionally initialize (if not already init'd) an alt. i2c bus.**/
+  bool scan_default_bus_mux                     = 4; /** Optional - Scan for i2c devices on the default I2C bus with a multiplexer.**/
+  bool scan_alt_bus_mux                         = 5; /** Optional - Scan for i2c devices on an alternative I2C bus with a multiplexer.**/
+}
+
+/**
+* I2cBusScanned represents a list of I2c addresses
+* found on the bus after I2cScan has executed.
+*/
+message I2cBusScanned {
+  repeated I2cDeviceDescriptor i2c_bus_found_devices = 1; /** The 7-bit addresses of the I2c devices found on the bus, empty if not found. */
+  I2cBusStatus i2c_bus_status                        = 2; /** The I2c bus' status. **/
+}
+
+// DEVICE COMMANDS
+
+/**
+* I2cDeviceAddOrReplace is a message for initializing (or replacing/updating) an i2c device.
+*/
+message I2cDeviceAddOrReplace {
+  I2cDeviceDescriptor i2c_device_description                        = 1; /** The I2c device's address and metadata. */
+  string i2c_device_name                                            = 2; /** The I2c device's name, MUST MATCH the name on the JSON definition file on
+                                                                     https://github.com/adafruit/Wippersnapper_Components. */
+  float i2c_device_period                                           = 3; /** The desired period to update the I2c device's sensor(s), in seconds. */
+  repeated wippersnapper.sensor.SensorType i2c_device_sensor_types  = 4; /** SI Types for each sensor on the I2c device. */
+  bool is_persistent                                   = 5; /** Offline-Mode ONLY - True if the device exits in the config file, False otherwise. **/
+  bool is_output                                       = 6; /** Required by the device to determine if the component is an output.**/
+  wippersnapper.i2c_output.I2cOutputAdd i2c_output_add = 7; /** Optional - If the I2C device is an output device, fill this field. **/
+  bool is_gps                                          = 8; /** Required by the device to determine if the component is a GPS.**/
+  wippersnapper.gps.GPSConfig gps_config               = 9; /** Optional - If the I2C device is a GPS driver, fill this field with the GPS config. **/
+}
+
+/**
+* I2cDeviceAddedOrReplaced contains the response from a device after processing a I2cDeviceAddOrReplace message.
+*/
+message I2cDeviceAddedOrReplaced {
+  I2cDeviceDescriptor i2c_device_description = 1; /** The I2c device's address and metadata. */
+  I2cBusStatus i2c_bus_status                = 2; /** The I2c bus' status. **/
+  I2cDeviceStatus i2c_device_status          = 3; /** The I2c device's status. **/
+}
+
+/**
+* I2cDeviceRemove represents a request to de-init an i2c device.
+*/
+message I2cDeviceRemove {
+  I2cDeviceDescriptor i2c_device_description = 1; /** The I2c device's address and metadata. */
+  bool is_output_device                      = 2; /** Determines if the device is an output device.**/
+}
+
+/**
+* I2cDeviceRemoved represents a response to a I2cDeviceRemove message.
+*/
+message I2cDeviceRemoved {
+  I2cDeviceDescriptor i2c_device_description = 1; /** The I2c device's address and metadata. */
+  bool did_remove                            = 2; /** True if the I2C device was successfully removed from the controller, False otherwise. **/
+}
+
+/**
+* Each I2cDeviceEvent represents data from **one** I2c sensor.
+* NOTE: An I2cDeviceEvent can have multiple sensor events if
+* the I2c device contains > 1 sensor.
+*/
+message I2cDeviceEvent {
+  I2cDeviceDescriptor i2c_device_description                  = 1; /** The I2c device's address and metadata. */
+  repeated wippersnapper.sensor.SensorEvent i2c_device_events = 2; /** Required, but optionally repeated, SensorEvent from a sensor. */
+}
+
+/**
+* I2cDeviceOutputWrite represents a request to write to an I2C output device.
+*/
+message I2cDeviceOutputWrite {
+  I2cDeviceDescriptor i2c_device_description                     = 1; /** Required - The I2c device's address and metadata. */
+  oneof output_msg {
+    wippersnapper.i2c_output.LedBackpackWrite write_led_backpack = 2; /** Optional - If the I2C device is a LED backpack, fill this field. **/
+    wippersnapper.i2c_output.CharLCDWrite write_char_lcd         = 3; /** Optional - If the I2C device is a character LCD, fill this field. **/
+    wippersnapper.i2c_output.OLEDWrite write_oled                = 4; /** Optional - If the I2C device is an OLED display, fill this field. **/
+  }
+}
diff --git a/proto/wippersnapper/i2c/v1/i2c.proto b/proto/wippersnapper/i2c/v1/i2c.proto
deleted file mode 100644
index eb74fa4d..00000000
--- a/proto/wippersnapper/i2c/v1/i2c.proto
+++ /dev/null
@@ -1,304 +0,0 @@
-// SPDX-FileCopyrightText: 2021-2025 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.i2c.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* BusResponse represents the state of the I2C bus, from a device.
-*/
-enum BusResponse {
-  BUS_RESPONSE_UNSPECIFIED         = 0; /** Unspecified error occurred. **/
-  BUS_RESPONSE_SUCCESS             = 1; /** I2C bus successfully initialized. **/
-  BUS_RESPONSE_ERROR_HANG          = 2; /** I2C Bus hang, user should reset their board if this persists. **/
-  BUS_RESPONSE_ERROR_PULLUPS       = 3; /** I2C bus failed to initialize - SDA or SCL needs a pull up. **/
-  BUS_RESPONSE_ERROR_WIRING        = 4; /** I2C bus failed to communicate - Please check your wiring. **/
-  BUS_RESPONSE_UNSUPPORTED_SENSOR  = 5; /** WipperSnapper firmware is outdated and does not include the sensor type - Please update your WipperSnapper firmware. **/
-  BUS_RESPONSE_DEVICE_INIT_FAIL    = 6; /** I2C device failed to initialize. **/
-  BUS_RESPONSE_DEVICE_DEINIT_FAIL  = 7; /** I2C device failed to de-initialize. **/
-}
-
-/**
-* I2CBusInitRequest represents a request to
-* initialize the I2C bus from the broker.
-*/
-message I2CBusInitRequest {
-  int32  i2c_pin_scl     = 1; /** The desired I2C SCL pin. */
-  int32  i2c_pin_sda     = 2; /** The desired I2C SDA pin. */
-  uint32 i2c_frequency   = 3; /** The desired I2C SCL frequency, in Hz. Default is 100000Hz. */
-  int32  i2c_port_number = 4; /** The I2C port number. */
-}
-
-/**
-* I2CBusInitResponse represents a response to I2CBusInitRequest
-*/
-message I2CBusInitResponse {
-  bool is_initialized  = 1 [deprecated = true, (nanopb).type = FT_IGNORE]; /** True if the I2C port has been initialized successfully, False otherwise. */
-  BusResponse bus_response = 2; /** Whether the I2C bus initialized properly or failed. **/
-}
-
-/**
-* I2CBusSetFrequency represents a request to change the
-* I2C clock speed to a desired frequency, in Hz.
-*/
-message I2CBusSetFrequency {
-  uint32 frequency = 1; /** The desired I2C SCL frequency, in Hz. */
-  int32 bus_id     = 2; /** An optional I2C bus identifier, if multiple exist. */
-}
-
-/**
-* I2CBusScanRequest represents the parameters required to execute
-* a device's I2C scan.
-*/
-message I2CBusScanRequest {
-  int32 i2c_port_number              = 1; /** The desired I2C port to scan. */
-  I2CBusInitRequest bus_init_request = 2; /** The I2C bus initialization request. */
-}
-
-/**
-* I2CBusScanResponse represents a list of I2C addresses
-* found on the bus after I2CBusScanRequest has executed.
-*/
-message I2CBusScanResponse {
-  repeated uint32 addresses_found  = 1 [packed=true, (nanopb).max_count = 120]; /** The 7-bit addresses of the I2C devices found on the bus, empty if not found. */
-  BusResponse bus_response         = 2; /** The I2C bus' status. **/
-}
-
-/**
-* I2CDeviceSensorProperties contains
-* the properties of an I2C device's sensor such as
-* its type and period.
-*/
-message I2CDeviceSensorProperties {
-  SensorType sensor_type = 1;
-  uint32 sensor_period   = 2;
-}
-
-
-/**
-* Represents a list of I2CDeviceInitRequest messages.
-*/
-message I2CDeviceInitRequests {
-  repeated I2CDeviceInitRequest list = 1;
-}
-
-/**
-* I2CDeviceInitRequest is a wrapper message for
-* an I2C device initialization request.
-*/
-message I2CDeviceInitRequest {
-  int32  i2c_port_number                                    = 1; /** The desired I2C port to initialize an I2C device on. */
-  I2CBusInitRequest i2c_bus_init_req                        = 2; /** An I2C bus initialization request. */
-  uint32 i2c_device_address                                 = 3; /** The 7-bit I2C address of the device on the bus. */
-  string i2c_device_name                                    = 4[(nanopb).max_size = 256]; /** The I2C device's name, MUST MATCH the name on the JSON definition file on https://github.com/adafruit/Wippersnapper_Components. */
-  repeated I2CDeviceSensorProperties i2c_device_properties  = 5[(nanopb).max_count = 15]; /** Properties of each sensor on the I2C device. */
-  bool is_output_device                                     = 6; /** True if the I2C device is an I2C output device, False otherwise (default). */
-  I2COutputAdd i2c_output_add                               = 7; /** The configuration for an I2C output device. */
-}
-
-/**
-* I2CDeviceInitResponse contains the response from a
-* device after processing a I2CDeviceInitRequest message.
-*/
-message I2CDeviceInitResponse {
-  bool is_success             = 1 [deprecated = true, (nanopb).type = FT_IGNORE]; /** !!DEPRECATED!! True if i2c device initialized successfully, false otherwise. */
-  uint32 i2c_device_address   = 2; /** The 7-bit I2C address of the device on the bus. */
-  BusResponse bus_response    = 3; /** The I2C bus' status. **/
-}
-
-/**
-* I2CDeviceUpdateRequest is a wrapper message which
-* contains a message to update a specific device's properties.
-*/
-message I2CDeviceUpdateRequest {
-  int32  i2c_port_number                                    = 1; /** The desired I2C port. */
-  uint32 i2c_device_address                                 = 2; /** The 7-bit I2C address of the device on the bus. */
-  string i2c_device_name                                    = 3[(nanopb).max_size = 256]; /** The I2C device's name, MUST MATCH the name on the JSON file. */
-  repeated I2CDeviceSensorProperties i2c_device_properties  = 4[(nanopb).max_count = 15]; /** Properties for the I2C device's sensors. */
-}
-
-/**
-* I2CDeviceUpdateResponse represents if an I2C device's
-* sensor(s) is/are successfully updated.
-*/
-message I2CDeviceUpdateResponse {
-  uint32 i2c_device_address   = 1; /** The 7-bit I2C address of the device which was updated. */
-  bool is_success             = 2 [deprecated = true, (nanopb).type = FT_IGNORE]; /** !!DEPRECATED!! True if the update request succeeded, False otherwise. */
-  BusResponse bus_response    = 3; /** The I2C bus' status. **/
-}
-
-/**
-* I2CDeviceDeinitRequest is a wrapper message containing
-* a deinitialization request for a specific i2c device.
-*/
-message I2CDeviceDeinitRequest {
-  int32  i2c_port_number        = 1; /** The desired I2C port to de-initialize an I2C device on. */
-  uint32 i2c_device_address     = 2; /** The 7-bit I2C address of the device on the bus. */
-}
-
-/**
-* I2CDeviceDeinitResponse represents if an I2C device's
-* sensor(s) is/are successfully de-initialized.
-*/
-message I2CDeviceDeinitResponse {
-  bool is_success           = 1 [deprecated = true, (nanopb).type = FT_IGNORE]; /** True if the deinitialization request succeeded, False otherwise. */
-  uint32 i2c_device_address = 2; /** The 7-bit I2C address of the device which was initialized. */
-  BusResponse bus_response  = 3; /** The I2C bus' status. **/
-}
-
-/** Adafruit Unified Sensor Library Messages. */
-
-/**
-* SensorType allows us determine what types of units the sensor uses, etc.
-*/
-enum SensorType {
-  SENSOR_TYPE_UNSPECIFIED                    = 0;  /** Sensor value type which is not defined by this list, "Raw Value: {value}". */
-  SENSOR_TYPE_ACCELEROMETER                  = 1;  /** Acceleration, in meter per second per second, "{value}m/s/s". */
-  SENSOR_TYPE_MAGNETIC_FIELD                 = 2;  /** Magnetic field strength, in micro-Tesla, "{value}µT". */
-  SENSOR_TYPE_ORIENTATION                    = 3;  /** Orientation angle, in degrees, "{value}°". */
-  SENSOR_TYPE_GYROSCOPE                      = 4;  /** Angular rate, in radians per second, "{value}rad/s". */
-  SENSOR_TYPE_LIGHT                          = 5;  /** Light-level, non-unit-specific (For a unit-specific measurement, see: Lux), , "Raw Value: {value}". */
-  SENSOR_TYPE_PRESSURE                       = 6;  /** Pressure, in hectopascal, , "{value}hPa". */
-  SENSOR_TYPE_PROXIMITY                      = 8;  /** Distance from an object to a sensor, non-unit-specific, "Raw Value: {value}". */
-  SENSOR_TYPE_GRAVITY                        = 9;  /** Metres per second squared, "{value}m/s^2". */
-  SENSOR_TYPE_LINEAR_ACCELERATION            = 10; /** Acceleration not including gravity, in meter per second squared, "{value}m/s^2". */
-  SENSOR_TYPE_ROTATION_VECTOR                = 11; /** An angle in radians, "{value} rad".*/
-  SENSOR_TYPE_RELATIVE_HUMIDITY              = 12; /** in percent (%), "{value}%". */
-  SENSOR_TYPE_AMBIENT_TEMPERATURE            = 13; /** Temperature of the air around a sensor, in degrees Celsius, "{value}°C". */
-  SENSOR_TYPE_OBJECT_TEMPERATURE             = 14; /** Temperature of the object a sensor is touching/pointed at, in degrees Celsius, "{value}°C".*/
-  SENSOR_TYPE_VOLTAGE                        = 15; /** Volts, "{value}V". */
-  SENSOR_TYPE_CURRENT                        = 16; /** Milliamps, "{value}mA". */
-  SENSOR_TYPE_COLOR                          = 17; /** Values are in 0..1.0 RGB channel luminosity and 32-bit RGBA format. "Color: {value}".*/
-  SENSOR_TYPE_RAW                            = 18; /** Sensor reads a value which is not defined by this list, "Raw Value: {value}".*/
-  SENSOR_TYPE_PM10_STD                       = 19; /** Standard Particulate Matter 1.0, in ppm, "{value}ppm". */
-  SENSOR_TYPE_PM25_STD                       = 20; /** Standard Particulate Matter 2.5, in ppm, "{value}ppm". */
-  SENSOR_TYPE_PM100_STD                      = 21; /** Standard Particulate Matter 100, in ppm, "{value}ppm". */
-  SENSOR_TYPE_PM10_ENV                       = 22; /** Environmental Particulate Matter 1.0, in ppm, "{value}ppm". */
-  SENSOR_TYPE_PM25_ENV                       = 23; /** Environmental Particulate Matter 2.5, in ppm, "{value}ppm". */
-  SENSOR_TYPE_PM100_ENV                      = 24; /** Environmental Particulate Matter 100, in ppm, "{value}ppm".*/
-  SENSOR_TYPE_CO2                            = 25; /** Measured CO2, in ppm, "{value}ppm". */
-  SENSOR_TYPE_GAS_RESISTANCE                 = 26; /** Proportional to the amount of VOC particles in the air, in Ohms, "{value}Ω". */
-  SENSOR_TYPE_ALTITUDE                       = 27; /** Values are in meters (m), "${$v} m". */
-  SENSOR_TYPE_LUX                            = 28; /** Light level, in lux, "Lux: {value}". */
-  SENSOR_TYPE_ECO2                           = 29; /** equivalent/estimated CO2 in ppm (estimated from some other measurement), "{value}ppm". */
-  SENSOR_TYPE_UNITLESS_PERCENT               = 30; /** Percentage, unit-less, "{value}%". */
-  SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT = 31; /** Temperature of the air around a sensor, in degrees Fahrenheit, "{value}°F". */
-  SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT  = 32; /** Temperature of the object a sensor is touching/pointed at, in degrees Fahrenheit, "{value}°F".*/
-  SENSOR_TYPE_VOC_INDEX                      = 33; /** Values are an index from 1-500 with 100 being normal, "${$v} VOC".*/
-  SENSOR_TYPE_NOX_INDEX                      = 34; /** Values are an index from 1-500 with 100 being normal, "${$v} NOx".*/
-  SENSOR_TYPE_TVOC                           = 35; /** Values are in parts per billion (ppb), "${$v} ppb". */
-}
-
-/**
-* SensorEvent  is used to return the sensor's value and type.
-*/
-message SensorEvent {
-  SensorType type = 1; /** The sensor's type and corresponding SI unit */
-  float value     = 2; /** The sensor's value */
-}
-
-/**
-* Each I2CDeviceEvent represents data from **one** I2C sensor.
-* NOTE: An I2CDeviceEvent can have multiple sensor events if
-* the I2C device contains > 1 sensor.
-*/
-message I2CDeviceEvent {
-  uint32 sensor_address              = 1; /** The 7-bit I2C address of the I2C device. */
-  repeated SensorEvent sensor_event  = 2[(nanopb).max_count = 15]; /** A, optionally repeated, SensorEvent from a sensor. */
-}
-
-/**
-* I2CDeviceOutputWrite represents a request to write to an I2C output device.
-* NOTE: This message is similar to the I2CDeviceOutputWrite message on
-* the api-v2 branch but NOT identical.
-*/
-message I2CDeviceOutputWrite {
-  uint32 i2c_device_address             = 1; /** The 7-bit I2C address of the device on the bus. */
-  string i2c_device_name                = 2[(nanopb).max_size = 256]; /** The I2C device's name, MUST MATCH the name on the JSON definition file on https://github.com/adafruit/Wippersnapper_Components. */
-  oneof output_msg {
-    LEDBackpackWrite write_led_backpack = 3; /** Optional - If the I2C device is a LED backpack, fill this field. **/
-    CharLCDWrite write_char_lcd         = 4; /** Optional - If the I2C device is a character LCD, fill this field. **/
-    SSD1306Write write_ssd1306          = 5; /** Optional - If the I2C device is a SSD1306 OLED display, fill this field. **/
-  }
-}
-
-///*** I2C Output Device Messages (from i2c_output.proto in api v2) ***///
-
-/**
-* LEDBackpackAlignment represents all text alignment
-* options for LED backpack displays
-*/
-enum LEDBackpackAlignment {
-  LED_BACKPACK_ALIGNMENT_UNSPECIFIED = 0; /** Unspecified alignment option. **/
-  LED_BACKPACK_ALIGNMENT_LEFT        = 1; /** (Default) Left-aligned. **/
-  LED_BACKPACK_ALIGNMENT_RIGHT       = 2; /** Right-aligned. **/
-}
-
-/**
-* Desired SSD1306 text 'magnification' size.
-*/
-enum SSD1306TextSize {
-  SSD1306_TEXT_SIZE_UNSPECIFIED = 0; /** Unspecified text size. **/
-  SSD1306_TEXT_SIZE_1           = 1; /** Default text size, 6x8px. **/
-  SSD1306_TEXT_SIZE_2           = 2; /** Larger text size option, 12x16px. **/
-}
-
-/**
-* LEDBackpackConfig represents the configuration for a LED backpack display.
-*/
-message LEDBackpackConfig {
-  int32 brightness                = 1; /** Desired brightness of the LED backpack, from 0 (off) to 15 (full brightness). **/
-  LEDBackpackAlignment alignment  = 2; /** Desired text alignment for the LED backpack. **/
-}
-
-/**
-* CharLCDConfig represents the configuration for a character LCD display.
-*/
-message CharLCDConfig {
-  uint32 rows            = 1; /** Number of rows for the character LCD. **/
-  uint32 columns         = 2; /** Number of columns for the character LCD. **/
-}
-
-/**
-* SSD1306Config represents the configuration for a SSD1306 OLED display.
-*/
-message SSD1306Config {
-  uint32 width              = 1; /** Width of the display. **/
-  uint32 height             = 2; /** Height of the display. **/
-  SSD1306TextSize text_size = 3; /** Desired text 'magnification' size. **/
-}
-
-/**
-* I2COutputAdd represents a request from the broker to add an I2C output device to a device.
-*/
-message I2COutputAdd {
-  oneof config {
-    LEDBackpackConfig led_backpack_config = 1; /** Configuration for LED backpack. **/
-    CharLCDConfig char_lcd_config         = 2; /** Configuration for character LCD. **/
-    SSD1306Config ssd1306_config          = 3; /** Configuration for SSD1306 OLED display. **/
-  }
-}
-
-/**
-* LEDBackpackWrite represents a request from the broker to write a message to a LED backpack.
-*/
-message LEDBackpackWrite {
-  string message = 1 [(nanopb).max_size = 128]; /** Message to write to the LED backpack. **/
-}
-
-/**
-* CharLCDWrite represents a request from the broker to write to a character LCD.
-*/
-message CharLCDWrite {
-  string message         = 1 [(nanopb).max_size = 128]; /** Message to write to the character LCD. **/
-  bool enable_backlight  = 2; /** Optional field to enable/disable the backlight. Should be its own feed (0 is off, 1 is on).**/
-}
-
-/**
-* SSD1306Write represents a request from the broker to
-* write to a SSD1306 OLED display.
-*/
-message SSD1306Write {
-  string message = 1 [(nanopb).max_size = 256]; /** Message to write to a SSD1306 OLED display. **/
-}
\ No newline at end of file
diff --git a/proto/wippersnapper/i2c_output.options b/proto/wippersnapper/i2c_output.options
new file mode 100644
index 00000000..f54f5b53
--- /dev/null
+++ b/proto/wippersnapper/i2c_output.options
@@ -0,0 +1,4 @@
+# i2c_output.options
+wippersnapper.i2c_output.LedBackpackWrite.message      max_size:8
+wippersnapper.i2c_output.CharLCDWrite.message          max_size:100
+wippersnapper.i2c_output.OLEDWrite.message             max_size:512
\ No newline at end of file
diff --git a/proto/wippersnapper/i2c_output.proto b/proto/wippersnapper/i2c_output.proto
new file mode 100644
index 00000000..f21b9dce
--- /dev/null
+++ b/proto/wippersnapper/i2c_output.proto
@@ -0,0 +1,81 @@
+// SPDX-FileCopyrightText: 2025 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.i2c_output;
+
+/**
+* LedBackpackAlignment represents all text alignment option for LED backpack displays
+*/
+enum LedBackpackAlignment {
+  LED_BACKPACK_ALIGNMENT_UNSPECIFIED = 0; /** Unspecified alignment option. **/
+  LED_BACKPACK_ALIGNMENT_LEFT        = 1; /** (Default) Left-aligned. **/
+  LED_BACKPACK_ALIGNMENT_RIGHT       = 2; /** Right-aligned. **/
+}
+
+/**
+* Desired OLED display text 'magnification' size.
+*/
+enum OledTextSize {
+  OLED_TEXT_SIZE_UNSPECIFIED = 0; /** Unspecified text size. **/
+  OLED_TEXT_SIZE_DEFAULT     = 1; /** Default text size, 6x8px. **/
+  OLED_TEXT_SIZE_LARGE       = 2; /** Larger text size option, 12x16px. **/
+}
+
+/**
+* LedBackpackConfig represents the configuration for a LED backpack display.
+*/
+message LedBackpackConfig {
+  int32 brightness                = 1; /** Desired brightness of the LED backpack, from 0 (off) to 15 (full brightness). **/
+  LedBackpackAlignment alignment  = 2; /** Desired text alignment for the LED backpack. **/
+}
+
+/**
+* CharLCDConfig represents the configuration for a character LCD display.
+*/
+message CharLCDConfig {
+  uint32 rows            = 1; /** Number of rows for the character LCD. **/
+  uint32 columns         = 2; /** Number of columns for the character LCD. **/
+}
+
+/**
+* OledConfig represents the configuration for a OLED display.
+*/
+message OledConfig {
+  uint32 width           = 1; /** Width of the OLED display in pixels. **/
+  uint32 height          = 2; /** Height of the OLED display in pixels. **/
+  OledTextSize font_size = 3; /** Desired font magnification for the OLED display. Defaults to OLED_TEXT_SIZE_DEFAULT. **/
+}
+
+
+/**
+* I2cOutputAdd represents a request from the broker to add an I2C output device to a device.
+*/
+message I2cOutputAdd {
+  oneof config {
+    LedBackpackConfig led_backpack_config = 1; /** Configuration for a LED backpack. **/
+    CharLCDConfig char_lcd_config         = 2; /** Configuration for a character LCD. **/
+    OledConfig oled_config                = 3; /** Configuration for an OLED display. **/
+  }
+}
+
+/**
+* LedBackpackWrite represents a request from the broker to write a message to a LED backpack.
+*/
+message LedBackpackWrite {
+  string message = 1; /** Message to write to the LED backpack. **/
+}
+
+/**
+* CharLCDWrite represents a request from the broker to write to a character LCD.
+*/
+message CharLCDWrite {
+  string message        = 1; /** Message to write to the character LCD. **/
+  bool enable_backlight = 2; /** Whether to enable the backlight. Defaults to True. **/
+}
+
+/**
+* OLEDWrite represents a request from the broker to write to a OLED display.
+*/
+message OLEDWrite {
+  string message = 1; /** Message to write to an OLED display. **/
+}
\ No newline at end of file
diff --git a/proto/nanopb/nanopb.proto b/proto/wippersnapper/nanopb.proto
similarity index 100%
rename from proto/nanopb/nanopb.proto
rename to proto/wippersnapper/nanopb.proto
diff --git a/proto/wippersnapper/pin/v1/pin.proto b/proto/wippersnapper/pin/v1/pin.proto
deleted file mode 100644
index 745d1522..00000000
--- a/proto/wippersnapper/pin/v1/pin.proto
+++ /dev/null
@@ -1,144 +0,0 @@
-// SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.pin.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* Mode. Specifies if a GPIO pin is an analog or digital pin.
-*/
-enum Mode {
-  MODE_UNSPECIFIED = 0; /** Invalid Mode from Broker. */
-  MODE_ANALOG      = 1; /** Set up an analog pin. */
-  MODE_DIGITAL     = 2; /** Set up a digital pin. */
-}
-
-/**
-* Represents a list of ConfigurePinRequest messages.
-*/
-message ConfigurePinRequests {
-  repeated ConfigurePinRequest list = 1;
-}
-
-/**
- * Represents a request from the broker to create, update, or delete a GPIO pin.
- */
-message ConfigurePinRequest {
-  string pin_name                  = 1 [(nanopb).max_size = 5]; /** The name of pin we are accessing. */
-  Mode mode                        = 2; /** Specifies the pin's type, analog or input. */
-  Direction direction              = 3; /** Specifies the pin's behavior. */
-  Pull pull                        = 4; /** Specifies an optional pullup resistor value. */
-  float period                     = 5; /** Specifies the time between measurements, in seconds. */
-  RequestType request_type         = 6; /** Specifies the type of ConfigurePinRequest. */
-  float aref                       = 7 [deprecated=true]; /** deprecated: Specifies the reference voltage used for analog input, defaults to 3.3v. */
-  AnalogReadMode analog_read_mode  = 8; /** ANALOG-ONLY: Specifies the read mode for an analog pin. */
-
-  /**
-   * Direction. Specifies the pin's direction, INPUT or OUTPUT.
-   */
-  enum Direction {
-    DIRECTION_UNSPECIFIED = 0; /** Invalid Direction from Broker. */
-    DIRECTION_INPUT       = 1; /** Set the pin to behave as an input. */
-    DIRECTION_OUTPUT      = 2; /** Set the pin to behave as an output. */
-  }
-
-  /**
-   * Pull. An optional pullup resistor value
-   */
-  enum Pull {
-    PULL_UNSPECIFIED = 0; /** Invalid Direction from Broker. */
-    PULL_UP          = 1; /** Set the pin to pull high. */
-    PULL_DOWN        = 2; /** Set the pin to pull low. */
-  }
-
-  /**
-   * Request Type. Describes the type of ConfigurePinRequest for the hardware to operate on.
-   */
-  enum RequestType {
-    REQUEST_TYPE_UNSPECIFIED = 0; /** Invalid request from Broker. */
-    REQUEST_TYPE_CREATE      = 1; /** The request creates a pin. */
-    REQUEST_TYPE_UPDATE      = 2; /** The request updates a previously created pin. */
-    REQUEST_TYPE_DELETE      = 3; /** The request deletes a previously created pin. */
-  }
-
-  /**
-   * Selects the type of value read by an analog pin.
-   * PIN_VALUE: Raw ADC reading.
-   * PIN_VOLTAGE: Calculated voltage reading.
-   * NOTE: This is only applicable to analog pins.
-   */
-  enum AnalogReadMode {
-    ANALOG_READ_MODE_UNSPECIFIED = 0;
-    ANALOG_READ_MODE_PIN_VALUE   = 1;
-    ANALOG_READ_MODE_PIN_VOLTAGE = 2;
-  }
-
-}
-
-
-/**
-* Pin Event. Describes a pin's value.
-*/
-message PinEvent {
-  string pin_name         = 1 [(nanopb).max_size = 5]; /** Specifies the pin's name. */
-  string pin_value        = 2 [(nanopb).max_size = 12]; /** Specifies the pin's value. */
-
-  Mode mode               = 3 [deprecated = true, (nanopb).type = FT_IGNORE]; /** DEPRECATED: Specifies the pin's mode, analog or digital. */
-  float  pin_value_volts  = 4 [deprecated = true, (nanopb).type = FT_IGNORE]; /** DEPRECATED: Specifies an anlog pin's voltage. */ 
-}
-
-/**
-* ConfigureReferenceVoltage - Changes the reference voltage used for analog inputs.
-* Direction: C2D
-*/
-message ConfigureReferenceVoltage {
-  float reference_voltage  = 1; /** Specifies an ADC reference voltage. */
-}
-
-/**
-* Sends a list of PinEvents
-* NOTE: Not working with nanopb decode repeated
-*/
-message PinEvents {
-  option deprecated = true; 
-  repeated PinEvent list = 1;
-}
-
-/* DEPRECATED - PWM Pin API */
-
-// Configures a PWM output pin
-message ConfigurePWMPinRequest {
-  option deprecated = true;
-  // Pin to write to
-  string pin_name         = 1 [(nanopb).max_size = 5, deprecated = true, (nanopb).type = FT_IGNORE];
-
-  // Duty cycle between always off (0)
-  // and always on (255)
-  int32 duty_cycle        = 2 [deprecated = true, (nanopb).type = FT_IGNORE];
-
-  // Target frequency, in Hz
-  int32 frequency         = 3 [deprecated = true, (nanopb).type = FT_IGNORE];
-
-  // If the frequency changes over time
-  // NOTE: CIRCUITPYTHON-API ONLY
-  bool variable_frequency = 4 [deprecated = true, (nanopb).type = FT_IGNORE];
-}
-
-message ConfigurePWMPinRequests {
-  option deprecated = true;
-  repeated ConfigurePWMPinRequest list = 1 [deprecated = true, (nanopb).type = FT_IGNORE];
-}
-
-// Write duty cycle to a pin PWM output pin
-message PWMPinEvent {
-  option deprecated = true;
-  // Duty cycle between always off (0)
-  // and always on (255)
-  int32 duty_cycle        = 2 [deprecated = true, (nanopb).type = FT_IGNORE];
-}
-
-message PWMPinEvents {
-  option deprecated = true;
-  repeated PWMPinEvent list = 1 [deprecated = true, (nanopb).type = FT_IGNORE];
-}
\ No newline at end of file
diff --git a/proto/wippersnapper/pixels.options b/proto/wippersnapper/pixels.options
new file mode 100644
index 00000000..53e2abb9
--- /dev/null
+++ b/proto/wippersnapper/pixels.options
@@ -0,0 +1,6 @@
+# pixels.options
+wippersnapper.pixels.PixelsAdd.pixels_pin_data          max_size: 6
+wippersnapper.pixels.PixelsAdd.pixels_pin_dotstar_clock max_size:6
+wippersnapper.pixels.PixelsAdded.pixels_pin_data        max_size: 6
+wippersnapper.pixels.PixelsRemove.pixels_pin_data       max_size: 6
+wippersnapper.pixels.PixelsWrite.pixels_pin_data        max_size: 6
diff --git a/proto/wippersnapper/pixels.proto b/proto/wippersnapper/pixels.proto
new file mode 100644
index 00000000..e673e114
--- /dev/null
+++ b/proto/wippersnapper/pixels.proto
@@ -0,0 +1,70 @@
+// SPDX-FileCopyrightText: 2022-2023 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+// Addressable Pixels API for Adafruit WipperSnapper
+syntax = "proto3";
+package wippersnapper.pixels;
+
+/**
+* PixelsType defines the type/model of pixel strand.
+*/
+enum PixelsType {
+  PIXELS_TYPE_UNSPECIFIED = 0; /** Unspecified pixel type, error. */
+  PIXELS_TYPE_NEOPIXEL    = 1; /** NeoPixel pixel strand. */
+  PIXELS_TYPE_DOTSTAR     = 2; /** DotStar pixel strand. */
+}
+
+/**
+* PixelsOrder defines the color ordering.
+*/
+enum PixelsOrder {
+  PIXELS_ORDER_UNSPECIFIED  = 0; /** Unspecified color ordering, error. */
+  PIXELS_ORDER_GRB          = 1; /** DEFAULT for NeoPixels - Green, Red, Blue */
+  PIXELS_ORDER_GRBW         = 2; /** Green, Red, Blue, White */
+  PIXELS_ORDER_RGB          = 3; /** Red, Green, Blue */
+  PIXELS_ORDER_RGBW         = 4; /** Red, Green, Blue, White */
+  PIXELS_ORDER_BRG          = 5; /** DEFAULT for DotStars - Blue, Red, Green */
+  PIXELS_ORDER_RBG          = 6; /** Red, Blue Green */
+  PIXELS_ORDER_GBR          = 7; /** Green, Blue, Red */
+  PIXELS_ORDER_BGR          = 8; /** Blue, Green, Red */
+}
+
+/**
+* PixelsAdd represents a call from IO to a device.
+* Adds a strand of addressable pixels.
+* Initial brightness is always 128.
+*/
+message PixelsAdd {
+  PixelsType pixels_type          = 1; /** Defines the model/type of pixel strand */
+  uint32 pixels_num               = 2; /** Number of pixels attached to strand. */
+  PixelsOrder pixels_ordering     = 3; /** Defines the pixel strand's color ordering. */
+  uint32 pixels_brightness        = 4; /** Strand brightness, 0 to 255 */
+  string pixels_pin_data          = 5; /** Data pin a NeoPixel or DotStar strand is connected to. */
+  string pixels_pin_dotstar_clock = 6; /** Clock pin a DotStar strand is connected to. */
+}
+
+/**
+* PixelsAdded represents response from a WipperSnapper
+* device to IO after a PixelsAdd call
+*/
+message PixelsAdded {
+  bool is_success = 1; /** True if the strand initialized successfully, False otherwise. */
+  string pixels_pin_data = 2; /** Data pin the responding strand is connected to. */
+}
+
+/**
+* PixelAdd represents a call from IO to a device
+* Removes a strand of addressable pixels and release the resources and pin.
+*/
+message PixelsRemove {
+  string pixels_pin_data = 1; /** Data pin the pixel strand is connected to. */
+}
+
+/**
+* PixelsWrite represents a call from IO to a device.
+* Writes to a strand of pixels.
+*/
+message PixelsWrite {
+  string pixels_pin_data   = 1; /** Data pin a strand is connected to. */
+  uint32 pixels_color      = 2; /* 32-bit color value. Most significant byte is white (for RGBW pixels) or ignored (for RGB pixels),
+                                   next is red, then green, and least significant byte is blue. */
+}
diff --git a/proto/wippersnapper/pixels/v1/pixels.proto b/proto/wippersnapper/pixels/v1/pixels.proto
deleted file mode 100644
index 348abb1f..00000000
--- a/proto/wippersnapper/pixels/v1/pixels.proto
+++ /dev/null
@@ -1,75 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-// Addressable Pixels API for Adafruit WipperSnapper
-
-syntax = "proto3";
-
-package wippersnapper.pixels.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* PixelsType defines the type/model of pixel strand.
-*/
-enum PixelsType {
-  PIXELS_TYPE_UNSPECIFIED = 0;
-  PIXELS_TYPE_NEOPIXEL    = 1;
-  PIXELS_TYPE_DOTSTAR     = 2;
-}
-
-/**
-* PixelsOrder defines the color ordering.
-*/
-enum PixelsOrder {
-  PIXELS_ORDER_UNSPECIFIED  = 0; /** Unspecified color ordering, error. */
-  PIXELS_ORDER_GRB          = 1; /** DEFAULT for NeoPixels - Green, Red, Blue */
-  PIXELS_ORDER_GRBW         = 2; /** Green, Red, Blue, White */
-  PIXELS_ORDER_RGB          = 3; /** Red, Green, Blue */
-  PIXELS_ORDER_RGBW         = 4; /** Red, Green, Blue, White */
-  PIXELS_ORDER_BRG          = 5; /** DEFAULT for DotStars - Blue, Red, Green */
-  PIXELS_ORDER_RBG          = 6; /** Red, Blue Green */
-  PIXELS_ORDER_GBR          = 7; /** Green, Blue, Red */
-  PIXELS_ORDER_BGR          = 8; /** Blue, Green, Red */
-}
-
-/**
-* PixelsCreateRequest represents a call from IO to a device.
-* Creates a strand of addressable pixels.
-* Initial brightness is always 128.
-*/
-message PixelsCreateRequest {
-  PixelsType pixels_type          = 1; /** Defines the model/type of pixel strand */
-  uint32 pixels_num               = 2; /** Number of pixels attached to strand. */
-  PixelsOrder pixels_ordering     = 3; /** Defines the pixel strand's color ordering. */
-  uint32 pixels_brightness        = 4; /** Strand brightness, 0 to 255 */
-  string pixels_pin_neopixel      = 5 [(nanopb).max_size = 6]; /** Generic pin a NeoPixel strand is connected to. */
-  string pixels_pin_dotstar_data  = 6 [(nanopb).max_size = 6]; /** Data pin a DotStar strand is connected to. */
-  string pixels_pin_dotstar_clock = 7 [(nanopb).max_size = 6]; /** Clock pin a DotStar strand is connected to. */
-}
-
-/**
-* PixelsCreateResponse represents response from a WipperSnapper
-* device to IO after a PixelsCreateRequest call
-*/
-message PixelsCreateResponse {
-  bool is_success = 1; /** True if the strand initialized successfully, False otherwise. */
-  string pixels_pin_data = 2 [(nanopb).max_size = 6]; /** Data pin the responding strand is connected to. */
-}
-
-/**
-* PixelCreateRequest represents a call from IO to a device
-* Deletes a strand of addressable pixels and release the resources and pin.
-*/
-message PixelsDeleteRequest {
-  PixelsType pixels_type = 1; /** Defines the model/type of pixel strand */
-  string pixels_pin_data = 2 [(nanopb).max_size = 6]; /** Data pin a strand is connected to. */
-}
-
-/**
-* PixelsWriteRequest represents a call from IO to a device.
-* Writes to a strand of pixels.
-*/
-message PixelsWriteRequest {
-  PixelsType pixels_type   = 1; /** Defines the model/type of pixel strand */
-  string pixels_pin_data   = 2 [(nanopb).max_size = 6]; /** Data pin a strand is connected to. */
-  uint32 pixels_color      = 3; /* 32-bit color value. Most significant byte is white (for RGBW pixels) or ignored (for RGB pixels), next is red, then green, and least significant byte is blue. */
-}
diff --git a/proto/wippersnapper/pwm.options b/proto/wippersnapper/pwm.options
new file mode 100644
index 00000000..1669e1b9
--- /dev/null
+++ b/proto/wippersnapper/pwm.options
@@ -0,0 +1,7 @@
+# pwm.options
+wippersnapper.pwm.PWMAdd.pin                                   max_size:6
+wippersnapper.pwm.PWMAdded.pin                                 max_size:6
+wippersnapper.pwm.PWMRemove.pin                                max_size:6
+wippersnapper.pwm.PWMWriteDutyCycle.pin                        max_size:6
+wippersnapper.pwm.PWMWriteFrequency.pin                        max_size:6
+wippersnapper.pwm.PWMWriteDutyCycleMulti.write_duty_cycle_reqs max_count:4
\ No newline at end of file
diff --git a/proto/wippersnapper/pwm.proto b/proto/wippersnapper/pwm.proto
new file mode 100644
index 00000000..963f1521
--- /dev/null
+++ b/proto/wippersnapper/pwm.proto
@@ -0,0 +1,60 @@
+// SPDX-FileCopyrightText: 2022-2023 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.pwm;
+
+/**
+* PWMAdd represents a  to a device to attach/allocate a PWM pin.
+* On ESP32 Arduino, this will "attach" a pin to a LEDC channel/timer group.
+* On non-ESP32 Arduino, this does nothing.
+*/
+message PWMAdd {
+  string pin       = 1; /** The pin to be attached. */
+  int32 frequency  = 2; /** PWM frequency of an analog pin, in Hz. **/
+  int32 resolution = 3; /** The resolution of an analog pin, in bits. **/
+}
+
+/**
+* PWMAdded represents a response from a device's execution of an
+* Add message.
+*/
+message PWMAdded {
+  string pin      = 1; /** The ed pin. */
+  bool did_attach = 2; /** True if Add successful, False otherwise. */
+}
+
+/**
+* PWMRemove represents a  to stop PWM'ing and release the pin for re-use.
+* On ESP32, this will "detach" a pin from a LEDC channel/timer group.
+* On non-ESP32 Arduino, this calls digitalWrite(LOW) on the pin
+*/
+message PWMRemove {
+  string pin       = 1; /** The PWM pin to de-initialized. */
+}
+
+/**
+* PWMWriteDutyCycle represents a  to write a duty cycle to a pin with a frequency (fixed).
+* This is used for controlling LEDs.
+*/
+message PWMWriteDutyCycle {
+  string pin       = 1; /** The pin to write to. */
+  int32 duty_cycle = 2; /** The desired duty cycle to write (range is from 0 to (2 ** duty_resolution)).
+                            This value will be changed by the slider on Adafruit IO. **/
+}
+
+/**
+* PWMWriteDutyCycleMulti represents a wrapper  to write duty cycles to multiple pins.
+* This is used for controlling RGB/RGBW LEDs.
+*/
+message PWMWriteDutyCycleMulti {
+  repeated PWMWriteDutyCycle write_duty_cycle_reqs = 1; /** Multiple duty cycles to write, one per pin of a RGB LED. **/
+}
+
+/**
+* PWMWriteFrequency represents a  to write a Frequency, in Hz, to a pin with a duty cycle of 50%.
+* This is used for playing tones using a piezo buzzer or speaker.
+*/
+message PWMWriteFrequency {
+  string pin       = 1; /** The pin to write to. */
+  int32 frequency  = 2; /** The desired PWM frequency, in Hz. This value will be changed by the slider on Adafruit IO. **/
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/pwm/v1/pwm.proto b/proto/wippersnapper/pwm/v1/pwm.proto
deleted file mode 100644
index 5b464b51..00000000
--- a/proto/wippersnapper/pwm/v1/pwm.proto
+++ /dev/null
@@ -1,61 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.pwm.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* PWMAttachRequest represents a request to a device to attach/allocate a PWM pin.
-* On ESP32 Arduino, this will "attach" a pin to a LEDC channel/timer group.
-* On non-ESP32 Arduino, this does nothing.
-*/
-message PWMAttachRequest {
-  string pin       = 1 [(nanopb).max_size = 6]; /** The pin to be attached. */
-  int32 frequency  = 2; /** PWM frequency of an analog pin, in Hz. **/
-  int32 resolution = 3; /** The resolution of an analog pin, in bits. **/
-}
-
-/**
-* PWMAttachResponse represents a response from a device's execution of an
-* AttachRequest message.
-*/
-message PWMAttachResponse {
-  string pin       = 1 [(nanopb).max_size = 6]; /** The requested pin. */
-  bool did_attach = 2; /** True if AttachRequest successful, False otherwise. */
-}
-
-/**
-* PWMDetachRequest represents a request to stop PWM'ing and release the pin for re-use.
-* On ESP32, this will "detach" a pin from a LEDC channel/timer group.
-* On non-ESP32 Arduino, this calls digitalWrite(LOW) on the pin
-*/
-message PWMDetachRequest {
-  string pin       = 1 [(nanopb).max_size = 6]; /** The PWM pin to de-initialized. */
-}
-
-/**
-* WriteDutyCycleRequest represents a request to write a duty cycle to a pin with a frequency (fixed).
-* This is used for controlling LEDs.
-*/
-message PWMWriteDutyCycleRequest {
-  string pin       = 1 [(nanopb).max_size = 6]; /** The pin to write to. */
-  int32 duty_cycle = 2; /** The desired duty cycle to write. This value will be changed by the slider on Adafruit IO. **/
-}
-
-/**
-* WriteDutyCycle represents a wrapper request to write duty cycles to multiple pins.
-* This is used for controlling RGB/RGBW LEDs.
-*/
-message PWMWriteDutyCycleMultiRequest {
-  repeated PWMWriteDutyCycleRequest write_duty_cycle_req = 1 [(nanopb).max_count = 4];
-}
-
-/**
-* WriteFrequencyRequest represents a request to write a Frequency, in Hz, to a pin with a duty cycle of 50%.
-* This is used for playing tones using a piezo buzzer or speaker.
-*/
-message PWMWriteFrequencyRequest {
-  string pin       = 1 [(nanopb).max_size = 6]; /** The pin to write to. */
-  int32 frequency  = 2; /** The desired PWM frequency, in Hz. This value will be changed by the slider on Adafruit IO. **/
-}
\ No newline at end of file
diff --git a/proto/wippersnapper/sensor.proto b/proto/wippersnapper/sensor.proto
new file mode 100644
index 00000000..a0fc2392
--- /dev/null
+++ b/proto/wippersnapper/sensor.proto
@@ -0,0 +1,91 @@
+// SPDX-FileCopyrightText: 2023 Brent Rubell, Loren Norman for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.sensor;
+
+/**
+* SensorType allows us determine what types of units the sensor uses, etc.
+*/
+enum SensorType {
+  SENSOR_TYPE_UNSPECIFIED                    = 0;  /** Sensor value type which is not defined by this list, "Raw Value: {value}". */
+  SENSOR_TYPE_ACCELEROMETER                  = 1;  /** Acceleration, in meter per second per second, "{value}m/s/s". */
+  SENSOR_TYPE_MAGNETIC_FIELD                 = 2;  /** Magnetic field strength, in micro-Tesla, "{value}µT". */
+  SENSOR_TYPE_ORIENTATION                    = 3;  /** Orientation angle, in degrees, "{value}°". */
+  SENSOR_TYPE_GYROSCOPE                      = 4;  /** Angular rate, in radians per second, "{value}rad/s". */
+  SENSOR_TYPE_LIGHT                          = 5;  /** Light-level, non-unit-specific (For a unit-specific measurement, see: Lux),
+                                                       "Raw Value: {value}". */
+  SENSOR_TYPE_PRESSURE                       = 6;  /** Pressure, in hectopascal, , "{value}hPa". */
+  SENSOR_TYPE_PROXIMITY                      = 8;  /** Distance from an object to a sensor, non-unit-specific, "Raw Value: {value}". */
+  SENSOR_TYPE_GRAVITY                        = 9;  /** Metres per second squared, "{value}m/s^2". */
+  SENSOR_TYPE_LINEAR_ACCELERATION            = 10; /** Acceleration not including gravity, in meter per second squared, "{value}m/s^2". */
+  SENSOR_TYPE_ROTATION_VECTOR                = 11; /** An angle in radians, "{value} rad".*/
+  SENSOR_TYPE_RELATIVE_HUMIDITY              = 12; /** in percent (%), "{value}%". */
+  SENSOR_TYPE_AMBIENT_TEMPERATURE            = 13; /** Temperature of the air around a sensor, in degrees Celsius, "{value}°C". */
+  SENSOR_TYPE_OBJECT_TEMPERATURE             = 14; /** Temperature of the object a sensor is touching/pointed at, in degrees Celsius, "{value}°C".*/
+  SENSOR_TYPE_VOLTAGE                        = 15; /** Volts, "{value}V". */
+  SENSOR_TYPE_CURRENT                        = 16; /** Milliamps, "{value}mA". */
+  SENSOR_TYPE_COLOR                          = 17; /** Values are in 0..1.0 RGB channel luminosity and 32-bit RGBA format. "Color: {value}".*/
+  SENSOR_TYPE_RAW                            = 18; /** Sensor reads a value which is not defined by this list, "Raw Value: {value}".*/
+  SENSOR_TYPE_PM10_STD                       = 19; /** Standard Particulate Matter 1.0, in ppm, "{value}ppm". */
+  SENSOR_TYPE_PM25_STD                       = 20; /** Standard Particulate Matter 2.5, in ppm, "{value}ppm". */
+  SENSOR_TYPE_PM100_STD                      = 21; /** Standard Particulate Matter 100, in ppm, "{value}ppm". */
+  SENSOR_TYPE_PM10_ENV                       = 22; /** Environmental Particulate Matter 1.0, in ppm, "{value}ppm". */
+  SENSOR_TYPE_PM25_ENV                       = 23; /** Environmental Particulate Matter 2.5, in ppm, "{value}ppm". */
+  SENSOR_TYPE_PM100_ENV                      = 24; /** Environmental Particulate Matter 100, in ppm, "{value}ppm".*/
+  SENSOR_TYPE_CO2                            = 25; /** Measured CO2, in ppm, "{value}ppm". */
+  SENSOR_TYPE_GAS_RESISTANCE                 = 26; /** Proportional to the amount of VOC particles in the air, in Ohms, "{value}Ω". */
+  SENSOR_TYPE_ALTITUDE                       = 27; /** Values are in meters (m), "${$v} m". */
+  SENSOR_TYPE_LUX                            = 28; /** Light level, in lux, "Lux: {value}". */
+  SENSOR_TYPE_ECO2                           = 29; /** equivalent/estimated CO2 in ppm (estimated from some other measurement), "{value}ppm". */
+  SENSOR_TYPE_UNITLESS_PERCENT               = 30; /** Percentage, unit-less, "{value}%". */
+  SENSOR_TYPE_AMBIENT_TEMPERATURE_FAHRENHEIT = 31; /** Temperature of the air around a sensor, in degrees Fahrenheit, "{value}°F". */
+  SENSOR_TYPE_OBJECT_TEMPERATURE_FAHRENHEIT  = 32; /** Temperature of the object a sensor is touching/pointed at, in Fahrenheit, "{value}°F".*/
+  SENSOR_TYPE_VOC_INDEX                      = 33; /** Values are an index from 1-500 with 100 being normal, "${$v} VOC".*/
+  SENSOR_TYPE_NOX_INDEX                      = 34; /** Values are an index from 1-500 with 100 being normal, "${$v} NOx".*/
+  SENSOR_TYPE_TVOC                           = 35; /** Values are in parts per billion (ppb), "${$v} ppb". */
+  SENSOR_TYPE_BYTES                          = 36; /** Values are in bytes, "${$v} bytes". */
+  SENSOR_TYPE_BOOLEAN                        = 37; /** Values are boolean, "Boolean Value: ${$v}". */
+}
+
+/**
+* SensorEvent  is used to return the sensor's value and type.
+*/
+message SensorEvent {
+  SensorType type                            = 1; /** The sensor's type and corresponding SI unit */
+  oneof value {
+    float float_value                        = 2; /** The sensor's value as a float. */
+    bytes bytes_value                        = 3; /** The sensor's value as a byte array. */
+    SensorEvent3DVector vector_value         = 4; /** The sensor's 3D vector values, as floats. */
+    SensorEventOrientation orientation_value = 5; /** The sensor's orientation values, as floats. */
+    SensorEventColor color_value             = 6; /** The sensor's color values, as floats. */
+    bool bool_value                          = 7; /** The sensor's value, as a boolean. */
+  }
+
+  /**
+  * SensorEventColor is used to return a sensor's color values in RGB colorspace.
+  */
+  message SensorEventColor {
+    float r = 1; /** The sensor's red channel value as a float. */
+    float g = 2; /** The sensor's green channel value as a float. */
+    float b = 3; /** The sensor's blue channel value as a float. */
+    float a = 4; /** The sensor's (optional) alpha channel value as a float. */
+  }
+
+  /**
+  * SensorEvent3DVector is used to return a sensor's 3D vector values.
+  */
+  message SensorEvent3DVector {
+    float x = 1; /** The sensor's x-axis value as a float. */
+    float y = 2; /** The sensor's y-axis value as a float. */
+    float z = 3; /** The sensor's z-axis value as a float. */
+  }
+
+  /**
+  * SensorEventOrientation is used to return an orientation sensor's values.
+  */
+  message SensorEventOrientation {
+    float roll    = 1; /** The sensor's roll value as a float. */
+    float pitch   = 2; /** The sensor's pitch value as a float. */
+    float heading = 3; /** The sensor's heading value as a float. */
+  }
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/servo.options b/proto/wippersnapper/servo.options
new file mode 100644
index 00000000..ad266f82
--- /dev/null
+++ b/proto/wippersnapper/servo.options
@@ -0,0 +1,5 @@
+# servo.options
+wippersnapper.servo.ServoAdd.servo_pin    max_size:6
+wippersnapper.servo.ServoAdded.servo_pin  max_size:6
+wippersnapper.servo.ServoRemove.servo_pin max_size:6
+wippersnapper.servo.ServoWrite.servo_pin  max_size:6
\ No newline at end of file
diff --git a/proto/wippersnapper/servo.proto b/proto/wippersnapper/servo.proto
new file mode 100644
index 00000000..f8439a3c
--- /dev/null
+++ b/proto/wippersnapper/servo.proto
@@ -0,0 +1,41 @@
+// SPDX-FileCopyrightText: 2022-2024 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.servo;
+
+/**
+* ServoAdd represents a request to attach a servo to a pin.
+*/
+message ServoAdd {
+  string servo_pin      = 1; /** The name of pin to attach a servo to. */
+  int32 servo_freq      = 2; /** The overall PWM frequency, default sent by Adafruit IO is 50Hz. **/
+  int32 min_pulse_width = 3; /** The minimum pulse length in uS. Default sent by Adafruit IO is 500uS. **/
+  int32 max_pulse_width = 4; /** The maximum pulse length in uS. Default sent by Adafruit IO is 2500uS. **/
+}
+
+/**
+* ServoAdded represents the result of attaching a servo to a pin.
+*/
+message ServoAdded {
+  bool attach_success = 1; /** True if a servo was attached successfully, False otherwise. **/
+  string servo_pin    = 2; /** The name of pin we're responding about. */
+}
+
+/**
+* ServoRemove represents a request to detach a servo from a pin and de-initialize the pin for other uses.
+*/
+message ServoRemove {
+  string servo_pin = 1; /** The name of pin to use as a servo pin. */
+}
+
+/**
+* ServoWrite represents a message to write the servo's position.
+*
+* NOTE: Position is sent from Adafruit IO as a pulse width in uS between 0uS
+* and 2500uS. The client application must convert pulse width to duty cycle w/fixed
+* freq of 50Hz prior to writing to the servo pin.
+*/
+message ServoWrite {
+  string servo_pin   = 1; /** The name of pin we're addressing. */
+  int32  pulse_width = 2; /** The pulse width to write to the servo, in uS **/
+}
diff --git a/proto/wippersnapper/servo/v1/servo.proto b/proto/wippersnapper/servo/v1/servo.proto
deleted file mode 100644
index 92a5ec5e..00000000
--- a/proto/wippersnapper/servo/v1/servo.proto
+++ /dev/null
@@ -1,43 +0,0 @@
-// SPDX-FileCopyrightText: 2022 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.servo.v1;
-import "nanopb/nanopb.proto";
-
-/**
-* ServoAttachRequest represents a request to attach a servo to a pin.
-*/
-message ServoAttachRequest {
-  string servo_pin      = 1 [(nanopb).max_size = 6]; /** The name of pin to attach a servo to. */
-  int32 servo_freq      = 2; /** The overall PWM frequency, default sent by Adafruit IO is 50Hz. **/
-  int32 min_pulse_width = 3; /** The minimum pulse length in uS. Default sent by Adafruit IO is 500uS. **/
-  int32 max_pulse_width = 4; /** The maximum pulse length in uS. Default sent by Adafruit IO is 2500uS. **/
-}
-
-/**
-* ServoAttachResponse represents the result of attaching a servo to a pin.
-*/
-message ServoAttachResponse {
-  bool attach_success = 1; /** True if a servo was attached successfully, False otherwise. **/
-  string servo_pin    = 2 [(nanopb).max_size = 6]; /** The name of pin we're responding about. */
-}
-
-/**
-* ServoDetachRequest represents a request to detach a servo from a pin and de-initialize the pin for other uses.
-*/
-message ServoDetachRequest {
-  string servo_pin = 1 [(nanopb).max_size = 5]; /** The name of pin to use as a servo pin. */
-}
-
-/**
-* ServoWriteReq represents a message to write the servo's position.
-*
-* NOTE: Position is sent from Adafruit IO as a pulse width in uS between 500uS
-* and 2500uS. The client application must convert pulse width to duty cycle w/fixed
-* freq of 50Hz prior to writing to the servo pin.
-*/
-message ServoWriteRequest {
-  string servo_pin   = 1 [(nanopb).max_size = 5]; /** The name of pin we're addressing. */
-  int32  pulse_width = 2; /** The pulse width to write to the servo, in uS **/
-}
diff --git a/proto/wippersnapper/signal.options b/proto/wippersnapper/signal.options
new file mode 100644
index 00000000..c7decfb7
--- /dev/null
+++ b/proto/wippersnapper/signal.options
@@ -0,0 +1,3 @@
+# signal.options
+wippersnapper.signal.BrokerToDevice  submsg_callback:true
+wippersnapper.signal.DeviceToBroker  submsg_callback:true
\ No newline at end of file
diff --git a/proto/wippersnapper/signal.proto b/proto/wippersnapper/signal.proto
new file mode 100644
index 00000000..f2afbc4d
--- /dev/null
+++ b/proto/wippersnapper/signal.proto
@@ -0,0 +1,103 @@
+// SPDX-FileCopyrightText: 2020-2024 Brent Rubell, Loren Norman for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.signal;
+import "analogio.proto";
+import "checkin.proto";
+import "digitalio.proto";
+import "ds18x20.proto";
+import "error.proto";
+import "gps.proto";
+import "i2c.proto";
+import "i2c_output.proto";
+import "nanopb.proto";
+import "pixels.proto";
+import "pwm.proto";
+import "servo.proto";
+import "uart.proto";
+
+/*
+ * BrokerToDevice
+ * The BrokerToDevice message is sent from the broker to the device.
+ * It contains a oneof payload, which is a union of all the possible
+ * messages that can be sent from the broker to a device.
+ */
+message BrokerToDevice {
+  oneof payload {
+    // digitalio.proto
+    wippersnapper.digitalio.DigitalIOAdd digitalio_add       = 10;
+    wippersnapper.digitalio.DigitalIORemove digitalio_remove = 11;
+    wippersnapper.digitalio.DigitalIOWrite digitalio_write   = 12;
+    // analogio.proto
+    wippersnapper.analogio.AnalogIOAdd analogio_add       = 20;
+    wippersnapper.analogio.AnalogIORemove analogio_remove = 21;
+    // checkin.proto
+    wippersnapper.checkin.CheckinResponse checkin_response = 30;
+    // servo.proto
+    wippersnapper.servo.ServoAdd servo_add       = 40;
+    wippersnapper.servo.ServoRemove servo_remove = 41;
+    wippersnapper.servo.ServoWrite servo_write   = 42;
+    // pwm.proto
+    wippersnapper.pwm.PWMAdd pwm_add                              = 50;
+    wippersnapper.pwm.PWMRemove pwm_remove                        = 51;
+    wippersnapper.pwm.PWMWriteDutyCycle pwm_write_duty            = 52;
+    wippersnapper.pwm.PWMWriteDutyCycleMulti pwm_write_duty_multi = 53;
+    wippersnapper.pwm.PWMWriteFrequency pwm_write_freq            = 54;
+    // pixels.proto
+    wippersnapper.pixels.PixelsAdd pixels_add       = 60;
+    wippersnapper.pixels.PixelsRemove pixels_remove = 61;
+    wippersnapper.pixels.PixelsWrite pixels_write   = 62;
+    // ds18x20.proto
+    wippersnapper.ds18x20.Ds18x20Add ds18x20_add       = 70;
+    wippersnapper.ds18x20.Ds18x20Remove ds18x20_remove = 71;
+    // uart.proto
+    wippersnapper.uart.UartAdd uart_add       = 80;
+    wippersnapper.uart.UartRemove uart_remove = 81;
+    wippersnapper.uart.UartWrite uart_write = 82;
+    // i2c.proto
+    wippersnapper.i2c.I2cBusScan i2c_bus_scan                      = 90;
+    wippersnapper.i2c.I2cDeviceAddOrReplace i2c_device_add_replace = 91;
+    wippersnapper.i2c.I2cDeviceRemove i2c_device_remove            = 92;
+    wippersnapper.i2c.I2cDeviceOutputWrite i2c_device_output_write = 93;
+    // error.proto
+    wippersnapper.error.Error error = 100;
+  }
+}
+
+/*
+ * DeviceToBroker
+ * The DeviceToBroker message is sent from the device to the broker.
+ * It contains a oneof payload, which is a union of all the possible
+ * messages that can be sent from a device to the broker.
+ */
+message DeviceToBroker {
+  option (nanopb_msgopt).submsg_callback = true;
+  oneof payload {
+    //digitalio.proto
+    wippersnapper.digitalio.DigitalIOEvent digitalio_event = 10;
+    // analogio.proto
+    wippersnapper.analogio.AnalogIOEvent analogio_event = 20;
+    // checkin.proto
+    wippersnapper.checkin.CheckinRequest checkin_request = 30;
+    // servo.proto
+    wippersnapper.servo.ServoAdded servo_added = 40;
+    // pwm.proto
+    wippersnapper.pwm.PWMAdded pwm_added = 50;
+    // pixels.proto
+    wippersnapper.pixels.PixelsAdded pixels_added = 60;
+    // ds18x20.proto
+    wippersnapper.ds18x20.Ds18x20Added ds18x20_added = 70;
+    wippersnapper.ds18x20.Ds18x20Event ds18x20_event = 71;
+    // uart.proto
+    wippersnapper.uart.UartAdded uart_added            = 80;
+    wippersnapper.uart.UartWritten uart_written        = 81;
+    wippersnapper.uart.UartInputEvent uart_input_event = 82;
+    // i2c.proto
+    wippersnapper.i2c.I2cBusScanned i2c_bus_scanned                      = 90;
+    wippersnapper.i2c.I2cDeviceAddedOrReplaced i2c_device_added_replaced = 91;
+    wippersnapper.i2c.I2cDeviceRemoved i2c_device_removed                = 92;
+    wippersnapper.i2c.I2cDeviceEvent i2c_device_event                    = 93;
+    // gps.proto
+    wippersnapper.gps.GPSEvent gps_event = 100;
+  }
+}
diff --git a/proto/wippersnapper/signal/v1/signal.md b/proto/wippersnapper/signal/v1/signal.md
deleted file mode 100644
index bc137c85..00000000
--- a/proto/wippersnapper/signal/v1/signal.md
+++ /dev/null
@@ -1,26 +0,0 @@
-
-# signal.proto
-
-This file details the `signal.proto` message used to communicate between WipperSnapper clients. The signal file contains high-level `oneof` messages that "wrap" the following protocol buffer APIs: `pin.proto`, `i2c.proto`, `servo.proto`, `pwm.proto`, `ds18x20.proto`, `pixels .proto`, `uart.proto`.
-
-## Sequence Diagrams
-
-### Generalized `msgRequest` and `msgResponse`
-
-Within `signal.proto`, each `.proto` API contains both a `request` and `response` message. The `request` message is a command sent from the broker to a device. The `response` message is a command sent from the device to the broker.
-
-```mermaid
-sequenceDiagram
-autonumber
-
-IO Broker->>Device Client: ServoRequest
-Note over IO Broker,Device Client: /:username/wprsnpr/:clientId/signals/device/servo
-Device Client->>App: ServoRequest
-App->>(nanopb) Encoder/Decoder: ServoRequest
-(nanopb) Encoder/Decoder->>Component Class: ServoAttachRequest 
-Component Class->>(nanopb) Encoder/Decoder: Result of ServoAttachRequest
-(nanopb) Encoder/Decoder->>App: ServoResponse
-App->>Device Client: ServoResponse
-Device Client->>IO Broker: ServoResponse
-Note over Device Client,IO Broker: /:username/wprsnpr/:clientId/signals/broker/servo
-```
diff --git a/proto/wippersnapper/signal/v1/signal.proto b/proto/wippersnapper/signal/v1/signal.proto
deleted file mode 100644
index 14afa53c..00000000
--- a/proto/wippersnapper/signal/v1/signal.proto
+++ /dev/null
@@ -1,207 +0,0 @@
-// SPDX-FileCopyrightText: 2020-2025 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.signal.v1;
-
-// Non-WipperSnapper
-import "nanopb/nanopb.proto";
-
-// WipperSnapper
-import "wippersnapper/pin/v1/pin.proto";
-import "wippersnapper/i2c/v1/i2c.proto";
-import "wippersnapper/servo/v1/servo.proto";
-import "wippersnapper/pwm/v1/pwm.proto";
-import "wippersnapper/ds18x20/v1/ds18x20.proto";
-import "wippersnapper/pixels/v1/pixels.proto";
-import "wippersnapper/uart/v1/uart.proto";
-import "wippersnapper/display/v1/display.proto";
-
-/**
-* UARTRequest represents a UART command sent to a device.
-*/
-message UARTRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.uart.v1.UARTDeviceAttachRequest req_uart_device_attach = 1;
-    wippersnapper.uart.v1.UARTDeviceDetachRequest req_uart_device_detach = 2;
-  }
-}
-
-/**
-* UARTResponse represents a UART command from a device.
-*/
-message UARTResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.uart.v1.UARTDeviceAttachResponse resp_uart_device_attach = 1;
-    wippersnapper.uart.v1.UARTDeviceEvent resp_uart_device_event           = 2;
-  }
-}
-
-/**
-* Ds18x20Request represents a Ds18x20 command sent to a device.
-*/
-message Ds18x20Request {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.ds18x20.v1.Ds18x20InitRequest req_ds18x20_init     = 1;
-    wippersnapper.ds18x20.v1.Ds18x20DeInitRequest req_ds18x20_deinit = 2;
-  }
-}
-
-/**
-* Ds18x20Response represents a Ds18x20 message from the device.
-*/
-message Ds18x20Response {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.ds18x20.v1.Ds18x20InitResponse resp_ds18x20_init      = 1;
-    wippersnapper.ds18x20.v1.Ds18x20DeviceEvent  resp_ds18x20_event     = 2;
-  }
-}
-
-/**
-* I2CRequest represents the broker's request for a specific i2c command to a device.
-*/
-message I2CRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.i2c.v1.I2CBusInitRequest req_i2c_init                     = 1 [deprecated = true, (nanopb).type = FT_IGNORE];
-    wippersnapper.i2c.v1.I2CBusScanRequest req_i2c_scan                     = 2;
-    wippersnapper.i2c.v1.I2CBusSetFrequency req_i2c_set_freq                = 3;
-    wippersnapper.i2c.v1.I2CDeviceInitRequest req_i2c_device_init           = 4;
-    wippersnapper.i2c.v1.I2CDeviceDeinitRequest req_i2c_device_deinit       = 5;
-    wippersnapper.i2c.v1.I2CDeviceUpdateRequest req_i2c_device_update       = 6;
-    wippersnapper.i2c.v1.I2CDeviceInitRequests req_i2c_device_init_requests = 7;
-    wippersnapper.i2c.v1.I2CDeviceOutputWrite req_i2c_device_out_write      = 8;
-  }
-}
-
-/**
-* I2CResponse represents the device's response to an I2C-specific message from IO.
-*/
-message I2CResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.i2c.v1.I2CBusInitResponse resp_i2c_init                  = 1 [deprecated = true, (nanopb).type = FT_IGNORE];
-    wippersnapper.i2c.v1.I2CBusScanResponse resp_i2c_scan                  = 2;
-    wippersnapper.i2c.v1.I2CDeviceInitResponse resp_i2c_device_init        = 3;
-    wippersnapper.i2c.v1.I2CDeviceDeinitResponse resp_i2c_device_deinit    = 4;
-    wippersnapper.i2c.v1.I2CDeviceUpdateResponse resp_i2c_device_update    = 5;
-    wippersnapper.i2c.v1.I2CDeviceEvent resp_i2c_device_event              = 6;
-  }
-}
-
-/**
-* ServoRequest represents the broker's request across the servo sub-topic.
-*/
-message ServoRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.servo.v1.ServoAttachRequest servo_attach = 1;
-    wippersnapper.servo.v1.ServoDetachRequest servo_detach = 2;
-    wippersnapper.servo.v1.ServoWriteRequest servo_write   = 3;
-  }
-}
-
-/**
-* ServoResponse represents the device's response across the servo sub-topic.
-*/
-message ServoResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.servo.v1.ServoAttachResponse servo_attach_resp = 1;
-  }
-}
-
-/**
-* PixelsRequest represents the broker's request across the pixels sub-topic.
-*/
-message PixelsRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.pixels.v1.PixelsCreateRequest req_pixels_create = 1;
-    wippersnapper.pixels.v1.PixelsDeleteRequest req_pixels_delete = 2;
-    wippersnapper.pixels.v1.PixelsWriteRequest req_pixels_write   = 3;
-  }
-}
-
-message PixelsResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.pixels.v1.PixelsCreateResponse resp_pixels_create = 1;
-  }
-}
-
-message CreateSignalRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    // Create, update or remove a GPIO pin
-    wippersnapper.pin.v1.ConfigurePinRequests pin_configs               = 6;
-    // Update a pins state
-    wippersnapper.pin.v1.PinEvent pin_event                             = 15;
-    // Create, update or remove a PWM output pin
-    wippersnapper.pin.v1.ConfigurePWMPinRequests pwm_pin_config         = 10 [deprecated = true, (nanopb).type = FT_IGNORE];
-    // Write duty cycle to a PWM output pin
-    wippersnapper.pin.v1.PWMPinEvents pwm_pin_event                     = 12 [deprecated = true, (nanopb).type = FT_IGNORE];
-    // Update a pin's state
-    wippersnapper.pin.v1.PinEvents pin_events                           = 7;
-  }
-}
-
-/**
-* Response from a signal message payload (device->broker)
-*/
-message SignalResponse {
-  oneof payload {
-    bool configuration_complete = 1; /** True if a device successfully completed a ConfigurePinRequests message, False otherwise. */
-  }
-}
-
-/**
-* PWMRequest represents a broker's request across the PWM sub-topic.
-*/
-message PWMRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.pwm.v1.PWMAttachRequest attach_request                        = 1;
-    wippersnapper.pwm.v1.PWMDetachRequest detach_request                        = 2;
-    wippersnapper.pwm.v1.PWMWriteDutyCycleRequest write_duty_request            = 3;
-    wippersnapper.pwm.v1.PWMWriteDutyCycleMultiRequest write_duty_multi_request = 4;
-    wippersnapper.pwm.v1.PWMWriteFrequencyRequest write_freq_request            = 5;
-  }
-}
-
-/**
-* PWMRequest represents a devices's response across the PWM sub-topic.
-*/
-message PWMResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.pwm.v1.PWMAttachResponse attach_response = 1;
-  }
-}
-
-/**
-* DisplayRequest represents a broker's request across the display sub-topic.
-*/
-message DisplayRequest {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.display.v1.DisplayAddOrReplace display_add = 1;
-    wippersnapper.display.v1.DisplayRemove display_remove    = 2;
-    wippersnapper.display.v1.DisplayWrite display_write      = 3;
-  }
-}
-
-/**
-* DisplayResponse represents a devices's response across the display sub-topic.
-*/
-message DisplayResponse {
-  option (nanopb_msgopt).submsg_callback = true;
-  oneof payload {
-    wippersnapper.display.v1.DisplayAddedOrReplaced display_added = 1;
-    wippersnapper.display.v1.DisplayRemoved display_removed       = 2;
-  }
-}
\ No newline at end of file
diff --git a/proto/wippersnapper/uart.options b/proto/wippersnapper/uart.options
new file mode 100644
index 00000000..fc65d052
--- /dev/null
+++ b/proto/wippersnapper/uart.options
@@ -0,0 +1,13 @@
+# uart.options
+wippersnapper.uart.UartAdd.device_id                              max_size: 32
+wippersnapper.uart.UartDeviceConfig.device_id                     max_size: 32
+wippersnapper.uart.UartAdded.device_id                            max_size: 32
+wippersnapper.uart.UartRemove.device_id                           max_size: 32
+wippersnapper.uart.UartWrite.device_id                            max_size: 32
+wippersnapper.uart.UartWritten.device_id                          max_size: 32
+wippersnapper.uart.UartSerialConfig.pin_rx                        max_size: 16
+wippersnapper.uart.UartSerialConfig.pin_tx                        max_size: 16
+wippersnapper.uart.GenericUartInputConfig.sensor_types            max_count:15
+wippersnapper.uart.PM25AQIConfig.sensor_types                     max_count:15
+wippersnapper.uart.UartInputEvent.device_id                       max_size:16
+wippersnapper.uart.UartInputEvent.events                          max_count:15
\ No newline at end of file
diff --git a/proto/wippersnapper/uart.proto b/proto/wippersnapper/uart.proto
new file mode 100644
index 00000000..a3f7f146
--- /dev/null
+++ b/proto/wippersnapper/uart.proto
@@ -0,0 +1,200 @@
+// SPDX-FileCopyrightText: 2023-2025 Brent Rubell for Adafruit Industries
+// SPDX-License-Identifier: MIT
+syntax = "proto3";
+package wippersnapper.uart;
+import "gps.proto";
+import "sensor.proto";
+
+/**
+* UartPacketFormat contains the configuration data, parity, and stop bits for a serial port.
+*/
+enum UartPacketFormat {
+  UART_PACKET_FORMAT_UNSPECIFIED = 0; // Format was not specified by IO.
+  UART_PACKET_FORMAT_8N1 = 1; // 8 data bits, no parity, 1 stop bit
+  UART_PACKET_FORMAT_5N1 = 2; // 5 data bits, no parity, 1 stop bit
+  UART_PACKET_FORMAT_6N1 = 3; // 6 data bits, no parity, 1 stop bit
+  UART_PACKET_FORMAT_7N1 = 4; // 7 data bits, no parity, 1 stop bit
+  UART_PACKET_FORMAT_5N2 = 5; // 5 data bits, no parity, 2 stop bits
+  UART_PACKET_FORMAT_6N2 = 6; // 6 data bits, no parity, 2 stop bits
+  UART_PACKET_FORMAT_7N2 = 7; // 7 data bits, no parity, 2 stop bits
+  UART_PACKET_FORMAT_8N2 = 8; // 8 data bits, no parity, 2 stop bits
+  UART_PACKET_FORMAT_5E1 = 9; // 5 data bits, even parity, 1 stop bit
+  UART_PACKET_FORMAT_6E1 = 10; // 6 data bits, even parity, 1 stop bit
+  UART_PACKET_FORMAT_7E1 = 11; // 7 data bits, even parity, 1 stop bit
+  UART_PACKET_FORMAT_8E1 = 12; // 8 data bits, even parity, 1 stop bit
+  UART_PACKET_FORMAT_5E2 = 13; // 5 data bits, even parity, 2 stop bits
+  UART_PACKET_FORMAT_6E2 = 14; // 6 data bits, even parity, 2 stop bits
+  UART_PACKET_FORMAT_7E2 = 15; // 7 data bits, even parity, 2 stop bits
+  UART_PACKET_FORMAT_8E2 = 16; // 8 data bits, even parity, 2 stop bits
+  UART_PACKET_FORMAT_5O1 = 17; // 5 data bits, odd parity, 1 stop bit
+  UART_PACKET_FORMAT_6O1 = 18; // 6 data bits, odd parity, 1 stop bit
+  UART_PACKET_FORMAT_7O1 = 19; // 7 data bits, odd parity, 1 stop bit
+  UART_PACKET_FORMAT_8O1 = 20; // 8 data bits, odd parity, 1 stop bit
+  UART_PACKET_FORMAT_5O2 = 21; // 5 data bits, odd parity, 2 stop bits
+  UART_PACKET_FORMAT_6O2 = 22; // 6 data bits, odd parity, 2 stop bits
+  UART_PACKET_FORMAT_7O2 = 23; // 7 data bits, odd parity, 2 stop bits
+  UART_PACKET_FORMAT_8O2 = 24; // 8 data bits, odd parity, 2 stop bits
+}
+
+/**
+* UartDeviceType represents the type of device connected to the UART port.
+* This is used to determine the driver to use for the device.
+*/
+enum UartDeviceType {
+  UART_DEVICE_TYPE_UNSPECIFIED    = 0; /** Unspecified device type. */
+  UART_DEVICE_TYPE_GENERIC_INPUT  = 1; /** Use UART input. */
+  UART_DEVICE_TYPE_GENERIC_OUTPUT = 2; /** Use the Generic UART output driver. */
+  UART_DEVICE_TYPE_GPS            = 3; /** Use the GPS driver. */
+  UART_DEVICE_TYPE_PM25AQI        = 4; /** Use the PM2.5 driver. */
+  UART_DEVICE_TYPE_TM22XX         = 5; /** Use the TM22XX stepper driver. */
+}
+
+/**
+* GenericDeviceLineEnding represents the line ending used by the device.
+* This is used to determine how to parse the incoming data.
+*/
+enum GenericDeviceLineEnding {
+  GENERIC_DEVICE_LINE_ENDING_UNSPECIFIED    = 0; /** Unspecified line ending. */
+  GENERIC_DEVICE_LINE_ENDING_LF             = 1; /** Newline (LF). */
+  GENERIC_DEVICE_LINE_ENDING_CRLF           = 2; /** Carriage return (CR) and newline (LF). */
+  GENERIC_DEVICE_LINE_ENDING_TIMEOUT_100MS  = 3; /** 100ms timeout - sensor returns every 100ms with a new Event. */
+  GENERIC_DEVICE_LINE_ENDING_TIMEOUT_1000MS = 4; /** 1s timeout - device returns every 1s with a new Event. */
+}
+
+/**
+* UartSerialConfig represents a message to configure the Serial port (eg: Hardware Serial, Software Serial).
+* This message is never sent directly, it is packed inside UartAdd.
+*/
+message UartSerialConfig {
+  string pin_rx           = 1; /** The pin on which to receive on. */
+  string pin_tx           = 2; /** The pin on which to transmit with. */
+  string device_name      = 3; /** (For CPython ONLY) The device name, i.e: /dev/ttyUSB0. */
+  uint32 uart_nbr         = 4; /** The UART port number to use, eg: 0, 1, 2, etc. */
+  uint32 baud_rate        = 5; /** The desired baudrate, in bits per second. */
+  UartPacketFormat format = 6; /** The data, parity, and stop bits. */
+  float timeout           = 7; /** Maximum milliseconds to wait for serial data. Defaults to 1000 ms. */
+  bool use_sw_serial      = 8; /** Use software serial instead of hardware serial. Defaults to False. */
+  bool sw_serial_invert   = 9; /** Optional: Inverts the UART signal on RX and TX pins. Defaults to False. */
+}
+
+/**
+* GenericUartInputConfig represents a message sent from IO to a device
+* containing device-specific configuration info for generic UART input devices.
+*/
+message GenericUartInputConfig {
+  string name                                            = 1; /** The name used to identify the device. */
+  GenericDeviceLineEnding line_ending                    = 2; /** The line ending used by the device. */
+  int32 period                                           = 3; /** The period to poll the device, in milliseconds */
+  repeated wippersnapper.sensor.SensorType sensor_types  = 4; /** SI Types for each sensor on the UART device. */
+}
+
+/**
+* TrinamicDynamixelConfig represents a message sent from IO to a device
+* containing device-specific configuration info for Trinamic stepper or DYNAMIXEL servos.
+*/
+message TrinamicDynamixelConfig {
+  uint32 device_id = 1; /** The device identifier, used for sub-addressing (multiple servos on one UART). */
+}
+
+/**
+* PM25AQIConfig represents a message sent from IO to a device
+* containing device-specific configuration info for PM2.5 AQI sensors.
+*/
+message PM25AQIConfig {
+  bool is_pm1006                                        = 1; /** True if the device is a PM1006 AQ sensor, Defaults to False. */
+  int32 period                                          = 2; /** The period to poll the device, in milliseconds */
+  repeated wippersnapper.sensor.SensorType sensor_types = 3; /** SI Types for each sensor on the I2c device. */
+}
+
+/**
+* UartDeviceConfig represents a message sent from IO to a device
+* containing device-specific configuration data.
+* This message is never sent directly, it is packed inside UartAdd.
+*/
+message UartDeviceConfig {
+  UartDeviceType device_type                        = 1; /** The type of device connected to the UART port. */
+  string device_id                                  = 2; /** The unique identifier string for the UART device. */
+  oneof config {
+    GenericUartInputConfig generic_uart_input  = 3; /** OPTIONAL configuration for a generic UART input device. */
+    TrinamicDynamixelConfig trinamic_dynamixel = 4; /** OPTIONAL configuration for a Trinamic stepper or DYNAMIXEL servo. */
+    PM25AQIConfig pm25aqi                      = 5; /** OPTIONAL configuration for a PM2.5 AQI sensor. */
+    wippersnapper.gps.GPSConfig gps            = 6; /** OPTIONAL configuration for a GPS RMC response. */
+  }
+}
+
+/**
+* UartAdd represents a message sent from IO to a device
+* to configure a device on a UART port for communication.
+*/
+message UartAdd {
+  UartSerialConfig cfg_serial  = 1; /** The Serial configuration. */
+  UartDeviceConfig cfg_device  = 2; /** The device-specific configuration. */
+}
+
+/**
+* UartAdded represents a message sent from a device to IO to
+* confirm that a device has been attached to the UART port.
+*/
+message UartAdded {
+  // Addressing
+  uint32 uart_nbr     = 1; /** The UART port number (eg: 0, 1, 2, etc.) that the device was attached to. */
+  UartDeviceType type = 2; /** The category of device attached to the UART port, corresponds to its driver type. */
+  string device_id    = 3; /** The unique identifier string for the UART device. */
+  // Payload
+  bool success        = 4; /** True if the device on the UART port was successfully initialized, False otherwise. */
+}
+
+/*
+* UartRemove represents a message sent from IO to a device
+* to detach a driver from the UART port and deinitialize the port.
+*/
+message UartRemove {
+  // Addressing
+  uint32 uart_nbr     = 1; /** The UART port number (eg: 0, 1, 2, etc.) that the device is attached to. */
+  UartDeviceType type = 2; /** The category of device attached to the UART port, corresponds to its driver type. */
+  string device_id    = 3; /** The unique identifier string for the UART device. */
+}
+
+/**
+* UartWrite represents a message sent from IO to a device
+* to write data to a device.
+*/
+message UartWrite {
+  // Addressing
+  uint32 uart_nbr     = 1; /** The UART port number (eg: 0, 1, 2, etc.) that the device is attached to. */
+  UartDeviceType type = 2; /** The category of device attached to the UART port, corresponds to its driver type. */
+  string device_id    = 3; /** The unique identifier string for the UART device. */
+  // Payload
+  oneof payload {
+    bytes bytes_data  = 4; /** Raw data to send to the device, corresponds to the Wiring API Serial.write(). */
+    string text_data  = 5; /** String to send to the device, corresponds to the Wiring API Serial.print(). */
+  }
+}
+
+/**
+* UartWritten represents the number of bytes written to a device.enum
+* This message is sent from a device to IO to confirm that data has been written to the device.
+*/
+message UartWritten {
+  // Addressing
+  uint32 uart_nbr      = 1; /** The UART port number (eg: 0, 1, 2, etc.) that the device is attached to. */
+  UartDeviceType type  = 2; /** The category of device attached to the UART port, corresponds to its driver type. */
+  string device_id     = 3; /** The unique identifier string for the UART device. */
+  // Payload
+  uint32 bytes_written = 4; /** The number of bytes written to the device. */
+}
+
+/**
+* UartInputEvent represents a message sent from a device to IO
+* containing data from a UART input device.
+* This message is sent from a device to IO to report sensor data.
+* It can contain multiple SensorEvents if the device has multiple sensors.
+*/
+message UartInputEvent {
+  // Addressing
+  uint32 uart_nbr     = 1; /** The UART port number (eg: 0, 1, 2, etc.) that the device is attached to. */
+  UartDeviceType type = 2; /** The category of device attached to the UART port, corresponds to its driver type. */
+  string device_id    = 3; /** The unique identifier string for the UART device. */
+  // Payload
+  repeated wippersnapper.sensor.SensorEvent events = 4; /** Required, but optionally repeated, SensorEvent from a sensor. */
+}
\ No newline at end of file
diff --git a/proto/wippersnapper/uart/v1/uart.proto b/proto/wippersnapper/uart/v1/uart.proto
deleted file mode 100644
index ba0fa502..00000000
--- a/proto/wippersnapper/uart/v1/uart.proto
+++ /dev/null
@@ -1,53 +0,0 @@
-// SPDX-FileCopyrightText: 2023 Brent Rubell for Adafruit Industries
-// SPDX-License-Identifier: MIT
-syntax = "proto3";
-
-package wippersnapper.uart.v1;
-import "nanopb/nanopb.proto";
-import "wippersnapper/i2c/v1/i2c.proto";
-
-/**
-* UARTBusData represents a message to configure a UART bus for communication with a device.
-* NOTE: This message is never sent directly, it is packed inside UARTDeviceAttachRequest.
-*/
-message UARTBusData {
-  int32 baudrate        = 1; /** The baudrate to use for UART communication (may be a common baud rate such as: 1200bps, 2400bps, 4800bps, 19200bps, 38400bps, 57600bps, or 115200bps). */
-  string pin_rx         = 2[(nanopb).max_size = 6]; /** The pin on which to receive UART stream data. */
-  string pin_tx         = 3[(nanopb).max_size = 6]; /** The pin on which to transmit UART stream data. */
-  bool is_invert        = 4; /** Inverts the UART signal on RX and TX pins. Defaults to False. */
-}
-
-/**
-* UARTDeviceAttachRequest represents a message sent from IO to a device
-* to configure the UART bus (if not already configured) and attach a device.
-*/
-message UARTDeviceAttachRequest {
-  UARTBusData bus_info     = 1; /** The UART bus configuration. */
-  string device_id         = 2[(nanopb).max_size = 15]; /** The unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */
-  int32 polling_interval   = 3; /** The polling interval, in milliseconds, to use for the device. */
-}
-
-/**
-* UARTDeviceAttachResponse represents a message sent from a device to IO to
-* confirm that a device has been attached to the UART bus.
-*/
-message UARTDeviceAttachResponse {
-  string device_id = 1[(nanopb).max_size = 15]; /** The unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */
-  bool is_success  = 2; /** True if the UARTInitRequest was successful, False otherwise. */
-}
-
-/*
-* UARTDeviceDetachRequest represents a message sent from IO to a device
-* to detach a device from the UART bus.
-*/
-message UARTDeviceDetachRequest {
-    string device_id = 1[(nanopb).max_size = 15]; /** The unique identifier of the device to detach from the UART bus. */
-}
-
-/**
-* UARTDeviceEvent represents incoming data from a UART sensor.
-*/
-message UARTDeviceEvent {
-  string device_id = 1[(nanopb).max_size = 15];                    /** The unique identifier of the device to attach to the UART bus, from Adafruit_WipperSnapper_Components. */
-  repeated wippersnapper.i2c.v1.SensorEvent sensor_event  = 2[(nanopb).max_count = 15]; /** A, optionally repeated, SensorEvent from a sensor. */
-}
diff --git a/protolint.yml b/protolint.yml
new file mode 100644
index 00000000..66e4f711
--- /dev/null
+++ b/protolint.yml
@@ -0,0 +1,17 @@
+# Protolint configuration file for WipperSnapper
+# configuration file reference: https://github.com/yoheimuta/protolint/blob/master/_example/config/.protolint.yaml
+---
+lint:
+  rules:
+    all_default: true
+  rules_option:
+    max_line_length:
+      max_chars: 150
+      tab_chars: 2
+  ignores:
+    - id: FIELDS_HAVE_COMMENT
+      files:
+        - proto/wippersnapper/signal.proto
+    - id: MESSAGE_NAMES_EXCLUDE_PREPOSITIONS
+      files:
+        - proto/wippersnapper/signal.proto
\ No newline at end of file