Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #124

Merged
merged 24 commits into from
Jul 2, 2024
Merged

Dev #124

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3b8e72e
Fix blocking of entity picture generation
Snuffy2 Jun 8, 2024
6f54177
fix: Fix blocking of entity picture generation
Disane87 Jun 9, 2024
9a88086
chore(release): 📢 0.4.4-dev.1
semantic-release-bot Jun 9, 2024
d5c310e
chore(deps): update dependency ruff to v0.5.0
renovate[bot] Jun 27, 2024
a3653d1
Merge pull request #95 from Disane87/renovate/ruff-0.x
renovate[bot] Jun 27, 2024
c833ff1
chore(deps): update actions/setup-python action to v4.8.0
renovate[bot] Jun 27, 2024
c1d50ce
Merge pull request #93 from Disane87/renovate/actions-setup-python-4.x
renovate[bot] Jun 27, 2024
930d3bc
chore(deps): update dependency colorlog to v6.8.2
renovate[bot] Jun 27, 2024
657a436
Merge pull request #94 from Disane87/renovate/colorlog-6.x
renovate[bot] Jun 28, 2024
dd23d01
chore(deps): update actions/setup-python action to v5
renovate[bot] Jun 28, 2024
911b2f8
Merge pull request #96 from Disane87/renovate/actions-setup-python-5.x
renovate[bot] Jun 28, 2024
b8985f9
chore(deps): update actions/stale action to v9
renovate[bot] Jun 28, 2024
8bb62d2
Merge pull request #97 from Disane87/renovate/actions-stale-9.x
renovate[bot] Jun 28, 2024
d792219
chore(deps): update dependency pip to v24
renovate[bot] Jun 28, 2024
398b46b
Merge pull request #98 from Disane87/renovate/pip-24.x
renovate[bot] Jun 28, 2024
ac096da
chore(deps): update mcr.microsoft.com/vscode/devcontainers/python doc…
renovate[bot] Jun 28, 2024
9363b6a
Merge pull request #113 from Disane87/renovate/mcr.microsoft.com-vsco…
renovate[bot] Jun 28, 2024
881a76b
feat: service integration to change a spool in Spoolman via API
Disane87 Jul 1, 2024
2f9801e
fix: added names for service.yaml
Disane87 Jul 1, 2024
ff78fc5
chore(release): 📢 0.5.0-dev.1
semantic-release-bot Jul 1, 2024
6b821b6
fix: removed periods from descriptions and names
Disane87 Jul 1, 2024
972db1a
Merge branch 'dev' of https://github.com/Disane87/spoolman-homeassist…
Disane87 Jul 1, 2024
0c78a1f
chore(release): 📢 0.5.0-dev.2
semantic-release-bot Jul 1, 2024
25ae299
Merge branch 'main' of https://github.com/Disane87/spoolman-homeassis…
Disane87 Jul 2, 2024
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
2 changes: 1 addition & 1 deletion .devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ludeeus/integration_blueprint",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11",
"image": "mcr.microsoft.com/vscode/devcontainers/python:1-3.11",
"postCreateCommand": "scripts/setup",
"containerEnv": { "DEVCONTAINER": "1" },
"forwardPorts": [
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
uses: "actions/checkout@v4"

- name: "Set up Python"
uses: actions/setup-python@v4.7.1
uses: actions/setup-python@v5.1.0
with:
python-version: "3.11"
cache: "pip"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
pull-requests: write

steps:
- uses: actions/stale@v8
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue has been staled because there was no activity from you within 14 days. Please feel free to reopen this issue.'
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
## [0.5.0-dev.2](https://github.com/Disane87/spoolman-homeassistant/compare/v0.5.0-dev.1...v0.5.0-dev.2) (2024-07-01)

### 🛠️ Fixes

* removed periods from descriptions and names ([6b821b6](https://github.com/Disane87/spoolman-homeassistant/commit/6b821b6f9b06fc53502a20b4cca2b9c079775cc7)), closes [#119](https://github.com/Disane87/spoolman-homeassistant/issues/119)

## [0.5.0-dev.1](https://github.com/Disane87/spoolman-homeassistant/compare/v0.4.4-dev.1...v0.5.0-dev.1) (2024-07-01)

### 🛠️ Fixes

* added names for service.yaml ([2f9801e](https://github.com/Disane87/spoolman-homeassistant/commit/2f9801e34b8ccda20c469bd45f7baaf2929da567))

### 🚀 Features

* service integration to change a spool in Spoolman via API ([881a76b](https://github.com/Disane87/spoolman-homeassistant/commit/881a76bf5e149a3917b3cfd1041ca01a2d1fafd9)), closes [#119](https://github.com/Disane87/spoolman-homeassistant/issues/119)

## [0.4.4-dev.1](https://github.com/Disane87/spoolman-homeassistant/compare/v0.4.3...v0.4.4-dev.1) (2024-06-09)

### 🛠️ Fixes

* Fix blocking of entity picture generation ([6f54177](https://github.com/Disane87/spoolman-homeassistant/commit/6f541777a3799d3b694753ec5cc3f63e3bac850b)), closes [#121](https://github.com/Disane87/spoolman-homeassistant/issues/121)

### 📔 Docs

* changed CONTRIBUTING.md ([144c4e2](https://github.com/Disane87/spoolman-homeassistant/commit/144c4e2d9f8916a8aa2bafbfffe898b048aa2ea0))

## [0.4.3](https://github.com/Disane87/spoolman-homeassistant/compare/v0.4.2...v0.4.3) (2024-04-01)


Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ This integration integrates Spoolman (https://github.com/Donkie/Spoolman/) into
- Enable/disabled archived spools
- Archived spools are grouped into one `Archived` device
- If a Klipper url is configured, the active spool will have an attribute `klipper_active_spool`
- Creation of a service `spoolman.patch_spool` to enable you to change values of a spool from automations

> [!NOTE]
> If one of the threshold is exceeded the integration fires an event. The event is named `spoolman_spool_threshold_exceeded`. Currently there are three thresholds defined: `info`, `warning` and `critical`.
Expand Down Expand Up @@ -194,6 +195,30 @@ A spool has this structure (according to the [OpenAPI description](https://donki
"archived": false
}
```

# Home Assistant services
This integration creates services to be used in automations:

## `spoolman.patch_spool`
This service is used to change values and properties if a spool. The `data` must match the data for the [Spoolman API](https://donkie.github.io/Spoolman/#tag/spool/operation/Update_spool_spool__spool_id__patch)

> [!IMPORTANT]
> You can't update `remaining_weight` and `used_weight` in one update. You can only set one of them. Spoolmann calculates the missing field by itself.

```yaml
service: spoolman.patch_spool
data:
id: 45
first_used: "2019-08-24T14:15:22.000Z"
last_used: "2019-08-24T14:15:22.000Z"
price: 20
initial_weight: 200
spool_weight: 200
location: Shelf B
remaining_weight: 200
lot_nr: 52342
```

# Contributing
If you're developer and want to contribute to the project, please feel free to do a PR!
But there are some contraints I want to enforce by convention (currently I evaluate the possibility to enforce this by rules. If you have a good hint, please let me know 🎉):
Expand Down
32 changes: 20 additions & 12 deletions custom_components/spoolman/__init__.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
"""Spoolman home assistant integration."""
import logging

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .const import CONF_URL, DEFAULT_NAME, DOMAIN, SPOOLMAN_API_WRAPPER
from custom_components.spoolman.schema_helper import SchemaHelper

from .const import DOMAIN, SPOOLMAN_API_WRAPPER, SPOOLMAN_PATCH_SPOOL_SERVICENAME
from .coordinator import SpoolManCoordinator

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [Platform.SENSOR]

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, # type: ignore
vol.Required(CONF_URL): cv.string,
}
)


async def async_setup_platform(
hass: HomeAssistant, config, add_devices, discovery_info=None
Expand All @@ -42,6 +35,21 @@ async def async_setup_entry(hass: HomeAssistant, entry):
coordinator = SpoolManCoordinator(hass, entry)
await coordinator.async_refresh()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

async def handle_spoolman_patch_spool(call):
spool_id = call.data.get('id')
data = {key: call.data[key] for key in call.data if key != 'id'}
_LOGGER.info(f"Patch spool called with id: {spool_id} and data: {data}")

try:
await coordinator.spoolman_api.patch_spool(spool_id, data)
except Exception as e:
_LOGGER.error(f"Failed to patch spool: {e}")
raise HomeAssistantError(f"Failed to patch spool: {e}")


hass.services.async_register(DOMAIN, SPOOLMAN_PATCH_SPOOL_SERVICENAME, handle_spoolman_patch_spool, schema=SchemaHelper.get_spoolman_patch_spool_schema())

return True


Expand Down
38 changes: 19 additions & 19 deletions custom_components/spoolman/classes/klipper_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,28 @@ def __init__(self, base_url):
"""Initialize the Klipper API."""
self.base_url = add_trailing_slash(base_url)

async def get_active_spool_id(self) -> int | None:
"""Get active spool from Klipper API."""
async def _get_json(self, endpoint: str) -> dict:
"""Convert the response to JSON."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}server/spoolman/spool_id"
url = f"{self.base_url}server/{endpoint}"
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
# Extract the spool_id from the data dictionary
spool_id = data.get('result', {}).get("spool_id")
response.raise_for_status()
return await response.json()

async def get_active_spool_id(self) -> int | None:
"""Get active spool from Klipper API."""
data = await self._get_json("spoolman/spool_id")
spool_id = data.get('result', {}).get("spool_id")

if spool_id is not None:
try:
return int(spool_id)
except ValueError:
raise ValueError(f"Invalid spool_id: {spool_id}")

# Convert spool_id to an integer if it is not None; otherwise, return None
return int(spool_id) if spool_id is not None else None
return None

else:
return 0
async def api_version(self) -> str | None:
"""Get api version from Klipper API."""
async with aiohttp.ClientSession() as session:
url = f"{self.base_url}server/info"
async with session.get(url) as response:
if response.status == 200:
data = await response.json()
return data.get('result').get("api_version_string")
else:
return None
data = await self._get_json("info")
return data.get('result', {}).get("api_version_string")
37 changes: 31 additions & 6 deletions custom_components/spoolman/classes/spoolman_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
_LOGGER = logging.getLogger(__name__)


class SpoolmanAPI:
"""Class for interacting with the Spoolman API."""

Expand Down Expand Up @@ -40,22 +39,20 @@ async def backup(self):
url = f"{self.base_url}/backup"
async with aiohttp.ClientSession() as session, session.post(url) as response:
response.raise_for_status()

response = await response.json()
_LOGGER.debug("SpoolmanAPI: backup response %s", response)
return response

async def get_spools(self, params):
"""Return a list of all spools."""
_LOGGER.debug("SpoolmanAPI: get_spool")
_LOGGER.debug("SpoolmanAPI: get_spools")
url = f"{self.base_url}/spool"
if len(params) > 0:
url = f"{url}?{self.string_from_dictionary(params)}"
async with aiohttp.ClientSession() as session, session.get(url) as response:
response.raise_for_status()

response = await response.json()
_LOGGER.debug("SpoolmanAPI: get_spool response %s", response)
_LOGGER.debug("SpoolmanAPI: get_spools response %s", response)
return response

async def get_spool_by_id(self, spool_id):
Expand All @@ -69,7 +66,7 @@ async def get_spool_by_id(self, spool_id):
return response

def string_from_dictionary(self, params_dict):
"""Initialize an empty string to hold the result."""
"""Generate a query string from a dictionary of parameters."""
_LOGGER.debug("SpoolmanAPI: string_from_dictionary")
result_string = ""

Expand All @@ -87,3 +84,31 @@ def string_from_dictionary(self, params_dict):

# Return the result string
return result_string

async def patch_spool(self, spool_id, data):
"""Update the spool with the specified ID."""
_LOGGER.info(f"SpoolmanAPI: patch_spool {spool_id} with data {data}")

if "remaining_weight" in data and "used_weight" in data:
if data["remaining_weight"] > 0 and data["used_weight"] > 0:
raise ValueError("remaining_weight and used_weight cannot be used together. Please use only one of them.")

url = f"{self.base_url}/spool/{spool_id}"
try:
async with aiohttp.ClientSession() as session, session.patch(url, json=data) as response:
response.raise_for_status()
response_data = await response.json()
_LOGGER.debug("SpoolmanAPI: patch_spool response %s", response_data)
return response_data
except aiohttp.ClientResponseError as e:
_LOGGER.error(f"HTTP error occurred: {e.status} {e.message}")
raise
except aiohttp.ClientConnectionError as e:
_LOGGER.error(f"Connection error occurred: {e}")
raise
except aiohttp.ClientError as e:
_LOGGER.error(f"Client error occurred: {e}")
raise
except Exception as e:
_LOGGER.error(f"An unexpected error occurred: {e}")
raise
2 changes: 2 additions & 0 deletions custom_components/spoolman/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@

KLIPPER_URL = "klipper_url"
KLIPPER_URL_DESC = "klipper_url_desc"

SPOOLMAN_PATCH_SPOOL_SERVICENAME = "patch_spool"
2 changes: 1 addition & 1 deletion custom_components/spoolman/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "spoolman",
"name": "Spoolman",
"codeowners": [
"@mfranke87"
"@disane87"
],
"config_flow": true,
"dependencies": [],
Expand Down
21 changes: 21 additions & 0 deletions custom_components/spoolman/schema_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Schema helper."""
from typing import Any
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from .const import (
CONF_NOTIFICATION_THRESHOLD_CRITICAL,
CONF_NOTIFICATION_THRESHOLD_INFO,
Expand All @@ -17,6 +18,26 @@
class SchemaHelper:
"""Schema helper contains the config and options schema."""

@staticmethod
def get_spoolman_patch_spool_schema():
"""Get the schema for the spoolman_patch_spool service."""
return vol.Schema({
vol.Required('id'): cv.string,
vol.Optional('first_used'): cv.string,
vol.Optional('last_used'): cv.string,
vol.Optional('filament_id'): cv.positive_int,
vol.Optional('price'): vol.Coerce(float),
vol.Optional('initial_weight'): vol.Coerce(float),
vol.Optional('spool_weight'): vol.Coerce(float),
vol.Optional('remaining_weight'): vol.Coerce(float),
vol.Optional('used_weight'): vol.Coerce(float),
vol.Optional('location'): cv.string,
vol.Optional('lot_nr'): cv.string,
vol.Optional('comment'): cv.string,
vol.Optional('archived'): cv.boolean,
vol.Optional('extra'): vol.Schema({}, extra=vol.ALLOW_EXTRA),
})

@staticmethod
def get_config_schema(get_values=False, config_data=None):
"""Get the form for config and options flows."""
Expand Down
Loading