Skip to content

Commit 9407683

Browse files
authored
Merge pull request #207 from binarydev/cookie-auth
Add cookie auth and update field IDs to fix #203 and #202
2 parents df78bf9 + 7ddeedf commit 9407683

File tree

20 files changed

+166
-25
lines changed

20 files changed

+166
-25
lines changed

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Click this button to skip steps 1 and 2 below: [![Open your Home Assistant insta
2929
5. Once Home Assistant comes back online, go to Settings -> Integrations
3030
6. Click the `Add Integration` button
3131
7. Search the list for `generac` and select it
32-
8. Enter the credentials you use to login for https://app.mobilelinkgen.com/ and submit the form
32+
8. Enter the credentials you use to login for https://app.mobilelinkgen.com/ and submit the form. As an alternative, if username/password do not work for you, follow the instructions below for Cookie-based authentication.
3333
9. The integration should initialize and begin pulling your device information within seconds
3434

3535
## Installation (without HACS)
@@ -44,6 +44,30 @@ Click this button to skip steps 1 and 2 below: [![Open your Home Assistant insta
4444

4545
## Configuration is done in the UI
4646

47+
## Cookie-based Authentication
48+
49+
Using Username+Password to login is currently broken, due to Generac blocking automated scripts from logging in on the MobileLink app via a Captcha. Instead, the recommended method of authentication is to manually login and retrieve your session cookie. This requires you to do the following:
50+
51+
1. Log into https://app.mobilelinkgen.com/ until you reach the main dashboard with your devices.
52+
2. Open the web-browser Developer Tools aka "devtools" (e.g. in Chrome, right-click the page and hit the Inspect option).
53+
3. Go to the Network tab in the devtools panel and refresh your browser.
54+
4. The Network tab will now have a long list of things it just loaded, but the one you care about is named "dashboard" and should be the first or one of the first items in the list, as seen [here](./setup_instructions/network-tab.png). Select it.
55+
5. Select the "dashboard" item in the list, and it will open a panel to the right-hand side that shows you more details, which looks like [this](./setup_instructions/network-tab-dashboard.jpg).
56+
6. Scroll down in that right-hand panel until you find a field named Cookie, which has a LARGE block of text (it will likely start with the letters "incap_ses", like [this](./setup_instructions/cookie.png)). That large block of text is what you want.
57+
7. Double click the large block of text to select it all.
58+
8. Copy-paste it into the "Session Cookie" field for the Generac setup UI in Home Assistant.
59+
9. Hit submit and enjoy your integration!
60+
61+
> [!IMPORTANT]
62+
>
63+
> ## Unusual Integration Setup
64+
>
65+
> Status Quo in summer 2025: This integration requires an unusual setup process to be able to access the data of your Generac devices. This is because Generac has changed (once again) the original authentication workflow to actively block third-party access. They state that Home Assistant users were overloading their API. We have since adjusted accordingly to adapt to the new authenatication method and reduced our default polling interval from every 30 seconds to every 120 seconds, to be a "good" user of the API and cut the volume of traffic to their servers by 75%. This polling interval can also be tuned to your needs in the options panel of the HA integration once you have set it up, as seen [here](./setup_instructions/options.png).
66+
>
67+
> This approach implies that when Generac is going to change something in their non-public/undocumented API, it's quite likely that the integration will break instantly.
68+
>
69+
> **It's impossible to predict** when this will happen, but **I will try** to keep the integration up-to-date and working **as long as possible**.
70+
4771
<!---->
4872

4973
## Contributions are welcome!

_test_local_access.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def main():
3333
jar = aiohttp.CookieJar(unsafe=True, quote_cookie=False)
3434
async with aiohttp.ClientSession(cookie_jar=jar) as session:
3535
api = GeneracApiClient(
36-
os.environ["GENERAC_USER"], os.environ["GENERAC_PASS"], session
36+
session, os.environ["GENERAC_USER"], os.environ["GENERAC_PASS"]
3737
)
3838
await api.login()
3939
device_data = await api.get_device_data()

custom_components/generac/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from .api import GeneracApiClient
1414
from .const import CONF_PASSWORD
15+
from .const import CONF_SESSION_COOKIE
1516
from .const import CONF_USERNAME
1617
from .const import DOMAIN
1718
from .const import PLATFORMS
@@ -28,11 +29,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
2829
hass.data.setdefault(DOMAIN, {})
2930
_LOGGER.info(STARTUP_MESSAGE)
3031

31-
username = entry.data.get(CONF_USERNAME, "")
32+
username = entry.data.get(CONF_USERNAME, "generac")
3233
password = entry.data.get(CONF_PASSWORD, "")
34+
session_cookie = entry.data.get(CONF_SESSION_COOKIE, "")
3335

3436
session = await async_client_session(hass)
35-
client = GeneracApiClient(username, password, session)
37+
client = GeneracApiClient(session, username, password, session_cookie)
3638

3739
coordinator = GeneracDataUpdateCoordinator(hass, client=client, config_entry=entry)
3840
await coordinator.async_config_entry_first_refresh()

custom_components/generac/api.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,17 @@ def get_setting_json(page: str) -> Mapping[str, Any] | None:
4040

4141
class GeneracApiClient:
4242
def __init__(
43-
self, username: str, password: str, session: aiohttp.ClientSession
43+
self,
44+
session: aiohttp.ClientSession,
45+
username: str,
46+
password: str,
47+
session_cookie: str,
4448
) -> None:
4549
"""Sample API Client."""
4650
self._username = username
4751
self._passeword = password
4852
self._session = session
53+
self._session_cookie = session_cookie
4954
self._logged_in = False
5055
self.csrf = ""
5156
# Below is the login fix from https://github.com/bentekkie/ha-generac/pull/140
@@ -60,7 +65,10 @@ def __init__(
6065
async def async_get_data(self) -> dict[str, Item] | None:
6166
"""Get data from the API."""
6267
try:
63-
if not self._logged_in:
68+
if self._session_cookie:
69+
self._headers["Cookie"] = self._session_cookie
70+
self._logged_in = True
71+
elif not self._logged_in:
6472
await self.login()
6573
self._logged_in = True
6674
except SessionExpiredException:

custom_components/generac/config_flow.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .api import InvalidCredentialsException
1010
from .const import CONF_OPTIONS
1111
from .const import CONF_PASSWORD
12+
from .const import CONF_SESSION_COOKIE
1213
from .const import CONF_USERNAME
1314
from .const import DOMAIN
1415
from .utils import async_client_session
@@ -36,11 +37,13 @@ async def async_step_user(self, user_input=None):
3637

3738
if user_input is not None:
3839
error = await self._test_credentials(
39-
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
40+
user_input.get(CONF_USERNAME, ""),
41+
user_input.get(CONF_PASSWORD, ""),
42+
user_input.get(CONF_SESSION_COOKIE, ""),
4043
)
4144
if error is None:
4245
return self.async_create_entry(
43-
title=user_input[CONF_USERNAME], data=user_input
46+
title=user_input.get(CONF_USERNAME, "generac"), data=user_input
4447
)
4548
else:
4649
self._errors["base"] = error
@@ -59,16 +62,20 @@ async def _show_config_form(self, user_input): # pylint: disable=unused-argumen
5962
return self.async_show_form(
6063
step_id="user",
6164
data_schema=vol.Schema(
62-
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
65+
{
66+
vol.Optional(CONF_USERNAME): str,
67+
vol.Optional(CONF_PASSWORD): str,
68+
vol.Optional(CONF_SESSION_COOKIE): str,
69+
}
6370
),
6471
errors=self._errors,
6572
)
6673

67-
async def _test_credentials(self, username, password):
74+
async def _test_credentials(self, username, password, session_cookie):
6875
"""Return true if credentials is valid."""
6976
try:
7077
session = await async_client_session(self.hass)
71-
client = GeneracApiClient(username, password, session)
78+
client = GeneracApiClient(session, username, password, session_cookie)
7279
await client.async_get_data()
7380
return None
7481
except InvalidCredentialsException as e: # pylint: disable=broad-except
@@ -115,5 +122,6 @@ async def async_step_user(self, user_input=None):
115122
async def _update_options(self):
116123
"""Update config entry options."""
117124
return self.async_create_entry(
118-
title=self.config_entry.data.get(CONF_USERNAME), data=self.options
125+
title=self.config_entry.data.get(CONF_USERNAME, "generac"),
126+
data=self.options,
119127
)

custom_components/generac/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
# Defaults
2828
DEFAULT_NAME = DOMAIN
29-
DEFAULT_SCAN_INTERVAL = 30
29+
DEFAULT_SCAN_INTERVAL = 120
3030

3131
# Platforms
3232
BINARY_SENSOR = "binary_sensor"
@@ -39,6 +39,7 @@
3939
CONF_ENABLED = "enabled"
4040
CONF_USERNAME = "username"
4141
CONF_PASSWORD = "password"
42+
CONF_SESSION_COOKIE = "session_cookie"
4243
CONF_SCAN_INTERVAL = "scan_interval"
4344

4445
# Options

custom_components/generac/sensor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def name(self):
197197
@property
198198
def native_value(self):
199199
"""Return the state of the sensor."""
200-
val = get_prop_value(self.aparatus_detail.properties, 70, 0)
200+
val = get_prop_value(self.aparatus_detail.properties, 71, 0)
201201
if isinstance(val, str):
202202
val = float(val)
203203
return val
@@ -217,7 +217,7 @@ def name(self):
217217
@property
218218
def native_value(self):
219219
"""Return the state of the sensor."""
220-
val = get_prop_value(self.aparatus_detail.properties, 31, 0)
220+
val = get_prop_value(self.aparatus_detail.properties, 32, 0)
221221
if isinstance(val, str):
222222
val = float(val)
223223
return val
@@ -294,7 +294,7 @@ def name(self):
294294
@property
295295
def native_value(self):
296296
"""Return the state of the sensor."""
297-
val = get_prop_value(self.aparatus_detail.properties, 69, 0)
297+
val = get_prop_value(self.aparatus_detail.properties, 70, 0)
298298
if isinstance(val, str):
299299
val = float(val)
300300
return val

custom_components/generac/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"description": "If you need help with the configuration have a look here: https://github.com/binarydev/ha-generac",
77
"data": {
88
"username": "Username",
9-
"password": "Password"
9+
"password": "Password",
10+
"session_cookie": "Session Cookie (optional alternative to username/password)"
1011
}
1112
}
1213
},

custom_components/generac/translations/fr.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"description": "Si vous avez besoin d'aide pour la configuration, regardez ici: https://github.com/binarydev/ha-generac",
77
"data": {
88
"username": "Identifiant",
9-
"password": "Mot de Passe"
9+
"password": "Mot de Passe",
10+
"session_cookie": "Cookie de session (alternative optionnelle à l'identifiant/mot de passe)"
1011
}
1112
}
1213
},

custom_components/generac/translations/nb.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/binarydev/ha-generac",
77
"data": {
88
"username": "Brukernavn",
9-
"password": "Passord"
9+
"password": "Passord",
10+
"session_cookie": "Sesjonskake (valgfritt alternativ til brukernavn/passord)"
1011
}
1112
}
1213
},

0 commit comments

Comments
 (0)