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 +"""