Skip to content

Commit

Permalink
Merge pull request #58 from Snuffy2/Rewrite-to-async
Browse files Browse the repository at this point in the history
Rewrite to async
  • Loading branch information
Snuffy2 authored May 31, 2024
2 parents bec347e + 57e56ec commit 44f7f0f
Show file tree
Hide file tree
Showing 14 changed files with 1,722 additions and 852 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/async_beta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: async_beta - Build and Publish GitHub Container Docker Image
on:
release:
types: [published, edited]
push:
branches:
- async_beta
workflow_dispatch:

jobs:
async_beta_build_and_publish_docker_image:
if: github.repository_owner == 'snicker' && (github.event.release.target_commitish == 'async_beta' || github.ref_name == 'async_beta')
runs-on: ubuntu-latest
steps:
- name: GitHub Context
env:
GITHUB_CONTEXT: ${{ toJSON(github) }}
run: echo "$GITHUB_CONTEXT"
- name: Debug Variables
run: |
echo "github.event_name: ${{ github.event_name }}"
echo "github.ref_name: ${{ github.ref_name }}"
echo "github.event.repository.default_branch: ${{ github.event.repository.default_branch }}"
echo "github.event.release.target_commitish: ${{ github.event.release.target_commitish }}"
echo "github.event.release.prerelease: ${{ github.event.release.prerelease }}"
echo "github.event.release.draft: ${{ github.event.release.draft }}"
- name: Checkout
uses: actions/[email protected]
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
flavor: |
latest=false
tags: |
type=raw, value=async_beta
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Docker Container
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
15 changes: 8 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
FROM python:3.12-slim-bookworm
# WARNING: Do not update python past 3.12.* if telnetlib is still being used.

LABEL org.opencontainers.image.source=https://github.com/snicker/juicepassproxy

ENV DEBUG=false
ENV UPDATE_UDPC=false

