Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM python:3.7-alpine

RUN apk add --no-cache gcc musl-dev

WORKDIR /usr/src/app

COPY requirements.txt ./
RUN pip install -r requirements.txt --force-reinstall --no-cache-dir

COPY ./entrypoint.sh .

COPY . .

RUN ["chmod", "+x", "./entrypoint.sh"]

RUN python setup.py install

CMD [ "./entrypoint.sh" ]
14 changes: 14 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ Print help
-j, --json Json output
-t TIMEOUT, --timeout TIMEOUT
Request timeout

MQTT_deamon
#######
::
cp config.yaml.sample config.yaml
::
docker run -e PYEBOX_MYACCOUNT=*** -e PYEBOX_MYPASSWORD=*** -e PYEBOX_OUTPUT=MQTT -e MQTT_USERNAME=mqtt_username -e MQTT_PASSWORD=mqtt_password -e MQTT_HOST=mqtt_ip -e MQTT_PORT=mqtt_port -e ROOT_TOPIC=homeassistant -e MQTT_NAME=ebox pyebox

Docker
#######
::
docker build -t pyebox .
::
docker run -e PYEBOX_MYACCOUNT=*** -e PYEBOX_MYPASSWORD=*** pyebox

Dev env
#######
Expand Down
8 changes: 8 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS YAML CAN CHANGE IN THE FUTURE
timeout: 30
# If frequency is not set the "daemon" will collect the data only one time and stop
# 24 hours
frequency: 86400
accounts:
- username: USERNAME
password: PASSWORD
23 changes: 23 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh
set -e

# Check user and password
if [ -z "$PYEBOX_MYACCOUNT" ] || [ -z "$PYEBOX_MYPASSWORD" ] && [ "$PYEBOX_OUTPUT" != "MQTT" ]
then
echo 'Error: No user or password. Set both environnement variables PYEBOX_MYACCOUNT and PYEBOX_MYPASSWORD or PYEBOX_OUTPUT=MQTT'
exit 1
fi

# Config
if [ -z "$CONFIG" ]
then
export CONFIG="/usr/src/app/config.yaml"
fi

if [ "$PYEBOX_OUTPUT" == "MQTT" ]
then
mqtt_pyebox
else
pyebox -u $PYEBOX_MYACCOUNT -p $PYEBOX_MYPASSWORD
fi

7 changes: 6 additions & 1 deletion pyebox/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys

from pyebox import EboxClient, REQUESTS_TIMEOUT, PyEboxError
from pyebox.mqtt_daemon import MqttEbox


def _format_output(account, data):
Expand Down Expand Up @@ -39,7 +40,7 @@ def _format_output(account, data):
Limit: {d[limit]:.2f} Gb
""")
print(output.format(d=data))


def main():
"""Main function"""
Expand Down Expand Up @@ -72,6 +73,10 @@ def main():
_format_output(args.username, client.get_data())
client.close_session()

def mqtt_daemon():
"""Entrypoint function."""
dev = MqttEbox()
asyncio.run(dev.async_run())

if __name__ == '__main__':
sys.exit(main())
2 changes: 1 addition & 1 deletion pyebox/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def get_data(self):
"""Return collected data"""
return self._data

def close_session(self):
async def close_session(self):
"""Close current session."""
if not self._session.closed:
if self._session._connector_owner:
Expand Down
150 changes: 150 additions & 0 deletions pyebox/mqtt_daemon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""MQTT Daemon which collected Ebox Data.
And send it to MQTT using Home-Assistant format.
"""
import asyncio
import json
import os
import uuid

from yaml import load
try:
from yaml import CLoader as Loader
except ImportError:
from yaml import Loader
import mqtt_hass_base

from pyebox.client import EboxClient, USAGE_MAP

def get_mac():
"""Get mac address."""
mac_addr = (':'.join(['{:02x}'.format((uuid.getnode() >> ele) & 0xff)
for ele in range(0, 8 * 6, 8)][::-1]))
return mac_addr


