diff --git a/README.md b/README.md
index 8373c1b..f0ad2e3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# anker-shelly-bridge
-
+
Automate a Shelly smart plug from live Anker SOLIX telemetry.
@@ -35,8 +35,9 @@ cd anker-shelly-bridge
On Windows, double-click `start.bat`.
-That is the whole install. `start.sh` creates its own Python environment,
-installs everything, and hands over to a guided setup with eight steps:
+That is the whole install. `start.sh` builds a Python environment for this
+project at `~/solix-automation/venv`, installs everything into it, and hands
+over to a guided setup with eight steps:
1. **Dependencies**: installs whatever is missing
2. **Anker account**: your login, stored locally with owner-only permissions
@@ -54,6 +55,10 @@ Nothing touches your hardware until step 8 asks. Ctrl-C is safe at any point.
Needs Python 3.12+ and git. If they are missing, `start.sh` tells you how to
install them for your platform.
+Everything the project needs lives in its own environment, so installing or
+removing packages elsewhere on the machine cannot break your automation. The
+background service is pinned to that same interpreter.
+
---
## What it does
diff --git a/solixauto/service.py b/solixauto/service.py
index d292823..bcde3c7 100644
--- a/solixauto/service.py
+++ b/solixauto/service.py
@@ -23,6 +23,22 @@ def service_label(profile_name):
return f"com.solixauto.{profile_name}"
+def preferred_python():
+ candidates = [
+ paths.BASE_DIR / "venv" / "bin" / "python",
+ paths.BASE_DIR / "venv" / "Scripts" / "python.exe",
+ ]
+ for candidate in candidates:
+ if candidate.exists():
+ probe = subprocess.run(
+ [str(candidate), "-c", "import anker_solix_api.api"],
+ capture_output=True,
+ )
+ if probe.returncode == 0:
+ return str(candidate)
+ return sys.executable
+
+
def script_path():
if sys.argv and sys.argv[0]:
return str(Path(sys.argv[0]).resolve())
@@ -84,7 +100,7 @@ def launchd_plist(profile_name):
ProgramArguments
- {sys.executable}
+ {preferred_python()}
{script}
run
{profile_name}
@@ -125,7 +141,7 @@ After=network-online.target
[Service]
Type=simple
-ExecStart={sys.executable} {script} run {profile_name} --quiet
+ExecStart={preferred_python()} {script} run {profile_name} --quiet
WorkingDirectory={Path(script).parent}
{env_lines}Restart=always
RestartSec=60
@@ -142,7 +158,7 @@ def windows_instructions(profile_name):
others. Use Task Scheduler:
schtasks /create /tn "{label}" /sc onlogon /rl highest ^
- /tr "\\"{sys.executable}\\" \\"{script}\\" run {profile_name} --quiet"
+ /tr "\\"{preferred_python()}\\" \\"{script}\\" run {profile_name} --quiet"
To remove it:
diff --git a/start.bat b/start.bat
index f652401..9d45ca9 100644
--- a/start.bat
+++ b/start.bat
@@ -1,35 +1,106 @@
@echo off
-setlocal
+setlocal enabledelayedexpansion
cd /d "%~dp0"
+if "%SOLIXAUTO_HOME%"=="" (
+ set "DATA_DIR=%USERPROFILE%\solix-automation"
+) else (
+ set "DATA_DIR=%SOLIXAUTO_HOME%"
+)
+set "VENV_DIR=%DATA_DIR%\venv"
+set "VENV_PYTHON=%VENV_DIR%\Scripts\python.exe"
+
echo.
echo ==============================================================
-echo Anker SOLIX to Shelly automation - setup
+echo Anker SOLIX to Shelly automation
echo ==============================================================
echo.
-set PYTHON=
-
-for %%P in (py python) do (
- if not defined PYTHON (
- where %%P >nul 2>&1 && set PYTHON=%%P
+if exist "%VENV_PYTHON%" (
+ "%VENV_PYTHON%" -c "import anker_solix_api.api, yaml, aiohttp" >nul 2>&1
+ if not errorlevel 1 (
+ echo Using: %VENV_PYTHON%
+ echo.
+ "%VENV_PYTHON%" "%~dp0solixauto.py" setup
+ goto :end
)
)
-if not defined PYTHON (
- echo No Python found on PATH.
- echo Install Python 3.12 or newer from https://www.python.org/downloads/
+set "BASE="
+for %%P in (py python) do (
+ if not defined BASE (
+ where %%P >nul 2>&1 && set "BASE=%%P"
+ )
+)
+
+if not defined BASE (
+ echo Python 3.12 or newer is required and was not found on PATH.
+ echo Install it from https://www.python.org/downloads/
echo Tick "Add python.exe to PATH" during installation, then run this again.
echo.
pause
exit /b 1
)
-echo Using: %PYTHON%
+where git >nul 2>&1
+if errorlevel 1 (
+ echo Git is required to fetch the Anker library, and was not found.
+ echo Install it from https://git-scm.com/download/win
+ echo.
+ pause
+ exit /b 1
+)
+
+echo This project runs in its own Python environment, so that installing or
+echo removing packages elsewhere on this machine cannot break your automation.
echo.
+echo It will be created at:
+echo %VENV_DIR%
+echo.
+set /p REPLY="Set it up now? [Y/n]: "
+if /i "%REPLY%"=="n" (
+ echo Nothing was changed.
+ goto :end
+)
-%PYTHON% "%~dp0solixauto.py" setup
+echo.
+echo Creating the environment...
+if not exist "%DATA_DIR%" mkdir "%DATA_DIR%"
+if exist "%VENV_DIR%" rmdir /s /q "%VENV_DIR%"
+%BASE% -m venv "%VENV_DIR%"
+if not exist "%VENV_PYTHON%" (
+ echo Could not create a virtual environment at %VENV_DIR%
+ pause
+ exit /b 1
+)
+
+echo Installing packages. This takes a minute.
+echo.
+"%VENV_PYTHON%" -m pip install --upgrade pip --quiet
+"%VENV_PYTHON%" -m pip install -r "%~dp0requirements.txt" --quiet
+"%VENV_PYTHON%" -m pip install "git+https://github.com/thomluther/anker-solix-api.git" --quiet
+"%VENV_PYTHON%" -m pip install aiofiles cryptography paho-mqtt --quiet
+
+"%VENV_PYTHON%" -c "import anker_solix_api.api" >nul 2>&1
+if errorlevel 1 (
+ echo.
+ echo The install finished but the Anker library still will not import.
+ echo Run this to see the error:
+ echo "%VENV_PYTHON%" -c "import anker_solix_api.api"
+ pause
+ exit /b 1
+)
+
+echo.
+echo Environment ready.
+echo.
+echo From now on, run commands with:
+echo "%VENV_PYTHON%" solixauto.py ^
+echo.
+"%VENV_PYTHON%" "%~dp0solixauto.py" setup
+
+:end
echo.
pause
diff --git a/start.sh b/start.sh
index 5c1c09e..fc9b7d4 100755
--- a/start.sh
+++ b/start.sh
@@ -4,51 +4,35 @@ set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$HERE"
-VENV_DIR="${SOLIXAUTO_VENV:-$HOME/solix-automation/venv}"
+DATA_DIR="${SOLIXAUTO_HOME:-$HOME/solix-automation}"
+VENV_DIR="${SOLIXAUTO_VENV:-$DATA_DIR/venv}"
+VENV_PYTHON="$VENV_DIR/bin/python"
REPO_URL="https://github.com/thomluther/anker-solix-api.git"
echo
echo "=============================================================="
-echo " Anker SOLIX to Shelly automation - setup"
+echo " Anker SOLIX to Shelly automation"
echo "=============================================================="
echo
works() {
- [ -x "$1" ] && "$1" -c "import anker_solix_api.api" >/dev/null 2>&1
+ [ -x "$1" ] && "$1" -c "import anker_solix_api.api, yaml, aiohttp" >/dev/null 2>&1
}
-usable_python() {
- local candidates=(
- "$VENV_DIR/bin/python"
- "$HOME/anker-solix-mqtt/venv/bin/python"
- "$HERE/venv/bin/python"
- )
- for candidate in "${candidates[@]}"; do
- if works "$candidate"; then
- echo "$candidate"
- return 0
- fi
- done
- for name in python3.14 python3.13 python3.12 python3; do
- if command -v "$name" >/dev/null 2>&1 && works "$(command -v "$name")"; then
- command -v "$name"
- return 0
- fi
- done
- return 1
-}
+if works "$VENV_PYTHON"; then
+ echo "Using: $VENV_PYTHON"
+ echo
+ exec "$VENV_PYTHON" "$HERE/solixauto.py" setup
+fi
-base_python() {
- if [ -x "$VENV_DIR/bin/python" ]; then
- echo "$VENV_DIR/bin/python"
- return 0
- fi
+find_base_python() {
for name in python3.14 python3.13 python3.12 python3; do
if command -v "$name" >/dev/null 2>&1; then
- local version
- version="$("$name" -c 'import sys; print(sys.version_info[0]*100+sys.version_info[1])' 2>/dev/null || echo 0)"
+ local path version
+ path="$(command -v "$name")"
+ version="$("$path" -c 'import sys; print(sys.version_info[0]*100+sys.version_info[1])' 2>/dev/null || echo 0)"
if [ "$version" -ge 312 ]; then
- command -v "$name"
+ echo "$path"
return 0
fi
fi
@@ -56,18 +40,7 @@ base_python() {
return 1
}
-PYTHON="$(usable_python || true)"
-
-if [ -n "${PYTHON:-}" ]; then
- echo "Using Python: $PYTHON"
- echo
- exec "$PYTHON" "$HERE/solixauto.py" setup
-fi
-
-echo "Nothing on this machine can talk to Anker yet."
-echo
-
-BASE="$(base_python || true)"
+BASE="$(find_base_python || true)"
if [ -z "${BASE:-}" ]; then
echo "Python 3.12 or newer is required and was not found."
@@ -76,8 +49,10 @@ if [ -z "${BASE:-}" ]; then
echo "Install it with: brew install python@3.13"
elif command -v apt >/dev/null 2>&1; then
echo "Install it with: sudo apt install python3 python3-venv python3-pip git"
+ elif command -v dnf >/dev/null 2>&1; then
+ echo "Install it with: sudo dnf install python3 python3-pip git"
else
- echo "Install Python 3.12+ from https://www.python.org/downloads/"
+ echo "Install Python 3.12 or newer from https://www.python.org/downloads/"
fi
echo
echo "Then run this script again."
@@ -90,18 +65,28 @@ if ! command -v git >/dev/null 2>&1; then
if [ "$(uname)" = "Darwin" ]; then
echo "Install the Xcode command line tools: xcode-select --install"
else
- echo "Install git with your package manager, for example: sudo apt install git"
+ echo "Install git with your package manager, for example:"
+ echo " sudo apt install git"
fi
echo
echo "Then run this script again."
exit 1
fi
-echo "This will create a self-contained Python environment and install"
-echo "everything needed. Nothing outside these two locations is touched:"
+if [ -d "$VENV_DIR" ]; then
+ echo "The environment at $VENV_DIR is incomplete."
+ echo "It will be rebuilt. Nothing else is touched."
+else
+ echo "This project runs in its own Python environment, so that installing or"
+ echo "removing packages elsewhere on this machine cannot break your"
+ echo "automation."
+ echo
+ echo "It will be created at:"
+ echo " $VENV_DIR"
+fi
+
echo
-echo " $VENV_DIR"
-echo " $HOME/solix-automation"
+echo "Base interpreter: $BASE"
echo
printf "Set it up now? [Y/n]: "
read -r reply
@@ -110,38 +95,48 @@ case "${reply:-y}" in
esac
echo
-echo "Creating the environment with $BASE ..."
-mkdir -p "$(dirname "$VENV_DIR")"
-"$BASE" -m venv "$VENV_DIR"
+echo "Creating the environment..."
+mkdir -p "$DATA_DIR"
+rm -rf "$VENV_DIR"
-VENV_PYTHON="$VENV_DIR/bin/python"
-[ -x "$VENV_PYTHON" ] || VENV_PYTHON="$VENV_DIR/Scripts/python.exe"
+if ! "$BASE" -m venv "$VENV_DIR"; then
+ echo
+ echo "Could not create a virtual environment."
+ if command -v apt >/dev/null 2>&1; then
+ echo "On Debian and Ubuntu this usually means python3-venv is missing:"
+ echo " sudo apt install python3-venv"
+ fi
+ exit 1
+fi
if [ ! -x "$VENV_PYTHON" ]; then
- echo "Could not create the environment at $VENV_DIR"
+ echo "The environment was created but has no interpreter at $VENV_PYTHON"
exit 1
fi
echo "Installing packages. This takes a minute."
echo
+
"$VENV_PYTHON" -m pip install --upgrade pip --quiet
"$VENV_PYTHON" -m pip install -r "$HERE/requirements.txt" --quiet
"$VENV_PYTHON" -m pip install "git+$REPO_URL" --quiet
-# The upstream package does not declare its runtime dependencies, so a plain
-# install of it imports the package but fails on first real use.
+# The upstream Anker library does not declare its runtime dependencies, so a
+# plain install imports the package but fails on first real use.
"$VENV_PYTHON" -m pip install aiofiles cryptography paho-mqtt --quiet
if ! works "$VENV_PYTHON"; then
echo
- echo "Some dependencies are still missing. The setup wizard will resolve"
- echo "the rest as it goes."
+ echo "The install finished but the Anker library still will not import."
+ echo "Run this to see the error:"
+ echo " $VENV_PYTHON -c 'import anker_solix_api.api'"
+ exit 1
fi
echo
-echo "Environment ready: $VENV_PYTHON"
+echo "Environment ready."
echo
-echo "Tip: from now on you can run commands as"
+echo "From now on, run commands with:"
echo " $VENV_PYTHON solixauto.py "
echo
exec "$VENV_PYTHON" "$HERE/solixauto.py" setup