From aa9ec0f0d9a8b3513fde95328ca0883d28bf7f1e Mon Sep 17 00:00:00 2001 From: Marc Billow Date: Sat, 4 Jul 2026 16:11:10 -0500 Subject: [PATCH] =?UTF-8?q?refactor:=20extract=20mqtt=5Fdemo/=20=E2=80=94?= =?UTF-8?q?=20move=20bridge,=20config,=20logger,=20appliance=20descriptors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example => mqtt_demo/.env.example | 0 Dockerfile => mqtt_demo/Dockerfile | 13 ++++--- mqtt_demo/__init__.py | 0 main.py => mqtt_demo/__main__.py | 8 ++--- {samsung_appliance => mqtt_demo}/bridge.py | 8 +++-- {samsung_appliance => mqtt_demo}/config.py | 0 deploy.sh => mqtt_demo/deploy.sh | 35 +++++++++++-------- .../base.py => mqtt_demo/descriptor.py | 2 +- .../docker-compose.yml | 4 ++- {samsung_appliance => mqtt_demo}/logger.py | 0 .../requirements.txt | 0 .../samples}/__init__.py | 2 +- .../appliances => mqtt_demo/samples}/dryer.py | 4 +-- .../samples}/fridge.py | 4 +-- .../appliances => mqtt_demo/samples}/oven.py | 4 +-- protocol/dtls_session.py | 4 ++- samsung_appliance/__init__.py | 5 --- 17 files changed, 51 insertions(+), 42 deletions(-) rename .env.example => mqtt_demo/.env.example (100%) rename Dockerfile => mqtt_demo/Dockerfile (69%) create mode 100644 mqtt_demo/__init__.py rename main.py => mqtt_demo/__main__.py (96%) rename {samsung_appliance => mqtt_demo}/bridge.py (99%) rename {samsung_appliance => mqtt_demo}/config.py (100%) rename deploy.sh => mqtt_demo/deploy.sh (68%) rename samsung_appliance/appliances/base.py => mqtt_demo/descriptor.py (99%) rename docker-compose.yml => mqtt_demo/docker-compose.yml (91%) rename {samsung_appliance => mqtt_demo}/logger.py (100%) rename requirements.txt => mqtt_demo/requirements.txt (100%) rename {samsung_appliance/appliances => mqtt_demo/samples}/__init__.py (95%) rename {samsung_appliance/appliances => mqtt_demo/samples}/dryer.py (99%) rename {samsung_appliance/appliances => mqtt_demo/samples}/fridge.py (99%) rename {samsung_appliance/appliances => mqtt_demo/samples}/oven.py (99%) delete mode 100644 samsung_appliance/__init__.py diff --git a/.env.example b/mqtt_demo/.env.example similarity index 100% rename from .env.example rename to mqtt_demo/.env.example diff --git a/Dockerfile b/mqtt_demo/Dockerfile similarity index 69% rename from Dockerfile rename to mqtt_demo/Dockerfile index ddad334..8510c75 100644 --- a/Dockerfile +++ b/mqtt_demo/Dockerfile @@ -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"] diff --git a/mqtt_demo/__init__.py b/mqtt_demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/mqtt_demo/__main__.py similarity index 96% rename from main.py rename to mqtt_demo/__main__.py index 6826cd7..570a10f 100644 --- a/main.py +++ b/mqtt_demo/__main__.py @@ -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(): diff --git a/samsung_appliance/bridge.py b/mqtt_demo/bridge.py similarity index 99% rename from samsung_appliance/bridge.py rename to mqtt_demo/bridge.py index e7e332c..36b8747 100644 --- a/samsung_appliance/bridge.py +++ b/mqtt_demo/bridge.py @@ -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' diff --git a/samsung_appliance/config.py b/mqtt_demo/config.py similarity index 100% rename from samsung_appliance/config.py rename to mqtt_demo/config.py diff --git a/deploy.sh b/mqtt_demo/deploy.sh similarity index 68% rename from deploy.sh rename to mqtt_demo/deploy.sh index 1ae387c..26af3a7 100755 --- a/deploy.sh +++ b/mqtt_demo/deploy.sh @@ -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'" diff --git a/samsung_appliance/appliances/base.py b/mqtt_demo/descriptor.py similarity index 99% rename from samsung_appliance/appliances/base.py rename to mqtt_demo/descriptor.py index fe3977b..ed335d2 100644 --- a/samsung_appliance/appliances/base.py +++ b/mqtt_demo/descriptor.py @@ -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 diff --git a/docker-compose.yml b/mqtt_demo/docker-compose.yml similarity index 91% rename from docker-compose.yml rename to mqtt_demo/docker-compose.yml index 769bb08..aa1a471 100644 --- a/docker-compose.yml +++ b/mqtt_demo/docker-compose.yml @@ -1,6 +1,8 @@ services: smartthings-local: - build: . + build: + context: .. + dockerfile: mqtt_demo/Dockerfile container_name: smartthings-local restart: unless-stopped diff --git a/samsung_appliance/logger.py b/mqtt_demo/logger.py similarity index 100% rename from samsung_appliance/logger.py rename to mqtt_demo/logger.py diff --git a/requirements.txt b/mqtt_demo/requirements.txt similarity index 100% rename from requirements.txt rename to mqtt_demo/requirements.txt diff --git a/samsung_appliance/appliances/__init__.py b/mqtt_demo/samples/__init__.py similarity index 95% rename from samsung_appliance/appliances/__init__.py rename to mqtt_demo/samples/__init__.py index 20657a9..8cfbcc8 100644 --- a/samsung_appliance/appliances/__init__.py +++ b/mqtt_demo/samples/__init__.py @@ -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 diff --git a/samsung_appliance/appliances/dryer.py b/mqtt_demo/samples/dryer.py similarity index 99% rename from samsung_appliance/appliances/dryer.py rename to mqtt_demo/samples/dryer.py index 0186680..533b97d 100644 --- a/samsung_appliance/appliances/dryer.py +++ b/mqtt_demo/samples/dryer.py @@ -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 ----------------------------------------------------- diff --git a/samsung_appliance/appliances/fridge.py b/mqtt_demo/samples/fridge.py similarity index 99% rename from samsung_appliance/appliances/fridge.py rename to mqtt_demo/samples/fridge.py index 4687eec..49b48c3 100644 --- a/samsung_appliance/appliances/fridge.py +++ b/mqtt_demo/samples/fridge.py @@ -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' diff --git a/samsung_appliance/appliances/oven.py b/mqtt_demo/samples/oven.py similarity index 99% rename from samsung_appliance/appliances/oven.py rename to mqtt_demo/samples/oven.py index 9da542c..48c6ce8 100644 --- a/samsung_appliance/appliances/oven.py +++ b/mqtt_demo/samples/oven.py @@ -23,7 +23,7 @@ Untested writes are gated behind /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 # --------------------------------------------------------------------- diff --git a/protocol/dtls_session.py b/protocol/dtls_session.py index d37dea8..d86a6a8 100644 --- a/protocol/dtls_session.py +++ b/protocol/dtls_session.py @@ -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 diff --git a/samsung_appliance/__init__.py b/samsung_appliance/__init__.py deleted file mode 100644 index e84bf95..0000000 --- a/samsung_appliance/__init__.py +++ /dev/null @@ -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"