refactor: extract mqtt_demo/ — move bridge, config, logger, appliance descriptors

This commit is contained in:
Marc Billow
2026-07-04 16:11:10 -05:00
parent 24fc197a9e
commit aa9ec0f0d9
17 changed files with 51 additions and 42 deletions
+8 -5
View File
@@ -3,12 +3,15 @@ FROM python:3.11-slim
WORKDIR /app
# Python deps first so layer cache survives code changes
COPY requirements.txt .
COPY mqtt_demo/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Application code
COPY main.py .
COPY samsung_appliance/ ./samsung_appliance/
# Application code — build context is the repo root (see
# docker-compose.yml's `context: ..`) since mqtt_demo/ depends on the
# protocol/ and ocf/ library packages that live alongside it.
COPY protocol/ ./protocol/
COPY ocf/ ./ocf/
COPY mqtt_demo/ ./mqtt_demo/
# /config holds the ab0b0ac4 client cert + key. Mount from the host so
# secrets aren't baked into the image.
@@ -28,4 +31,4 @@ ENV CERT_PATH=/config/ab0b0ac4_fullchain.pem \
# No port — bridge is outbound-only (DTLS UDP to appliance, MQTT to broker).
CMD ["python", "main.py"]
CMD ["python", "-m", "mqtt_demo"]
View File
+4 -4
View File
@@ -23,10 +23,10 @@ import threading
import paho.mqtt.client as mqtt
from samsung_appliance.appliances import get_descriptor
from samsung_appliance.bridge import PushBridge
from samsung_appliance.config import SharedConfig, load_appliances
from samsung_appliance.logger import logger
from mqtt_demo.samples import get_descriptor
from mqtt_demo.bridge import PushBridge
from mqtt_demo.config import SharedConfig, load_appliances
from mqtt_demo.logger import logger
def main():
@@ -23,15 +23,17 @@ import time
import cbor2
from .appliances.base import ApplianceDescriptor, bridge_diagnostic_discovery
from .config import ApplianceConfig, SharedConfig
from .logger import bridge_logger
from protocol.dtls_session import DtlsCoapSession, fmt_code
from ocf.keepalive import KeepaliveTask
from ocf.observe_refresh import ObserveRefreshTask
from ocf.poll_scheduler import PollScheduler
from ocf.state_cache import StateCache
from .descriptor import ApplianceDescriptor, bridge_diagnostic_discovery
from .config import ApplianceConfig, SharedConfig
from .logger import bridge_logger
DEBUG_BRIDGE = os.environ.get('DEBUG_BRIDGE') == '1'
+20 -15
View File
@@ -20,8 +20,15 @@
# certs in $APPDATA_DIR are preserved.
set -e
if [ ! -f .env ]; then
echo "Error: .env file not found. Copy .env.example to .env and configure it."
# This script lives in mqtt_demo/ but the build context is the repo
# root (mqtt_demo/docker-compose.yml uses `context: ..`, since the
# image needs the protocol/ and ocf/ library packages alongside
# mqtt_demo/). Run everything from the repo root so the tar allowlist
# and remote layout line up with that context.
cd "$(dirname "$0")/.."
if [ ! -f mqtt_demo/.env ]; then
echo "Error: mqtt_demo/.env file not found. Copy mqtt_demo/.env.example to mqtt_demo/.env and configure it."
exit 1
fi
@@ -29,7 +36,7 @@ fi
# Sourcing would tokenize unquoted spaces in values (e.g.
# `APPLIANCE_1_NAME=Samsung Dryer`) as shell commands.
get_env() {
grep -E "^${1}=" .env | head -1 | cut -d= -f2-
grep -E "^${1}=" mqtt_demo/.env | head -1 | cut -d= -f2-
}
SSH_HOST=$(get_env SSH_HOST)
REMOTE_DIR=$(get_env REMOTE_DIR)
@@ -44,22 +51,20 @@ ssh "${SSH_HOST}" mkdir -p "${REMOTE_DIR}" "${APPDATA_DIR}"
# Source code — explicit allowlist instead of an excludelist. Anything
# else in the repo (research files, certs, logs, the .git dir) stays
# local.
# local. protocol/ and ocf/ are the library packages mqtt_demo/ imports
# from; they need to land as REMOTE_DIR's siblings of mqtt_demo/ so the
# compose file's `context: ..` resolves the same way it does locally.
COPYFILE_DISABLE=1 tar cz \
main.py \
samsung_appliance/ \
Dockerfile \
docker-compose.yml \
requirements.txt \
deploy.sh \
protocol/ \
ocf/ \
mqtt_demo/ \
README.md \
.env.example \
.gitignore \
| ssh "${SSH_HOST}" "cd ${REMOTE_DIR} && tar xz && find . -name '._*' -delete"
# Ship .env separately and lock it down on the remote.
scp .env "${SSH_HOST}:${REMOTE_DIR}/.env"
ssh "${SSH_HOST}" "chmod 600 ${REMOTE_DIR}/.env"
scp mqtt_demo/.env "${SSH_HOST}:${REMOTE_DIR}/mqtt_demo/.env"
ssh "${SSH_HOST}" "chmod 600 ${REMOTE_DIR}/mqtt_demo/.env"
# Verify certs are present on the remote — they have to be uploaded
# once before the first build.
@@ -74,7 +79,7 @@ if ! ssh "${SSH_HOST}" "test -s ${APPDATA_DIR}/client_fullchain.pem && test -s $
fi
echo "Rebuilding container…"
ssh "${SSH_HOST}" "cd ${REMOTE_DIR} && docker compose up -d --build"
ssh "${SSH_HOST}" "cd ${REMOTE_DIR}/mqtt_demo && docker compose up -d --build"
echo "Done."
echo "Logs: ssh ${SSH_HOST} 'cd ${REMOTE_DIR} && docker compose logs -f'"
echo "Logs: ssh ${SSH_HOST} 'cd ${REMOTE_DIR}/mqtt_demo && docker compose logs -f'"
@@ -27,7 +27,7 @@ from dataclasses import dataclass, field
from typing import Callable, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from ..poll_scheduler import PollTier
from ocf.poll_scheduler import PollTier
@dataclass
@@ -1,6 +1,8 @@
services:
smartthings-local:
build: .
build:
context: ..
dockerfile: mqtt_demo/Dockerfile
container_name: smartthings-local
restart: unless-stopped
@@ -9,7 +9,7 @@ Adding a new appliance class:
main.py imports get_descriptor(name) to look up the descriptor at
startup; the bridge itself stays class-agnostic.
"""
from .base import ApplianceDescriptor
from ..descriptor import ApplianceDescriptor
from .dryer import DRYER
from .oven import OVEN
from .fridge import FRIDGE
@@ -6,14 +6,14 @@ samsung_dryer/{bridge,sensors,discovery}.py modules into one place.
"""
import time
from .base import (
from ..descriptor import (
ApplianceDescriptor,
avail_base,
avail_with_remote,
device_block,
encode,
)
from ..poll_scheduler import PollTier
from ocf.poll_scheduler import PollTier
# --- OBSERVE paths -----------------------------------------------------
@@ -24,13 +24,13 @@ Key differences from newer Tizen RT firmware:
"""
import time
from .base import (
from ..descriptor import (
ApplianceDescriptor,
avail_base,
device_block,
encode,
)
from ..poll_scheduler import PollTier
from ocf.poll_scheduler import PollTier
MODEL = 'ARTIK051_REF_17K'
@@ -23,7 +23,7 @@ Untested writes are gated behind <prefix>/remote_available so HA
disables them in the UI when the oven's Remote Control switch is off."""
import time
from .base import (
from ..descriptor import (
ApplianceDescriptor,
avail_base,
avail_with_cycle,
@@ -31,7 +31,7 @@ from .base import (
device_block,
encode,
)
from ..poll_scheduler import PollTier
from ocf.poll_scheduler import PollTier
# ---------------------------------------------------------------------
+3 -1
View File
@@ -33,7 +33,9 @@ from .coap import (
encode_options, parse_coap, build_coap, block_value, fmt_code,
split_dtls as _split_dtls,
)
from samsung_appliance.logger import logger
import logging
logger = logging.getLogger(__name__)
# Diagnostic logging — when DEBUG_BRIDGE=1 in env, the bridge dumps
-5
View File
@@ -1,5 +0,0 @@
"""Samsung appliance local-API → MQTT bridge with HA discovery.
Multi-device: dryer, oven, etc. The device class is selected at startup
via the DEVICE_CLASS env var (see samsung_appliance.appliances)."""
__version__ = "2.0.0"