Skip to content

Commit

Permalink
Merge pull request #33 from BrewBlox/develop
Browse files Browse the repository at this point in the history
Edge release
  • Loading branch information
steersbob authored Feb 23, 2024
2 parents 3732c80 + b1fd0bc commit 7b2f120
Show file tree
Hide file tree
Showing 21 changed files with 2,134 additions and 1,144 deletions.
14 changes: 7 additions & 7 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,21 @@ jobs:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- uses: actions/setup-python@v4
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Get image metadata
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: ${{ env.DOCKER_IMAGE }}

- name: ghcr.io login
uses: docker/login-action@v2
uses: docker/login-action@v3
if: github.event_name != 'pull_request'
with:
registry: ghcr.io
Expand All @@ -53,7 +53,7 @@ jobs:
poetry run invoke build
- name: Build Docker image
uses: docker/build-push-action@v4
uses: docker/build-push-action@v5
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ ENV VENV=/app/.venv
ENV PATH="$VENV/bin:$PATH"

COPY --from=base /wheeley /wheeley
COPY ./parse_appenv.py ./parse_appenv.py
COPY ./entrypoint.sh ./entrypoint.sh

RUN <<EOF
set -ex
Expand All @@ -36,4 +38,5 @@ RUN <<EOF
rm -rf /wheeley
EOF

ENTRYPOINT ["python3", "-m", "brewblox_hass"]

ENTRYPOINT ["bash", "./entrypoint.sh"]
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ services:
hass:
image: ghcr.io/brewblox/brewblox-hass:${BREWBLOX_RELEASE}
restart: unless-stopped
command: '--hass-mqtt-host=HOSTNAME'
environment:
- BREWBLOX_HASS_HASS_MQTT_HOST={ADDRESS}
```
Replace `HOSTNAME` with the hostname or IP address of the machine where your HA mosquitto broker is running.
Replace `{ADDRESS}` with the hostname or IP address of the machine where your HA mosquitto broker is running.

If your broker requires a username and password, add the following entries to your `brewblox/.env` file:

Expand All @@ -33,7 +34,7 @@ HASS_MQTT_USERNAME=changeme
HASS_MQTT_PASSWORD=changeme
```

Then extend the configuration of your `hass` service:
Then extend your `hass` service environment:

```yml
version: '3.7'
Expand All @@ -42,8 +43,8 @@ services:
hass:
image: ghcr.io/brewblox/brewblox-hass:${BREWBLOX_RELEASE}
restart: unless-stopped
command: '--hass-mqtt-host=HOSTNAME'
environment:
- BREWBLOX_HASS_HASS_MQTT_HOST={HOSTNAME}
- HASS_MQTT_USERNAME
- HASS_MQTT_PASSWORD
```
50 changes: 0 additions & 50 deletions brewblox_hass/__main__.py

This file was deleted.

48 changes: 48 additions & 0 deletions brewblox_hass/app_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging
from contextlib import AsyncExitStack, asynccontextmanager
from pprint import pformat

from fastapi import FastAPI

from . import mqtt, relay, utils

LOGGER = logging.getLogger(__name__)


def setup_logging(debug: bool):
level = logging.DEBUG if debug else logging.INFO
unimportant_level = logging.INFO if debug else logging.WARN
format = '%(asctime)s.%(msecs)03d [%(levelname).1s:%(name)s:%(lineno)d] %(message)s'
datefmt = '%Y/%m/%d %H:%M:%S'

logging.basicConfig(level=level, format=format, datefmt=datefmt)
logging.captureWarnings(True)

logging.getLogger('gmqtt').setLevel(unimportant_level)
logging.getLogger('httpx').setLevel(unimportant_level)
logging.getLogger('httpcore').setLevel(logging.WARN)
logging.getLogger('uvicorn.access').setLevel(unimportant_level)
logging.getLogger('uvicorn.error').disabled = True
logging.getLogger('bleak.backends.bluezdbus.manager').setLevel(unimportant_level)


@asynccontextmanager
async def lifespan(app: FastAPI):
LOGGER.info(utils.get_config())
LOGGER.debug('LOGGERS:\n' + pformat(logging.root.manager.loggerDict))

async with AsyncExitStack() as stack:
await stack.enter_async_context(mqtt.lifespan())
yield


def create_app() -> FastAPI:
config = utils.get_config()
setup_logging(config.debug)

# Call setup functions for modules
mqtt.setup()
relay.setup()

app = FastAPI(lifespan=lifespan)
return app
41 changes: 34 additions & 7 deletions brewblox_hass/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
from typing import Optional
from typing import Literal

from brewblox_service.models import BaseServiceConfig, MqttProtocol
from pydantic_settings import BaseSettings, SettingsConfigDict


class ServiceConfig(BaseServiceConfig):
hass_mqtt_protocol: MqttProtocol
hass_mqtt_host: str
hass_mqtt_port: Optional[int]
hass_mqtt_path: str
class ServiceConfig(BaseSettings):
model_config = SettingsConfigDict(
env_file='.appenv',
env_prefix='brewblox_hass_',
case_sensitive=False,
json_schema_extra='ignore',
)

name: str = 'hass'
debug: bool = False
debugger: bool = False

mqtt_protocol: Literal['mqtt', 'mqtts'] = 'mqtt'
mqtt_host: str = 'eventbus'
mqtt_port: int = 1883

hass_mqtt_protocol: Literal['mqtt', 'mqtts'] = 'mqtt'
hass_mqtt_host: str = 'eventbus'
hass_mqtt_port: int = 1883

state_topic: str = 'brewcast/state'


class HassMqttCredentials(BaseSettings):
model_config = SettingsConfigDict(
env_prefix='hass_',
case_sensitive=False,
json_schema_extra='ignore',
)

mqtt_username: str | None = None
mqtt_password: str | None = None
48 changes: 48 additions & 0 deletions brewblox_hass/mqtt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from contextlib import AsyncExitStack, asynccontextmanager
from contextvars import ContextVar

from fastapi_mqtt.config import MQTTConfig
from fastapi_mqtt.fastmqtt import FastMQTT

from . import utils

CV_LOCAL: ContextVar[FastMQTT] = ContextVar('mqtt.client.local')
CV_HASS: ContextVar[FastMQTT] = ContextVar('mqtt.client.hass')


def setup():
config = utils.get_config()
hass_credentials = utils.get_hass_credentials()

mqtt_config = MQTTConfig(host=config.mqtt_host,
port=config.mqtt_port,
ssl=(config.mqtt_protocol == 'mqtts'),
reconnect_retries=-1)
fmqtt = FastMQTT(config=mqtt_config)
CV_LOCAL.set(fmqtt)

hass_mqtt_config = MQTTConfig(host=config.hass_mqtt_host,
port=config.hass_mqtt_port,
ssl=(config.hass_mqtt_protocol == 'mqtts'),
username=hass_credentials.mqtt_username,
password=hass_credentials.mqtt_password,
reconnect_retries=-1)
hass_fmqtt = FastMQTT(config=hass_mqtt_config)
CV_HASS.set(hass_fmqtt)


@asynccontextmanager
async def mqtt_lifespan(fmqtt: FastMQTT):
await fmqtt.connection()
yield
await fmqtt.client.disconnect()


@asynccontextmanager
async def lifespan():
async with AsyncExitStack() as stack:
# Order matters here: we want to be able to publish
# before we start receiving messages
await stack.enter_async_context(mqtt_lifespan(CV_HASS.get()))
await stack.enter_async_context(mqtt_lifespan(CV_LOCAL.get()))
yield
Loading

0 comments on commit 7b2f120

Please sign in to comment.