diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8ec14bb..34dea03 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -3,7 +3,7 @@
-
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 45acbc0..4c43964 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -98,7 +98,7 @@ repos:
- id: check-toml
- id: check-yaml
# this fails on esphome's !secret notation
- exclude: ^esphome-config\.yaml$
+ exclude: ^esphome-configs/.*$
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
diff --git a/docs/source/hardware.rst b/docs/source/hardware.rst
index b8cff96..774757d 100644
--- a/docs/source/hardware.rst
+++ b/docs/source/hardware.rst
@@ -3,4 +3,27 @@
Hardware
========
-TBD.
+The "machine control unit" devices use ESP32 microcontrollers; our reference builds use ESP32-DevKitC / ESP32-WROOM-32D boards such as `these from Amazon _. For reasons described in :ref:`the introduction ` we use `ESPHome `__ as the software on the ESP32s.
+
+.. _hardware.esphome-configs:
+
+ESPHome Configurations
+----------------------
+
+Example ESPHome configurations for various ESPHome versions and various hardware combinations can be found in the `esphome-configs/ directory of the git repo `__ broken down by ESPHome version.
+
+All of the example ESPHome configurations begin with a ``substitutions`` key, which contains a ``machine_name`` substitution. This must be set to the same name as used in the :ref:`configuration.machines-json` config file. If desired, you can override the ``esphome`` ``name`` and ``friendly_name`` values (though this is not recommended).
+
+The ESPHome configurations are based on a `ESPHome secrets.yaml file `__ for substituting in sensitive values and installation-specific values using the ``!secrets`` substitution operator. The example configurations expect the following secrets to be defined:
+
+ota_password
+ A password used for OTA updates from ESPHome.
+
+wifi_ssid
+ WiFi network SSID to connect to.
+
+wifi_password
+ WiFi network password.
+
+domain_name
+ Domain name to use for DNS.
diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst
index 0bc2587..45b19d6 100644
--- a/docs/source/introduction.rst
+++ b/docs/source/introduction.rst
@@ -14,12 +14,16 @@ powers the RFID-based door access control to the makerspace, dm-mac uses
the `Neon CRM `__ as its source for user data,
though that is completely optional and pluggable.
+.. _introduction.software:
+
Software Components
-------------------
At a high level, the system is made up of the central control server and
the ESPHome configuration for the ESP32’s.
+.. _introduction.control-server:
+
Control Server
~~~~~~~~~~~~~~
@@ -39,6 +43,8 @@ the business logic contained in a central server with relatively “dumb”
machine control units on the machines allows for simpler management of
the system.
+.. _introduction.mcu-software:
+
Machine Control Unit Software
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/esphome-config.yaml b/esphome-configs/2024.6.4/no-current-input.yaml
similarity index 94%
rename from esphome-config.yaml
rename to esphome-configs/2024.6.4/no-current-input.yaml
index 88d3af9..a21d5f7 100644
--- a/esphome-config.yaml
+++ b/esphome-configs/2024.6.4/no-current-input.yaml
@@ -1,6 +1,9 @@
+substitutions:
+ machine_name: "MY_HOSTNAME"
+
esphome:
- name: "esp32test"
- friendly_name: "esp32test"
+ name: ${machine_name}
+ friendly_name: ${machine_name}
esp32:
board: esp32dev
@@ -11,13 +14,9 @@ esp32:
logger:
level: DEBUG
-# Enable Home Assistant API
-api:
- encryption:
- key: !secret api_encryption_key
-
ota:
password: !secret ota_password
+ platform: esphome
wifi:
ssid: !secret wifi_ssid
diff --git a/noxfile.py b/noxfile.py
index 6e49e2e..dc95920 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -258,7 +258,7 @@ def docs(session: Session) -> None:
session.install(".")
args = session.posargs or ["-b", "html", "docs/source", "docs/build", "-E", "-W"]
- if session.interactive and not session.posargs:
+ if os.environ.get("DOCS_REBUILD") == "true" and not session.posargs:
args = ["-a", "--watch=docs/source/_static", "--open-browser", *args]
builddir = Path("docs", "build")
diff --git a/src/dm_mac/__init__.py b/src/dm_mac/__init__.py
index 3ea07e4..5e1a9f8 100644
--- a/src/dm_mac/__init__.py
+++ b/src/dm_mac/__init__.py
@@ -2,13 +2,16 @@
from flask import Flask
+from dm_mac.views.api import api
+from dm_mac.views.machine import machineapi
+
+
+# see: https://github.com/pallets/flask/issues/4786#issuecomment-1416354177
+api.register_blueprint(machineapi)
+
def create_app() -> Flask:
"""Factory to create the app."""
app: Flask = Flask("dm_mac")
-
- from dm_mac.views.api import api
-
app.register_blueprint(api)
-
return app
diff --git a/src/dm_mac/views/api.py b/src/dm_mac/views/api.py
index 948b94b..8a8b05d 100644
--- a/src/dm_mac/views/api.py
+++ b/src/dm_mac/views/api.py
@@ -9,4 +9,4 @@
@api.route("/")
def index() -> str:
"""Main API index route - placeholder."""
- return "Hello, World!"
+ return "Nothing to see here..."
diff --git a/src/dm_mac/views/machine.py b/src/dm_mac/views/machine.py
index 34cdd9a..4d8d0c3 100644
--- a/src/dm_mac/views/machine.py
+++ b/src/dm_mac/views/machine.py
@@ -4,19 +4,22 @@
from logging import getLogger
from typing import Any
from typing import Dict
+from typing import Tuple
from typing import cast
from flask import Blueprint
+from flask import Response
+from flask import jsonify
from flask import request
logger: Logger = getLogger(__name__)
-machine: Blueprint = Blueprint("machine", __name__, url_prefix="/machine")
+machineapi: Blueprint = Blueprint("machine", __name__, url_prefix="/machine")
-@machine.route("/update", methods=["POST"])
-def update() -> str:
+@machineapi.route("/update", methods=["POST"])
+def update() -> Tuple[Response, int]:
"""API method to update machine state.
Accepts POSTed JSON containing the following key/value pairs:
@@ -40,4 +43,4 @@ def update() -> str:
# method for this)
# check if this data would update the state; if not, just call
# noop_update() and return the same display value
- return "Not implemented."
+ return jsonify({"error": "not implemented"}), 501
diff --git a/tests/conftest.py b/tests/conftest.py
index d455859..e9422ab 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -19,11 +19,8 @@ def app() -> Generator[Flask, None, None]:
"TESTING": True,
}
)
-
# other setup can go here
-
yield app
-
# clean up / reset resources here
diff --git a/tests/views/test_api.py b/tests/views/test_api.py
index cd43796..6eefe5d 100644
--- a/tests/views/test_api.py
+++ b/tests/views/test_api.py
@@ -11,5 +11,5 @@ def test_index_response(self, client: FlaskClient) -> None:
"""Test for API index response."""
response: TestResponse = client.get("/api/")
assert response.status_code == 200
- assert response.text == "Hello, World!"
+ assert response.text == "Nothing to see here..."
assert response.headers["Content-Type"] == "text/html; charset=utf-8"
diff --git a/tests/views/test_machine.py b/tests/views/test_machine.py
new file mode 100644
index 0000000..3faf411
--- /dev/null
+++ b/tests/views/test_machine.py
@@ -0,0 +1,24 @@
+"""Tests for /machine API endpoints."""
+
+from flask.testing import FlaskClient
+from werkzeug.test import TestResponse
+
+
+class TestUpdate:
+ """Tests for /machine/update API endpoint."""
+
+ def test_noop_update_idle_machine(self, client: FlaskClient) -> None:
+ """Test for API index response."""
+ response: TestResponse = client.post("/api/machine/update", json={"foo": "bar"})
+ assert response.status_code == 501
+ assert response.json == {"error": "not implemented"}
+
+
+"""
+Questions:
+
+1. Where do I load the machine/user configs? In create_app()?
+2. Do I _need_ to use `app.test_client()` via the pytest fixture, or can I just
+import create_app() here and call its .test_client() within my test methods?
+3. If not, how do I handle setup of the configs unique for each
+"""