Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
astraliens committed Apr 18, 2023
1 parent 8bc44f4 commit e4fd48d
Show file tree
Hide file tree
Showing 361 changed files with 53,697 additions and 2 deletions.
129 changes: 129 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
# home-assistant-gama-300-dlms-meter
Home Assistant Integration for reading data from ELGama GAMA300 meters via RS485-TCP Gateway
# Home Assistant ELGAMA GAMA300 DLMS over TCPIP
Home Assistant Integration for reading data from ELGAMA GAMA300 electricity meter (GAMA100 not tested, but possibly supported too) via RS485-TCP Gateway

## Features

- Get most of live values from meter
- Add energy sensors for integration with energy usage
- Tracks daily / total energy consumption

## Notes

This code was written for couple of days without any skills in Python and HA architecture. That is second time I've used Python, so anyone is welcome to make this code better.

## Connecting meter to gateway and HA

First your meter must support RS485 interface (check your meter manual or model number to ensure your meter support it, but also you can try to connect optical reader to gateway). To connect meter to HA you need any RS485->TCP gateway (I've used HI-FLYING HF5142B, coz prefer wires, but you can use any other or try to connect it with esp32 / 8266 with RS485 converter bridging it to TCP socket).
On gateway side you should configure TCP server to forward data from RS485 port, it should be tcp server without authorization, you can choose any port to listen.

#### RS485 params:
- Baud rate: 9600
- Data bit: 8
- Stop bit: 1
- Parity: None

![HF5142B_connection](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_connection.jpg)
![HF5142B_TCP_Server](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_TCP_Server.jpg)

After connecting meter you need to add `home-assistant-gama-300-dlms-meter` integration using HACS to your HA. In you HA go to *Main Menu -> HACS -> Integrations*, in top right corner press 3 dots and click to "Custom Repositories". Add repository `https://github.com/astraliens/home-assistant-gama_300_dlms_meter_reader` and category `Integration`. After this step close modal add repository window and press "Explore & Download Repositories" blue button at the bottom right corner of screen and search for `Gama 300 DLMS Meter` integration. Press on it and in right bottom corner of screen press "Download" button.
After this you can simply add it like regular integration, specifiying IP and port of gateway and your meter serial number (you can find it on meter itself). In several seconds integration get data from meter, create all found sensors and will update them constantly.

Pinout of meter shown on photo:
![gama_300_rs485_pinout](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/gama_300_rs485_pinout.jpg)


## Energy consumption monitoring

You can add `Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0` sensor to your energy consumption monitoring to calculate your overall spents

## DLMS Protocol

Many thanks to <a href="https://www.gurux.fi/Gurux.DLMS">Gurux</a> for Python library implementation which used in this integration to retrieve data from meter

## Donations

You can say thanks by donating for buying pizza at:

<a href="https://www.buymeacoffee.com/astraliens" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Pizza" height="41" width="174"></a>
47 changes: 47 additions & 0 deletions custom_components/gama_300_dlms_meter_reader/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Home Assistant ELGAMA GAMA300 DLMS over TCPIP
Home Assistant Integration for reading data from ELGAMA GAMA300 electricity meter (GAMA100 not tested, but possibly supported too) via RS485-TCP Gateway

## Features

- Get most of live values from meter
- Add energy sensors for integration with energy usage
- Tracks daily / total energy consumption

## Notes

This code was written for couple of days without any skills in Python and HA architecture. That is second time I've used Python, so anyone is welcome to make this code better.

## Connecting meter to gateway and HA

First your meter must support RS485 interface (check your meter manual or model number to ensure your meter support it, but also you can try to connect optical reader to gateway). To connect meter to HA you need any RS485->TCP gateway (I've used HI-FLYING HF5142B, coz prefer wires, but you can use any other or try to connect it with esp32 / 8266 with RS485 converter bridging it to TCP socket).
On gateway side you should configure TCP server to forward data from RS485 port, it should be tcp server without authorization, you can choose any port to listen.

#### RS485 params:
- Baud rate: 9600
- Data bit: 8
- Stop bit: 1
- Parity: None

![HF5142B_connection](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_connection.jpg)
![HF5142B_TCP_Server](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/HF5142B_TCP_Server.jpg)

After connecting meter you need to add `home-assistant-gama-300-dlms-meter` integration using HACS to your HA. In you HA go to *Main Menu -> HACS -> Integrations*, in top right corner press 3 dots and click to "Custom Repositories". Add repository `https://github.com/astraliens/home-assistant-gama_300_dlms_meter_reader` and category `Integration`. After this step close modal add repository window and press "Explore & Download Repositories" blue button at the bottom right corner of screen and search for `Gama 300 DLMS Meter` integration. Press on it and in right bottom corner of screen press "Download" button.
After this you can simply add it like regular integration, specifiying IP and port of gateway and your meter serial number (you can find it on meter itself). In several seconds integration get data from meter, create all found sensors and will update them constantly.

Pinout of meter shown on photo:
![gama_300_rs485_pinout](https://raw.githubusercontent.com/astraliens/home-assistant-gama-300-dlms-meter/main/images/gama_300_rs485_pinout.jpg)


## Energy consumption monitoring

You can add `Sum Li Active power+ (QI+QIV) Time integral 1 Rate 0` sensor to your energy consumption monitoring to calculate your overall spents

## DLMS Protocol

Many thanks to <a href="https://www.gurux.fi/Gurux.DLMS">Gurux</a> for Python library implementation which used in this integration to retrieve data from meter

## Donations

You can say thanks by donating for buying pizza at:

<a href="https://www.buymeacoffee.com/astraliens" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Pizza" height="41" width="174"></a>
34 changes: 34 additions & 0 deletions custom_components/gama_300_dlms_meter_reader/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant

from . import hub
from .const import DOMAIN

# List of platforms to support. There should be a matching .py file for each,
# eg <cover.py> and <sensor.py>
PLATFORMS: list[str] = ["sensor"]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Store an instance of the "connecting" class that does the work of speaking
# with your actual devices.
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = hub.Hub(hass, entry.data["host"], entry.data["port"], entry.data["serial"])

# This creates each HA object for each platform your device requires.
# It's done by calling the `async_setup_entry` function in each platform module.
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
# This is called when an entry/configured device is to be removed. The class
# needs to unload itself, and remove callbacks. See the classes for further
# details
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
119 changes: 119 additions & 0 deletions custom_components/gama_300_dlms_meter_reader/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from __future__ import annotations

import logging
from typing import Any

import voluptuous as vol

from homeassistant import config_entries, exceptions
from homeassistant.core import HomeAssistant

from .const import DOMAIN # pylint:disable=unused-import
from .hub import Hub

_LOGGER = logging.getLogger(__name__)

# This is the schema that used to display the UI to the user. This simple
# schema has a single required host field, but it could include a number of fields
# such as username, password etc. See other components in the HA core code for
# further examples.
# Note the input displayed to the user will be translated. See the
# translations/<lang>.json file and strings.json. See here for further information:
# https://developers.home-assistant.io/docs/config_entries_config_flow_handler/#translations
# At the time of writing I found the translations created by the scaffold didn't
# quite work as documented and always gave me the "Lokalise key references" string
# (in square brackets), rather than the actual translated value. I did not attempt to
# figure this out or look further into it.
DATA_SCHEMA = vol.Schema({
("host"): str,
("port"): int,
("serial"): int,
})

async def validate_input(hass: HomeAssistant, data: dict) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
"""
# Validate the data can be used to set up a connection.

# This is a simple example to show an error in the UI for a short hostname
# The exceptions are defined at the end of this file, and are used in the
# `async_step_user` method below.
if len(data["host"]) < 3:
raise InvalidHost

hub = Hub(hass, data["host"],data["port"],data["serial"])
# The dummy hub provides a `test_connection` method to ensure it's working
# as expected
result = await hub.test_connection()
#if not result:
# If there is an error, raise an exception to notify HA that there was a
# problem. The UI will also show there was a problem
# raise CannotConnect

# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data["username"], data["password"]
# )

# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth

# Return info that you want to store in the config entry.
# "Title" is what is displayed to the user for this hub device
# It is stored internally in HA as part of the device config.
# See `async_step_user` below for how this is used
return {"title": data["host"]}


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

VERSION = 1
# Pick one of the available connection classes in homeassistant/config_entries.py
# This tells HA if it should be asking for updates, or it'll be notified of updates
# automatically. This example uses PUSH, as the dummy hub will notify HA of
# changes.
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
# This goes through the steps to take the user through the setup process.
# Using this it is possible to update the UI and prompt for additional
# information. This example provides a single form (built from `DATA_SCHEMA`),
# and when that has some validated input, it calls `async_create_entry` to
# actually create the HA config entry. Note the "title" value is returned by
# `validate_input` above.
errors = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)

return self.async_create_entry(title=info["title"], data=user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidHost:
# The error string is set here, and should be translated.
# This example does not currently cover translations, see the
# comments on `DATA_SCHEMA` for further details.
# Set the error on the `host` field, not the entire form.
errors["host"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"

# If there is no user input or there were errors, show the form again, including any errors that were found with the input.
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)


class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidHost(exceptions.HomeAssistantError):
"""Error to indicate there is an invalid hostname."""
3 changes: 3 additions & 0 deletions custom_components/gama_300_dlms_meter_reader/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This is the internal name of the integration, it should also match the directory
# name for the integration.
DOMAIN = "gama_300_dlms_meter_reader"
Loading

0 comments on commit e4fd48d

Please sign in to comment.