Skip to content

Commit 4018e22

Browse files
author
Jon Kristian
committed
WIP. Working setup with new API access. Needs work, expect bugs!
1 parent b092100 commit 4018e22

23 files changed

+328
-164
lines changed

.gitignore

+17-11
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
# Packages
2-
*.egg
1+
# artifacts
2+
__pycache__
3+
.pytest*
34
*.egg-info
4-
eggs
5-
.eggs
5+
*/build/*
6+
*/dist/*
7+
*.log*
8+
*.db*
69

7-
# GITHUB Proposed Python stuff:
8-
*.py[cod]
910

10-
# emacs auto backups
11-
*~
12-
*#
13-
*.orig
14-
.idea
11+
# misc
12+
.coverage
13+
.vscode
14+
coverage.xml
15+
pyalko
16+
17+
18+
# Home Assistant configuration
19+
config/*
20+
!config/configuration.yaml

CONTRIBUTING.md

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Contribution guidelines
2+
3+
Contributing to this project should be as easy and transparent as possible, whether it's:
4+
5+
- Reporting a bug
6+
- Discussing the current state of the code
7+
- Submitting a fix
8+
- Proposing new features
9+
10+
## Github is used for everything
11+
12+
Github is used to host code, to track issues and feature requests, as well as accept pull requests.
13+
14+
Pull requests are the best way to propose changes to the codebase.
15+
16+
1. Fork the repo and create your branch from `master`.
17+
2. If you've changed something, update the documentation.
18+
3. Make sure your code lints (using black).
19+
4. Test you contribution.
20+
5. Issue that pull request!
21+
22+
## Any contributions you make will be under the MIT Software License
23+
24+
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
25+
26+
## Report bugs using Github's [issues](../../issues)
27+
28+
GitHub issues are used to track public bugs.
29+
Report a bug by [opening a new issue](../../issues/new/choose); it's that easy!
30+
31+
## Write bug reports with detail, background, and sample code
32+
33+
**Great Bug Reports** tend to have:
34+
35+
- A quick summary and/or background
36+
- Steps to reproduce
37+
- Be specific!
38+
- Give sample code if you can.
39+
- What you expected would happen
40+
- What actually happens
41+
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
42+
43+
People *love* thorough bug reports. I'm not even kidding.
44+
45+
## Use a Consistent Coding Style
46+
47+
Use [black](https://github.com/ambv/black) to make sure the code follows the style.
48+
49+
## Test your code modification
50+
51+
This custom component uses [integration_blueprint template](https://github.com/ludeeus/integration_blueprint) devcontainer.
52+
53+
It comes with development environment in a container, easy to launch
54+
if you use Visual Studio Code. With this container you will have a stand alone
55+
Home Assistant instance running and already configured with the included
56+
[`configuration.yaml`](./configuration.yaml)
57+
file.
58+
59+
## License
60+
61+
By contributing, you agree that your contributions will be licensed under its MIT License.

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Jon Kristian Nilsen
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

100644100755
+3-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# AL-KO Robolinho component for Home Assistant
22
This component allows you to integrate your AL-KO Robolinho mower in Home Assistant.
3-
For advanced users only, please see further explaination below.
43

54
## Supports
65
- Battery level sensor
@@ -17,36 +16,15 @@ For advanced users only, please see further explaination below.
1716

1817
# Installation
1918

19+
## Requesting API access
20+
Before installing you should know that you will have to to request access to the API. [Fill out this form](https://alko-garden.com/api-access). If all went well you will receive your credentials.
21+
2022
## Manual or via HACS
2123
If you're using HACS you can add this repo as a custom repository and install, otherwise download or clone and copy the folder `custom_components/alko` into your `custom_components/`. Be sure to restart.
2224

23-
## Installing a working app
24-
As stated in [issue #2](https://github.com/jonkristian/alko/issues/2), it is not possible to extract the credentials from recent AL-KO InTouch app (probably since version 4.0.0). In order to extract the credentials from the app, you need to download an older app version.
25-
26-
- Download and install older InTouch app from the any APK mirrors website. If you have the "official" InTouch app from Google Play installed, remember to unsinstall it first. Tested and confirmed on v3.4.2 version downloaded from APKPure. Unfortunately, only .xapk file is available there, so you need to download APKPure app itself and then install InTouch v3.4.2 using the APKPure app.
27-
- Log in to the app and enter "My devices" tab (to make sure that the app downloaded devices information).
28-
- Proceed with further instructions below regarding token extraction using adb and intouch-credentials.sh script.
29-
30-
## Finding secret and tokens (Android only | Requires adb)
31-
The AL-KO API is unfortunately not open for 3rd party applications. In order for this component to work you will have to have set up the AL-KO inTOUCH app and a [working adb connection to your phone](https://developer.android.com/studio/command-line/adb). To extract your secret and tokens I've created a very simple and crude script that will output these in the terminal.
32-
- Download [intouch-credentials.sh](https://raw.githubusercontent.com/jonkristian/alko/master/tools/intouch-credentials.sh) (located in tools folder).
33-
- Then `chmod +x intouch-credentials.sh` and `./intouch-credentials.sh` to run.
34-
- Now unlock your phone and click confirm the backup operation (do not encrypt).
35-
- This should print your `client_secret`, `access_token` and `refresh_token` the latter two is required during the integration setup step.
36-
- Add the following to your configuration.yaml.
37-
38-
```yaml
39-
alko:
40-
client_id: inTouchApp
41-
client_secret: your_client_secret
42-
```
43-
After a restart you should now be able to add the integration via the integrations page, remember that tokens are short-lived 30 minutes.
44-
***
4525
## Contribute
4626
If you own a smart product from AL-KO and would like to contribute, please don't hesitate getting in touch.
4727

48-
### Regarding the AL-KO Api
49-
It's really sad that many manufacturers don't allow for 3rd party apps, since these APIs are already there and available through their own app most of the time, I personally don't see what the harm is. I've already sent an e-mail to AL-KO requesting that they open up to allow for more advanced home automation platforms like Home Assistant, but weeks have gone and I don't really expect getting a reply back. Maybe they will listen if more people get involved so if you want to try and do something about this, the e-mail I've found is gardentech@al-ko.com.
5028
***
5129
⭐️ this repository if you found it useful ❤️
5230

configuration.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# https://www.home-assistant.io/integrations/default_config/
2+
default_config:
3+
4+
# https://www.home-assistant.io/integrations/homeassistant/
5+
homeassistant:
6+
debug: true
7+
8+
# https://www.home-assistant.io/integrations/logger/
9+
logger:
10+
default: info
11+
logs:
12+
custom_components.alko: debug

custom_components/alko/__init__.py

100644100755
+21-58
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""The AL-KO integration."""
2+
23
from __future__ import annotations
34

45
from datetime import timedelta
@@ -11,91 +12,55 @@
1112
import async_timeout
1213
import voluptuous as vol
1314

14-
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
15-
from homeassistant.components.select import DOMAIN as SELECT_DOMAIN
16-
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
15+
from homeassistant.const import Platform
1716
from homeassistant.config_entries import ConfigEntry
18-
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
1917
from homeassistant.core import HomeAssistant
2018
from homeassistant.exceptions import ConfigEntryAuthFailed
21-
from homeassistant.helpers import (
22-
aiohttp_client,
23-
config_entry_oauth2_flow,
24-
config_validation as cv,
25-
)
19+
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
2620
from homeassistant.helpers.entity import DeviceInfo
2721
from homeassistant.helpers.update_coordinator import (
2822
CoordinatorEntity,
2923
DataUpdateCoordinator,
3024
UpdateFailed,
3125
)
3226

33-
from .api import ConfigEntryAlkoClient, AlkoLocalOAuth2Implementation
34-
from .config_flow import OAuth2FlowHandler
35-
from .const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN
36-
37-
CONFIG_SCHEMA = vol.Schema(
38-
{
39-
DOMAIN: vol.Schema(
40-
{
41-
vol.Required(CONF_CLIENT_ID): cv.string,
42-
vol.Required(CONF_CLIENT_SECRET): cv.string,
43-
}
44-
)
45-
},
46-
extra=vol.ALLOW_EXTRA,
27+
from .api import (
28+
ConfigEntryAlkoClient,
29+
AlkoLocalOAuth2Implementation,
30+
OAuth2SessionAlko
4731
)
4832

49-
_LOGGER = logging.getLogger(__name__)
50-
51-
PLATFORMS = (SENSOR_DOMAIN, SELECT_DOMAIN, SWITCH_DOMAIN)
52-
53-
54-
async def async_setup(hass: HomeAssistant, config: dict):
55-
"""Set up the AL-KO component."""
56-
hass.data[DOMAIN] = {}
57-
58-
if DOMAIN not in config:
59-
return True
33+
from .const import DOMAIN
6034

61-
hass.data[DOMAIN][CONF_CLIENT_ID] = config[DOMAIN][CONF_CLIENT_ID]
35+
_LOGGER = logging.getLogger(__name__)
6236

63-
OAuth2FlowHandler.async_register_implementation(
64-
hass,
65-
AlkoLocalOAuth2Implementation(
66-
hass,
67-
DOMAIN,
68-
config[DOMAIN][CONF_CLIENT_ID],
69-
config[DOMAIN][CONF_CLIENT_SECRET],
70-
OAUTH2_AUTHORIZE,
71-
OAUTH2_TOKEN,
72-
),
73-
)
74-
75-
return True
37+
PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.SELECT]
7638

7739

7840
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
79-
"""Set up AL-KO from a config entry."""
41+
"""Set up ALKO from a config entry."""
8042
implementation = (
8143
await config_entry_oauth2_flow.async_get_config_entry_implementation(
8244
hass, entry
8345
)
8446
)
47+
if not isinstance(implementation, AlkoLocalOAuth2Implementation):
48+
raise TypeError(
49+
"Unexpected auth implementation; can't find oauth client id")
8550

8651
session = aiohttp_client.async_get_clientsession(hass)
87-
oauth_session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
52+
oauth_session = OAuth2SessionAlko(hass, entry, implementation)
8853

8954
client = ConfigEntryAlkoClient(session, oauth_session)
90-
91-
client_id = hass.data[DOMAIN][CONF_CLIENT_ID]
55+
client_id = implementation.client_id
9256
alko = Alko(client, client_id)
9357

9458
async def async_update_data() -> Alko:
9559
"""Fetch data from Alko."""
9660
try:
9761
async with async_timeout.timeout(60):
9862
await alko.get_devices()
63+
_LOGGER.debug("Fetched devices: %s", repr(alko.devices))
9964
return alko
10065
except AlkoAuthenticationException as exception:
10166
raise ConfigEntryAuthFailed from exception
@@ -112,12 +77,11 @@ async def async_update_data() -> Alko:
11277
update_interval=timedelta(seconds=120),
11378
)
11479

115-
hass.data[DOMAIN][entry.entry_id] = coordinator
116-
11780
# Fetch initial data so we have data when entities subscribe
11881
await coordinator.async_config_entry_first_refresh()
82+
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
11983

120-
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
84+
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
12185

12286
return True
12387

@@ -131,12 +95,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
13195
return unload_ok
13296

13397

134-
class AlkoEntity(CoordinatorEntity):
98+
class AlkoEntity(CoordinatorEntity[DataUpdateCoordinator[Alko]]):
13599
"""Defines a base AL-KO entity."""
136100

137101
def __init__(
138102
self,
139-
coordinator: DataUpdateCoordinator,
103+
coordinator: DataUpdateCoordinator[Alko],
140104
device: AlkoDevice,
141105
key: str,
142106
name: str,
@@ -178,6 +142,5 @@ def device_info(self) -> DeviceInfo:
178142
"manufacturer": "AL-KO",
179143
"model": self._device_model,
180144
"name": self._device_name,
181-
"device_type": self._device_type,
182145
"sw_version": self._firmware_main,
183146
}

custom_components/alko/api.py

100644100755
+14-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,24 @@
55
from aiohttp import BasicAuth, ClientSession
66
from pyalko import AlkoClient
77

8+
from homeassistant.components.application_credentials import AuthImplementation
89
from homeassistant.helpers import config_entry_oauth2_flow
910
from homeassistant.helpers.aiohttp_client import async_get_clientsession
1011

1112
_LOGGER = logging.getLogger(__name__)
1213

14+
class OAuth2SessionAlko(config_entry_oauth2_flow.OAuth2Session):
15+
"""OAuth2Session for Alko."""
16+
17+
async def force_refresh_token(self) -> None:
18+
"""Force a token refresh."""
19+
new_token = await self.implementation.async_refresh_token(self.token)
20+
21+
self.hass.config_entries.async_update_entry(
22+
self.config_entry, data={
23+
**self.config_entry.data, "token": new_token}
24+
)
25+
1326

1427
class ConfigEntryAlkoClient(AlkoClient):
1528
"""Provide AL-KO authentication tied to an OAuth2 based config entry."""
@@ -32,7 +45,7 @@ async def async_get_access_token(self):
3245

3346

3447
class AlkoLocalOAuth2Implementation(
35-
config_entry_oauth2_flow.LocalOAuth2Implementation
48+
AuthImplementation,
3649
):
3750
"""AL-KO Local OAuth2 implementation."""
3851

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Application credentials platform for the ALKO integration."""
2+
3+
from homeassistant.components.application_credentials import (
4+
AuthorizationServer,
5+
ClientCredential,
6+
)
7+
from homeassistant.core import HomeAssistant
8+
from homeassistant.helpers import config_entry_oauth2_flow
9+
10+
from .api import AlkoLocalOAuth2Implementation
11+
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
12+
13+
14+
async def async_get_auth_implementation(
15+
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
16+
) -> config_entry_oauth2_flow.AbstractOAuth2Implementation:
17+
"""Return custom auth implementation."""
18+
return AlkoLocalOAuth2Implementation(
19+
hass,
20+
auth_domain,
21+
credential,
22+
AuthorizationServer(
23+
# authorize_url=OAUTH2_AUTHORIZE,
24+
authorize_url="", # Overridden in config flow.
25+
token_url=OAUTH2_TOKEN,
26+
),
27+
)

0 commit comments

Comments
 (0)