class MqttEbox(mqtt_hass_base.MqttDevice):
"""MQTT MqttEbox."""

timeout = None
frequency = None

def __init__(self):
"""Create new MqttEbox Object."""
mqtt_hass_base.MqttDevice.__init__(self, "mqtt-ebox")

def read_config(self):
"""Read config from yaml file."""
with open(os.environ['CONFIG']) as fhc:
self.config = load(fhc, Loader=Loader)
self.timeout = self.config.get('timeout', 30)
# 6 hours
self.frequency = self.config.get('frequency', None)

async def _init_main_loop(self):
"""Init before starting main loop."""

def _publish_sensor(self, sensor_type, contract_id,
unit=None, device_class=None, icon=None):
"""Publish a Home-Assistant MQTT sensor."""
mac_addr = get_mac()

base_topic = ("{}/sensor/ebox_{}".format(self.mqtt_root_topic,
contract_id))

sensor_config = {}
sensor_config["device"] = {"connections": [["mac", mac_addr]],
"name": "ebox_{}".format(contract_id),
"identifiers": ['ebox', contract_id],
"manufacturer": "mqtt-ebox",
"sw_version": "0"}

sensor_state_config = "{}/{}/state".format(base_topic, sensor_type)
sensor_config.update({
"state_topic": sensor_state_config,
"name": "ebox_{}_{}".format(contract_id, sensor_type),
"unique_id": "{}_{}".format(contract_id, sensor_type),
"force_update": True,
"expire_after": 0,
})

sensor_config_topic = "{}/{}/config".format(base_topic, sensor_type)

self.mqtt_client.publish(topic=sensor_config_topic,
retain=True,
payload=json.dumps(sensor_config))

return sensor_state_config

async def _main_loop(self):
"""Run main loop."""
self.logger.debug("Get Data")
for account in self.config['accounts']:
client = EboxClient(account['username'],
account['password'],
self.timeout)
await client.fetch_data()
fetched_data = client.get_data()

# Balance
# Publish sensor
balance_topic = self._publish_sensor('balance', account['username'],
unit="$", device_class=None,
icon="mdi:currency-usd")
# Send sensor data
self.mqtt_client.publish(
topic=balance_topic,
payload=fetched_data['balance'])

# Usage
# Publish sensor
usage_topic = self._publish_sensor('usage', account['username'],
unit="%", device_class=None,
icon="mdi:currency-usd")
# Send sensor data
self.mqtt_client.publish(
topic=usage_topic,
payload=fetched_data['usage'])

# Before offpeak and offpeak data
for data_name in USAGE_MAP:
# Publish sensor
sensor_topic = self._publish_sensor(data_name,
account['username'],
unit='Gb',
icon=None,
device_class=None)
# Send sensor data
self.mqtt_client.publish(
topic=sensor_topic,
payload=fetched_data[data_name])

await client.close_session()

if self.frequency is None:
self.logger.info("Frequency is None, so it's a one shot run")
self.must_run = False
return

self.logger.info("Waiting for %d seconds before the next check", self.frequency)
i = 0
while i < self.frequency and self.must_run:
await asyncio.sleep(1)
i += 1

def _on_connect(self, client, userdata, flags, rc):
"""On connect callback method."""

def _on_publish(self, client, userdata, mid):
"""MQTT on publish callback."""

def _mqtt_subscribe(self, client, userdata, flags, rc):
"""Subscribe to all needed MQTT topic."""

def _on_message(self, client, userdata, msg):
"""MQTT on message callback."""

def _signal_handler(self, signal_, frame):
"""Handle SIGKILL."""

async def _loop_stopped(self):
"""Run after the end of the main loop."""
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
aiohttp
bs4
mqtt-hass-base==0.1.4
PyYAML==5.1.2
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
packages=['pyebox'],
entry_points={
'console_scripts': [
'pyebox = pyebox.__main__:main'
'pyebox = pyebox.__main__:main',
'mqtt_pyebox = pyebox.__main__:mqtt_daemon'
]
},
license='Apache 2.0',
Expand Down