Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
shivan committed Aug 11, 2023
1 parent 01f9eeb commit d6faf61
Show file tree
Hide file tree
Showing 13 changed files with 518 additions and 1 deletion.
71 changes: 71 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '43 14 * * 2'

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write

strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed

steps:
- name: Checkout repository
uses: actions/checkout@v2

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl

# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language

#- run: |
# make bootstrap
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
14 changes: 14 additions & 0 deletions .github/workflows/hassfest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Validate with hassfest

on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- uses: home-assistant/actions/hassfest@master
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dev-env/
.DS_Store
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
# homeassistant-liquidcheck
# homeassistant-liquidcheck

Home Assistant Component for liquid-check

[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs)

### Installation

Copy this folder to `<config_dir>/custom_components/liquid-check/`.

### HACS
Search for liquid-check

### Configuration

The integration is configurated via UI
69 changes: 69 additions & 0 deletions custom_components/liquid-check/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
""" Integration for Liquid-Check"""
import voluptuous as vol


from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_MONITORED_CONDITIONS,
)
import homeassistant.helpers.config_validation as cv

from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.typing import HomeAssistantType

from .const import DOMAIN, SENSOR_TYPES, DATA_COORDINATOR
from .coordinator import LiquidCheckDataUpdateCoordinator


CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_MONITORED_CONDITIONS): vol.All(
cv.ensure_list, [vol.In(list(SENSOR_TYPES))]
),
}
)
},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass, config):
"""Platform setup, do nothing."""
hass.data.setdefault(DOMAIN, {})

if DOMAIN not in config:
return True

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=dict(config[DOMAIN])
)
)
return True


async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
"""Load the saved entities."""
coordinator = LiquidCheckDataUpdateCoordinator(
hass,
config=entry.data,
options=entry.options,
)

await coordinator.async_refresh()

if not coordinator.last_update_success:
raise ConfigEntryNotReady

hass.data[DOMAIN][entry.entry_id] = {
DATA_COORDINATOR: coordinator,
}

hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor")
)
return True
112 changes: 112 additions & 0 deletions custom_components/liquid-check/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""Config flow for liquid-check integration."""
# import logging

import voluptuous as vol
import requests
import json
from requests.exceptions import HTTPError, ConnectTimeout

from homeassistant import config_entries
import homeassistant.helpers.config_validation as cv

from homeassistant.const import (
CONF_HOST,
CONF_MONITORED_CONDITIONS,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.util import slugify

from .const import DOMAIN, SENSOR_TYPES # pylint:disable=unused-import

SUPPORTED_SENSOR_TYPES = list(SENSOR_TYPES)

DEFAULT_MONITORED_CONDITIONS = [
"firmware",
"measure_percent",
"content",
]


@callback
def liquidcheck_entries(hass: HomeAssistant):
"""Return the hosts for the domain."""
return set(
(entry.data[CONF_HOST]) for entry in hass.config_entries.async_entries(DOMAIN)
)


class LiquidCheckConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Liquid-Check config flow."""

VERSION = 1

CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL

def __init__(self) -> None:
"""Initialize the config flow."""
self._errors = {}
self._info = {}

def _host_in_configuration_exists(self, host) -> bool:
"""Return True if site_id exists in configuration."""
if host in liquidcheck_entries(self.hass):
return True
return False

def _check_host(self, host) -> bool:
"""Check if we can connect to the liquid-check."""
try:
response = requests.get(f"http://{host}/infos.json")
self._info = json.loads(response.text)
except (ConnectTimeout, HTTPError):
self._errors[CONF_HOST] = "could_not_connect"
return False

return True

async def async_step_user(self, user_input=None):
"""Handle the initial step."""

if user_input is not None:
if self._host_in_configuration_exists(user_input[CONF_HOST]):
self._errors[CONF_HOST] = "host_exists"
else:
host = user_input[CONF_HOST]
conditions = user_input[CONF_MONITORED_CONDITIONS]
can_connect = await self.hass.async_add_executor_job(
self._check_host, host
)
if can_connect:
return self.async_create_entry(
title=f"{self._info['device']} - {self._info['number']}",
data={
CONF_HOST: host,
CONF_MONITORED_CONDITIONS: conditions,
},
)
else:
user_input = {}
user_input[CONF_HOST] = "192.168.0.?"
user_input[CONF_MONITORED_CONDITIONS] = DEFAULT_MONITORED_CONDITIONS

default_monitored_conditions = (
[] if self._async_current_entries() else DEFAULT_MONITORED_CONDITIONS
)
setup_schema = vol.Schema(
{
vol.Required(CONF_HOST, default=user_input[CONF_HOST]): str,
vol.Required(
CONF_MONITORED_CONDITIONS, default=default_monitored_conditions
): cv.multi_select(SUPPORTED_SENSOR_TYPES),
}
)

return self.async_show_form(
step_id="user", data_schema=setup_schema, errors=self._errors
)

async def async_step_import(self, user_input=None):
"""Import a config entry."""
if self._host_in_configuration_exists(user_input[CONF_HOST]):
return self.async_abort(reason="host_exists")
return await self.async_step_user(user_input)
23 changes: 23 additions & 0 deletions custom_components/liquid-check/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Constants for the liquid-check integration."""
from datetime import timedelta

from homeassistant.const import (
UnitOfVolume,
PERCENTAGE,
)

DOMAIN = "liquid-check"

DATA_COORDINATOR = "corrdinator"

MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)

SENSOR_TYPES = {
"device": ["Device", None, "", "data", "device/name"],
"firmware": ["Firmware Version", None, "", "data", "device/firmware"],
"measure_percent": ["Füllstand", PERCENTAGE, "", "data", "measure/percent"],
"level": ["Level", None, "", "data", "measure/level"],
"content": ["Füllstand", UnitOfVolume.LITERS, "", "data", "measure/conent"],
"age": ["Alter", None, "", "data", "measure/age"],
"error": ["Fehler", None, "", "data", "system/error"],
}
59 changes: 59 additions & 0 deletions custom_components/liquid-check/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Provides the LiquidCheck DataUpdateCoordinator."""
from datetime import timedelta
import logging
import requests
import json

from async_timeout import timeout
from homeassistant.util.dt import utcnow
from homeassistant.const import CONF_HOST
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


class LiquidCheckDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching LiquidCheck data."""

def __init__(self, hass: HomeAssistantType, *, config: dict, options: dict):
"""Initialize global liquitdcheck data updater."""
self._host = config[CONF_HOST]
self._next_update = 0
update_interval = timedelta(seconds=30)

super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=update_interval,
)

async def _async_update_data(self) -> dict:
"""Fetch data from LiquidCheck."""

def _update_data() -> dict:
"""Fetch data from LiquidCheck via sync functions."""
data = self.data_update()

return {
"data": data["payload"]
}

try:
async with timeout(4):
return await self.hass.async_add_executor_job(_update_data)
except Exception as error:
raise UpdateFailed(f"Invalid response from API: {error}") from error

def data_update(self):
"""Update liquid check data."""
try:
response = requests.get(f"http://{self._host}/data.jsn")
data = json.loads(response.text)
_LOGGER.debug(data)
return data
except:
pass
11 changes: 11 additions & 0 deletions custom_components/liquid-check/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"domain": "liquid-check",
"version": "1.0.0",
"name": "Liquid-Check",
"documentation": "https://github.com/shivan/homeassistant-liquidcheck",
"config_flow": true,
"dependencies": [],
"codeowners": ["@shivan"],
"requirements": [],
"iot_class": "local_polling"
}
Loading

0 comments on commit d6faf61

Please sign in to comment.