Skip to content

Commit

Permalink
feat: first version
Browse files Browse the repository at this point in the history
  • Loading branch information
vermut committed May 11, 2024
0 parents commit a6a35da
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name-template: "v$RESOLVED_VERSION 🌈"
tag-template: "v$RESOLVED_VERSION"
sort-direction: ascending
categories:
- title: "⚠ Breaking changes"
label: "breaking change"
- title: "🚀 Features"
labels:
- "feature"
- title: "🔧 Code enhancements"
labels:
- "enhancement"
- title: "🐛 Bug Fixes"
labels:
- "fix"
- "bugfix"
- "bug"
- title: "🈵 Translations"
label: "translation"
- title: "📄 Documentation"
label: "documentation"
change-template: "- $TITLE @$AUTHOR (#$NUMBER)"
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
template: |
$CHANGES
41 changes: 41 additions & 0 deletions .github/scripts/update_manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Update the manifest file."""

# pylint: skip-file

import json
import os
import sys


def update_manifest():
"""Update the manifest file."""
version = "v0.0.0"
for index, value in enumerate(sys.argv):
if value in ["--version", "-V"]:
version = sys.argv[index + 1]

version_striped = version.replace("v", "")

print("Version number being inserted: " + str(version_striped))

print("Opening file...")

with open(f"{os.getcwd()}/custom_components/openmower/manifest.json") as manifestfile:
manifest = json.load(manifestfile)

manifest["version"] = version_striped

print("Manifest file after inserting new version number:")
print(manifest)

print("Saving file...")

with open(
f"{os.getcwd()}/custom_components/openmower/manifest.json", "w"
) as manifestfile:
manifestfile.write(json.dumps(manifest, indent=4, sort_keys=True))

print("Done!")


update_manifest()
16 changes: 16 additions & 0 deletions .github/workflows/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Draft a release note

on:
push:
branches:
- master

jobs:
draft_release:
name: Release Drafter
runs-on: ubuntu-latest
steps:
- name: Run release-drafter
uses: release-drafter/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 changes: 33 additions & 0 deletions .github/workflows/update-manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Release

on:
release:
types: [published]

jobs:
release_zip_file:
name: Prepare release asset
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v2

- name: Get version
id: version
uses: home-assistant/actions/helpers/version@master

- name: "Set version number"
run: |
python3 ${{ github.workspace }}/.github/scripts/update_manifest.py --version ${{ steps.version.outputs.version }}
- name: Create zip
run: |
cd custom_components/openmower
zip openmower.zip -r ./
- name: Upload zip to release
uses: svenstaro/upload-release-action@v1-release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ./custom_components/openmower/openmower.zip
asset_name: openmower.zip
tag: ${{ github.ref }}
overwrite: true
28 changes: 28 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Validate

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

jobs:
validate-hacs:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- name: HACS validation
uses: "hacs/action@main"
with:
category: integration

hassfest:
runs-on: ubuntu-latest
name: Hassfest
steps:
- name: Check out the repository
uses: "actions/[email protected]"

- name: Hassfest validation
uses: "home-assistant/actions/hassfest@master"
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
OpenMower integration for HomeAssistant
===


Usage
===

* Install via HACS, by adding this repo as Custom Repository
* Configure external MQTT in mower_config.txt
* Add new integration "OpenMower"
* Fill in your MQTT prefix and datum point location
37 changes: 37 additions & 0 deletions custom_components/openmower/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""The OpenMower integration."""

from __future__ import annotations

import logging

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

from .const import DOMAIN

PLATFORMS: list[Platform] = [
Platform.LAWN_MOWER,
Platform.SENSOR,
Platform.DEVICE_TRACKER,
]
_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up OpenMower from a config entry."""

hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = "__openmower_stub"

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."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)

return unload_ok
41 changes: 41 additions & 0 deletions custom_components/openmower/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Config flow for OpenMower integration."""

from __future__ import annotations

import logging
from typing import Any

import voluptuous as vol

from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PREFIX, CONF_LATITUDE, CONF_LONGITUDE

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PREFIX, default="openmower"): str,
vol.Optional(CONF_LATITUDE): float,
vol.Optional(CONF_LONGITUDE): float,
}
)


class ConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for OpenMower."""

VERSION = 1

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
return self.async_create_entry(title="OpenMower", data=user_input)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
3 changes: 3 additions & 0 deletions custom_components/openmower/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Constants for the OpenMower integration."""

DOMAIN = "openmower"
101 changes: 101 additions & 0 deletions custom_components/openmower/device_tracker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import annotations

import logging
import math

from homeassistant.components import mqtt
from homeassistant.components.device_tracker import TrackerEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PREFIX, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.json import json_loads_object
from .const import DOMAIN
from ..device_tracker import SourceType

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
if not await mqtt.async_wait_for_mqtt_client(hass):
_LOGGER.info("Datum not defined, not adding tracker")
return

# Make sure MQTT integration is enabled and the client is available
if not (entry.data[CONF_LATITUDE] and entry.data[CONF_LONGITUDE]):
_LOGGER.error("MQTT integration is not available")
return

async_add_entities(
(
OpenMowerPosition(
entry.data[CONF_PREFIX],
entry.data[CONF_LATITUDE],
entry.data[CONF_LONGITUDE],
),
)
)


class OpenMowerPosition(TrackerEntity):
_attr_name = "OpenMower"
_attr_unique_id = "openmower_position"
_attr_device_info = DeviceInfo(
identifiers={(DOMAIN, "openmower")}, manufacturer="OpenMower"
)

# Constants
_EARTH = 6371008.8
_M = 1 / ((2 * math.pi / 360) * 6371008.8)

def __init__(self, prefix: str, datum_lat: float, datum_lon: float) -> None:
self._mqtt_topic_prefix = prefix
if self._mqtt_topic_prefix and self._mqtt_topic_prefix[-1] != "/":
self._mqtt_topic_prefix = self._mqtt_topic_prefix + "/"

self._datum_lat = datum_lat
self._datum_lon = datum_lon

# Init with datum
self._attr_longitude = datum_lon
self._attr_latitude = datum_lat

async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
await mqtt.async_subscribe(
self.hass,
self._mqtt_topic_prefix + "robot_state/json",
self.async_robot_state_received,
0,
)

@callback
def async_robot_state_received(self, msg: mqtt.ReceiveMessage) -> None:
value_json = json_loads_object(msg.payload)

# https://stackoverflow.com/a/50506609
# https://github.com/Turfjs/turf/issues/635#issuecomment-292011500
# Calculate new longitude and latitude
self._attr_latitude = self._datum_lat + (value_json["pose"]["y"] * self._M)
self._attr_longitude = self._datum_lon + (
value_json["pose"]["x"] * self._M
) / math.cos(self._datum_lat * (math.pi / 180))

self.async_write_ha_state()

@property
def latitude(self) -> float | None:
return self._attr_latitude

@property
def longitude(self) -> float | None:
return self._attr_longitude

@property
def source_type(self) -> SourceType | str:
return SourceType.GPS
Loading

0 comments on commit a6a35da

Please sign in to comment.