RUN pip install --upgrade pip
RUN pip install --root-user-action=ignore --no-cache-dir --upgrade pip
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y git curl
RUN git clone https://github.com/snicker/juicepassproxy.git /juicepassproxy
RUN pip install --no-cache-dir -r /juicepassproxy/requirements.txt
RUN apt-get install -y tini git curl
RUN mkdir -p /juicepassproxy
RUN mkdir -p /log
RUN mkdir -p /config
COPY * /juicepassproxy
RUN pip install --root-user-action=ignore --no-cache-dir -r /juicepassproxy/requirements.txt
RUN chmod -f +x /juicepassproxy/*.sh

ENTRYPOINT /juicepassproxy/docker_entrypoint.sh
ENTRYPOINT ["/usr/bin/tini", "--", "/juicepassproxy/docker_entrypoint.sh"]
19 changes: 19 additions & 0 deletions Dockerfile_dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM python:3.13-rc-slim-bookworm

LABEL org.opencontainers.image.source=https://github.com/snicker/juicepassproxy

ENV DEBUG=false

RUN pip install --root-user-action=ignore --no-cache-dir --upgrade pip
RUN apt-get update && apt-get upgrade -y
RUN apt-get install -y tini git curl
RUN mkdir -p /juicepassproxy
RUN mkdir -p /log
RUN mkdir -p /config
COPY requirements.txt /juicepassproxy
RUN pip install --root-user-action=ignore --no-cache-dir -r /juicepassproxy/requirements.txt
COPY * /juicepassproxy
RUN chmod -f +x /juicepassproxy/*.sh

#ENTRYPOINT ["/usr/bin/tini", "--", "/juicepassproxy/docker_entrypoint.sh"]
ENTRYPOINT ["/juicepassproxy/docker_entrypoint.sh"]
113 changes: 64 additions & 49 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ Builds upon work by lovely folks in this issue: https://github.com/home-assistan

_Hopefully we won't need this if EnelX fixes their API!_

#### It is required that both your JuiceBox and the machine you are running `juicepassproxy` on have internal static IPs on your intranet.
#### It is required that both your JuiceBox and the machine you are running `juicepassproxy` on have internal, static IPs on your intranet.

## Docker Compose Installation

### Features
* If JuiceBox Local IP is defined, it will run a telnet script to get the EnelX Server and Port as well as the JuiceBox ID.
* If `JUICEBOX_HOST` is defined, it will run a telnet script to get the EnelX Server and Port as well as the JuiceBox ID.

* If DST is not defined, it will use `dig` with the CloudFlare DNS (1.1.1.1) to get the IP address of the EnelX Server and avoid a DNS lookup loop.
* If `ENELX_IP` is not defined, it will use the CloudFlare DNS (1.1.1.1) to get the IP address of the EnelX Server and avoid a DNS lookup loop.

* If SRC is not defined, it will use `ifconfig` to get the Local IP address of the Docker.
* If `LOCAL_IP` is not defined, it will lookup the Local IP address of the Docker.

* If Update UDPC is true, JuicePass Proxy will continually update the JuiceBox via telnet to send its data to JuicePass Proxy. Use this if you are not able to change your DNS to route the JuiceBox traffic to JuicePass Proxy.
* If `UPDATE_UDPC` is true, JuicePass Proxy will continually update the JuiceBox via telnet to send its data to JuicePass Proxy. Use this if you are not able to change your DNS to route the JuiceBox traffic to JuicePass Proxy.

### Instructions

Expand All @@ -34,21 +34,19 @@ _Hopefully we won't need this if EnelX fixes their API!_
JPP_HOST=<IP address of the machine that the JuicePass Proxy Docker Container is running on>
```
1. Add the `juicepassproxy` service to your Docker Compose file.
1. Add the `juicepassproxy` container to your Docker Compose file.
A. Set `ports:` to the port listed in the UDCP line _(see [Getting EnelX Server IPs](#getting-enelx-server-ips))_.
A. Set `ports:` to the port listed in the UDCP line, likely `8047` _(see [Getting EnelX Server IPs](#getting-enelx-server-ips))_.
B. Define the applicable environment variables _(see [Docker Environment Variables](#docker-environment-variables))_.
C. Specify the location for the config folder
C. Specify the location for the config and optionally the logs folder
1. Start the Docker container.
### Example Docker Compose
```yaml
version: '3.8'
networks:
default:
driver: bridge
Expand Down Expand Up @@ -76,88 +74,105 @@ services:
volumes:
- /etc/localtime:/etc/localtime:ro
- ./config:/config
- ./log:/log #Optional
```

### Docker Environment Variables

Variable | Required | Description & Default |
-- | -- | --
**JUICEBOX_HOST** | **Recommended**</br></br>**Required if Update UDPC is True.** | If defined, it will attempt to get the EnelX Server and Port using Telnet. If unsuccessful, it will default to the EnelX Server and Port below.
**JUICEBOX_HOST** | **Recommended**</br></br>**Required if Update UDPC is True.** | If defined, will attempt to get the EnelX Server Name, IP, and Port using telnet.
**UPDATE_UDPC** | No | Default: false. If true, will continually update the JuiceBox via telnet to point to JuicePass Proxy.
**TELNET_TIMEOUT** | No | Default: 30. Timeout in seconds for telnet operations.
**JPP_HOST** | **Required if Update UDPC is True** | This is the IP or Hostname of the machine where JuicePass Proxy is running (**not** the IP of the Docker Container)
**SRC** | No | If not defined, it will attempt to get the Local Docker IP. If unsuccessful, it will default to 127.0.0.1.
**DST** | No | If not defined, it will attempt to get the IP of the EnelX Server. If unsuccessful, it will default to 54.161.185.130. If manually defined, you should only use the IP address of the EnelX Server and not the fully qualified domain name to avoid DNS lookup loops.
**JUICEBOX_ID** | No | If not defined, it will attempt to get the JuiceBox ID using Telnet.
**MQTT_HOST** | No | 127.0.0.1
**MQTT_PORT** | No | 1883
**MQTT_USER** | No |
**MQTT_PASS** | No |
**MQTT_DISCOVERY_PREFIX** | No | homeassistant

<details>
<summary><h3>Less Common Docker Environment Variables</h3></summary>

Variable | Required | Description & Default |
-- | -- | --
**DEVICE_NAME** | No | JuiceBox
**DEBUG** | No | false

**EXPERIMENTAL** | No | Default: false. Enables additional entities in Home Assistant that are in in development or can be used toward developing the ability to send commands to a JuiceBox
**IGNORE_ENELX** | No | Default: false. If true, will not send commands received from EnelX to the JuiceBox nor send outgoing information from the JuiceBox to EnelX
**TELNET_TIMEOUT** | No | Default: 30. Timeout in seconds for telnet operations.
**JUICEBOX_ID** | No | If not defined, will attempt to get the JuiceBox ID using telnet.
**LOCAL_IP**<br><br>_Deprecated Variable: SRC_ | No | If not defined, will attempt to get the Local Docker IP. Can optionally define port (ex. 127.0.0.1:8047). If unsuccessful, will default to 127.0.0.1.
**LOCAL_PORT** | No | Local port for JuicePass Proxy to listen on. If not defined, will use the EnelX Port.
**ENELX_IP**<br><br>_Deprecated Variable: DST_ | No | If not defined, will attempt to get the IP of the EnelX Server. If unsuccessful, will default to 54.161.185.130. Can optionally define port (ex. 54.161.185.130:8047). If defined, only use the IP address of the EnelX Server and not the fully qualified domain name to avoid DNS lookup loops.
</details>

<details>
<summary><h2>Manual Installation</h2></summary>

1. Clone this repository
2. Use Python 3.10-3.12 (I recommend setting up a virtual environment)
2. Use Python 3.10+ (I recommend setting up a virtual environment)
3. Install requirements `pip install -r requirements.txt`
4. Launch by executing `python3 juicepassproxy.py --juicebox_host <IP of the JuiceBox> --mqtt_host <mqtt_host>` (params documented below)
5. Nothing happens!
6. Configure your DNS server running on your network (like Pi-hole or your router) to route all DNS requests from EnelX to the machine running this proxy. For me this was `juicenet-udp-prod3-usa.enelx.com`. See below for instructions to determine that.
7. Alternatively to #6, you can enable `update_udpc` on the command line and set `juicebox_host` and the application will force publish the IP in the `src` argument to the Juicebox and avoid the need to set DNS rules on your router or DNS server. **NOTE: if you need to publish a different IP than the one in the `src` argument, you can make use of the `--juicepass_proxy_host` arg.**
7. Alternatively to #6, you can enable `--update_udpc` on the command line and set `--juicebox_host` and the application will force publish the IP in the `--local_ip` argument to the JuiceBox and avoid the need to set DNS rules on your router or DNS server. **NOTE: if you need to publish a different IP than the one in the `--local_ip` argument, you can make use of the `--jpp_host` arg.**

### CLI Options

```
options:
-h, --help show this help message and exit
-s SRC, --src SRC Source IP (and optional port). If not defined, will
obtain it automatically. (Ex. 127.0.0.1:8047)
-d DST, --dst DST Destination IP (and optional port) of EnelX Server. If
not defined, --juicebox_host required and then will
obtain it automatically. (Ex. 127.0.0.1:8047)
--debug
-u MQTT_USER, --mqtt_user MQTT_USER
MQTT Username
-P MQTT_PASSWORD, --mqtt_password MQTT_PASSWORD
--juicebox_host HOST Host or IP address of the JuiceBox. Required for
--update_udpc or if --enelx_ip not defined.
--update_udpc Update UDPC on the JuiceBox. Requires --juicebox_host
--jpp_host, --juicepass_proxy_host HOST
EXTERNAL host or IP address of the machine running
JuicePass Proxy. Optional: only necessary when using
--update_udpc and it will be inferred from the address
in --local_ip if omitted.
-H, --mqtt_host HOST MQTT Hostname to connect to (default: 127.0.0.1)
-p, --mqtt_port PORT MQTT Port (default: 1883)
-u, --mqtt_user USER MQTT Username
-P, --mqtt_password PASSWORD
MQTT Password
-H MQTT_HOST, --mqtt_host MQTT_HOST
MQTT Hostname to connect to (default: 127.0.0.1)
-p MQTT_PORT, --mqtt_port MQTT_PORT
MQTT Port (default: 1883)
-D MQTT_DISCOVERY_PREFIX, --mqtt_discovery_prefix MQTT_DISCOVERY_PREFIX
-D, --mqtt_discovery_prefix PREFIX
Home Assistant MQTT topic prefix (default:
homeassistant)
--config_loc LOC The location to store the config file (default:
~/.juicepassproxy)
--log_loc LOC The location to store the log files (default: ~)
--name DEVICE_NAME Home Assistant Device Name (default: JuiceBox)
--juicebox_id JUICEBOX_ID
JuiceBox ID. If not defined, will obtain it
automatically.
--update_udpc Update UDPC on the JuiceBox. Requires --juicebox_host
--telnet_timeout TELNET_TIMEOUT
--debug Show Debug level logging. (default: Info)
--experimental Enables additional entities in Home Assistant that are
in in development or can be used toward developing the
ability to send commands to a JuiceBox.
--ignore_enelx If set, will not send commands received from EnelX to
the JuiceBox nor send outgoing information from the
JuiceBox to EnelX
--telnet_timeout SECONDS
Timeout in seconds for Telnet operations (default: 30)
--juicebox_host JUICEBOX_HOST
Host or IP address of the JuiceBox. Required for
--update_udpc or if --dst not defined.
--juicepass_proxy_host JUICEPASS_PROXY_HOST
EXTERNAL host or IP address of the machine running
JuicePass Proxy. Optional: only necessary when using
--update_udpc and it will be inferred from the address
in --src if omitted.
--config_loc CONFIG_LOC
The location to store the config file (default:
/~/.juicepassproxy)
--juicebox_id ID JuiceBox ID. If not defined, will obtain it
automatically.
--local_ip IP Local IP (and optional port). If not defined, will
obtain it automatically. (Ex. 127.0.0.1:8047)
[Deprecated: -s --src]
--local_port PORT Local Port for JPP to listen on.
--enelx_ip IP Destination IP (and optional port) of EnelX Server. If
not defined, --juicebox_host required and then will
obtain it automatically. (Ex. 54.161.185.130:8047)
[Deprecated: -d --dst]
```

_For **DST**, you should only use the IP address of the EnelX Server and **not** the fully qualified domain name (FQDN) to avoid DNS lookup loops._
_For `--enelx_ip`, only use the IP address of the EnelX Server and **not** the fully qualified domain name (FQDN) to avoid DNS lookup loops._

</details>

#### Deprecated Environment Variables and Command Line options will continue to work for now but will be removed at some point in the future.

## Getting EnelX Server IPs

To get the destination IP:Port of the EnelX server, telnet to your Juicenet device:
To get the destination IP:Port of the EnelX server, telnet into your JuiceBox device:
`$ telnet 192.168.x.x 2000`
and type the `list` command:

Expand Down
40 changes: 37 additions & 3 deletions const.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
CONF_YAML = "juicepassproxy.yaml"
import logging

# Will auto-update based on GitHub release tag
VERSION = "v0.2.2"

CONF_YAML = "juicepassproxy.yaml"

LOGFILE = "juicepassproxy.log"
LOG_FORMAT = "%(asctime)-20s %(levelname)-9s [%(name)s] %(message)s"
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
DEFAULT_LOGLEVEL = logging.INFO
DAYS_TO_KEEP_LOGS = 14

# Defaults
DEFAULT_ENELX_SERVER = "juicenet-udp-prod3-usa.enelx.com"
DEFAULT_ENELX_PORT = "8047"
DEFAULT_SRC = "127.0.0.1"
DEFAULT_DST = "54.161.147.91"
DEFAULT_LOCAL_IP = "127.0.0.1"
DEFAULT_ENELX_IP = "54.161.147.91"
DEFAULT_MQTT_HOST = "127.0.0.1"
DEFAULT_MQTT_PORT = "1883"
DEFAULT_MQTT_DISCOVERY_PREFIX = "homeassistant"
DEFAULT_DEVICE_NAME = "JuiceBox"
DEFAULT_TELNET_TIMEOUT = 30

# How many times to fully restart JPP before exiting
MAX_JPP_LOOP = 10

# Will stop JuiceboxMITM or JuiceboxUDPC Updater if there are more than MAX_ERROR_COUNT handled exceptions within ERROR_LOOKBACK_MIN minutes.
MAX_ERROR_COUNT = 10
ERROR_LOOKBACK_MIN = 60

# How many times to retry connections or sending attempts before failing
MAX_RETRY_ATTEMPT = 3

# How many seconds before timing out a UDPC Update
UDPC_UPDATE_CHECK_TIMEOUT = 60

# How many seconds before timing out handling a MITM Message
MITM_HANDLER_TIMEOUT = 10

# How many seconds to wait to receive a MITM Message before timing out
MITM_RECV_TIMEOUT = 120

# How many seconds before timing out sending a MITM Message
MITM_SEND_DATA_TIMEOUT = 10

EXTERNAL_DNS = "1.1.1.1"
Loading

0 comments on commit 44f7f0f

Please sign in to comment.