Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2d0452d
Add HAC installation to README
binarylogic Sep 3, 2024
fe90e30
dedup API_BASE, LOGIN_BASE; TIMEOUT unused
jcoalson Dec 30, 2024
eaef64f
typos
jcoalson Dec 30, 2024
008dbb5
Update homeassistant
bentekkie Dec 31, 2024
0e59fdd
Remove deprecated stuff
bentekkie Dec 31, 2024
12533ba
Merge pull request #89 from bentekkie/update
bentekkie Dec 31, 2024
f6c1dd6
Merge branch 'main' into patch-1
bentekkie Dec 31, 2024
c352676
Merge pull request #86 from binarylogic/patch-1
bentekkie Dec 31, 2024
65af56d
Merge branch 'main' into minor-cleanup
bentekkie Dec 31, 2024
1c2b83f
Merge pull request #88 from jcoalson/minor-cleanup
bentekkie Dec 31, 2024
caf27dc
Make date parsing more resiliant
bentekkie Dec 31, 2024
5266667
Merge branch 'main' into fix-date-parsing
bentekkie Dec 31, 2024
8ff70bd
fix formatting
bentekkie Dec 31, 2024
0daf655
Merge pull request #90 from bentekkie/fix-date-parsing
bentekkie Dec 31, 2024
4a31ae9
Bump actions/checkout from 3.5.3 to 4.2.2
dependabot[bot] Dec 31, 2024
475cf63
Merge pull request #91 from bentekkie/dependabot/github_actions/actio…
bentekkie Dec 31, 2024
5d23ebe
Bump actions/setup-python from 4.7.0 to 5.3.0
dependabot[bot] Dec 31, 2024
438e905
Bump beautifulsoup4 from 4.12.2 to 4.12.3 in /.github/workflows
dependabot[bot] Dec 31, 2024
3d983ca
Merge pull request #94 from bentekkie/dependabot/github_actions/actio…
bentekkie Dec 31, 2024
94f1770
Merge branch 'main' into dependabot/pip/dot-github/workflows/beautifu…
bentekkie Dec 31, 2024
efa4feb
Bump crazy-max/ghaction-github-labeler from 4.1.0 to 5.1.0
dependabot[bot] Dec 31, 2024
13bc314
Merge pull request #99 from bentekkie/dependabot/pip/dot-github/workf…
bentekkie Dec 31, 2024
07df9ca
Bump pre-commit from 3.3.3 to 4.0.1 in /.github/workflows
dependabot[bot] Dec 31, 2024
54fd28a
Merge pull request #95 from bentekkie/dependabot/pip/dot-github/workf…
bentekkie Dec 31, 2024
faf870a
Bump beautifulsoup4 from 4.12.2 to 4.12.3
dependabot[bot] Dec 31, 2024
4a9708f
Merge pull request #102 from bentekkie/dependabot/pip/beautifulsoup4-…
bentekkie Dec 31, 2024
0dfb7b4
Bump reorder-python-imports from 3.10.0 to 3.14.0
dependabot[bot] Dec 31, 2024
49e0f46
Merge pull request #101 from bentekkie/dependabot/pip/reorder-python-…
bentekkie Dec 31, 2024
e5e0120
Bump pre-commit from 3.3.3 to 4.0.1
dependabot[bot] Dec 31, 2024
9de8bd1
Merge pull request #100 from bentekkie/dependabot/pip/pre-commit-4.0.1
bentekkie Jan 1, 2025
3b051a7
Merge branch 'main' into dependabot/github_actions/crazy-max/ghaction…
bentekkie Jan 1, 2025
e430e59
Merge pull request #92 from bentekkie/dependabot/github_actions/crazy…
bentekkie Jan 1, 2025
2ea187d
Add headers to get around Incapsula WAF
tcatkins99 May 11, 2025
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": "hacs/default",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11",
"image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.12",
"postCreateCommand": "scripts/setup",
"customizations": {
"vscode": {
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/constraints.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
pip==23.2.1
pre-commit==3.3.3
pre-commit==4.0.1
black==23.7.0
flake8==6.1.0
reorder-python-imports==3.10.0
beautifulsoup4==4.12.2
beautifulsoup4==4.12.3
dacite==1.8.1
pipreqs==0.4.13
4 changes: 2 additions & 2 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v3.5.3
uses: actions/checkout@v4.2.2

- name: Run Labeler
uses: crazy-max/ghaction-github-labeler@v4.1.0
uses: crazy-max/ghaction-github-labeler@v5.1.0
with:
skip-delete: true
10 changes: 5 additions & 5 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ on:
- cron: "0 0 * * *"

env:
DEFAULT_PYTHON: 3.11
DEFAULT_PYTHON: 3.12

jobs:
pre-commit:
runs-on: "ubuntu-latest"
name: Pre-commit
steps:
- name: Check out the repository
uses: actions/checkout@v3.5.3
uses: actions/checkout@v4.2.2

- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v4.7.0
uses: actions/setup-python@v5.3.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

Expand All @@ -44,7 +44,7 @@ jobs:
name: HACS
steps:
- name: Check out the repository
uses: "actions/checkout@v3.5.3"
uses: "actions/checkout@v4.2.2"

- name: HACS validation
uses: "hacs/action@main"
Expand All @@ -57,7 +57,7 @@ jobs:
name: Hassfest
steps:
- name: Check out the repository
uses: "actions/checkout@v3.5.3"
uses: "actions/checkout@v4.2.2"

- name: Hassfest validation
uses: "home-assistant/actions/hassfest@master"
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@

## Installation

### Installation via [HACS](https://hacs.xyz)

1. [Open HACS](http://homeassistant.local:8123/hacs/dashboard) in your Home Assistant interface.
2. Add this repository as a "Custom Repository" (https://github.com/bentekkie/ha-generac).
3. On the HACs home screen, search for "Generac" and select it.
4. Click the "Download" in bottom right of the page.
5. Restart Home Assistant to apply the changes.
6. Navigate to Configuration > Integrations.
7. Click on the "+ Add Integration" button.
8. Search for "Generac" and select it.
9. Follow the on-screen instructions to complete the setup.

### Manual Installation

1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`).
2. If you do not have a `custom_components` directory (folder) there, you need to create it.
3. In the `custom_components` directory (folder) create a new folder called `generac`.
Expand Down Expand Up @@ -41,9 +55,9 @@ custom_components/generac/sensor.py
custom_components/generac/switch.py
```

## Configuration is done in the UI
## Configuration

<!---->
Configuration is done in the UI.

## Contributions are welcome!

Expand Down
8 changes: 1 addition & 7 deletions custom_components/generac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):

hass.data[DOMAIN][entry.entry_id] = coordinator

for platform in PLATFORMS:
if entry.options.get(platform, True):
coordinator.platforms.append(platform)
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(entry, platform)
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.add_update_listener(async_reload_entry)
return True

Expand Down
44 changes: 34 additions & 10 deletions custom_components/generac/api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""Sample API Client."""
"""Generac API Client."""

# Incorporates changes from https://github.com/bentekkie/ha-generac/pull/140

import json
import logging
from typing import Any
Expand All @@ -8,6 +11,7 @@
from bs4 import BeautifulSoup
from dacite import from_dict

from .const import ALLOWED_DEVICES
from .models import Apparatus
from .models import ApparatusDetail
from .models import Item
Expand Down Expand Up @@ -47,6 +51,13 @@ def __init__(
self._session = session
self._logged_in = False
self.csrf = ""
self._headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
}

async def async_get_data(self) -> dict[str, Item] | None:
"""Get data from the API."""
Expand All @@ -57,9 +68,9 @@ async def async_get_data(self) -> dict[str, Item] | None:
except SessionExpiredException:
self._logged_in = False
return await self.async_get_data()
return await self.get_generator_data()
return await self.get_device_data()

async def get_generator_data(self):
async def get_device_data(self):
apparatuses = await self.get_endpoint("/v2/Apparatus/list")
if apparatuses is None:
_LOGGER.debug("Could not decode apparatuses response")
Expand All @@ -70,11 +81,12 @@ async def get_generator_data(self):
data: dict[str, Item] = {}
for apparatus in apparatuses:
apparatus = from_dict(Apparatus, apparatus)
if apparatus.type != 0:
if apparatus.type not in ALLOWED_DEVICES:
_LOGGER.debug(
"Unknown apparatus type %s %s", apparatus.type, apparatus.name
)
continue

detail_json = await self.get_endpoint(
f"/v1/Apparatus/details/{apparatus.apparatusId}"
)
Expand All @@ -89,9 +101,11 @@ async def get_generator_data(self):

async def get_endpoint(self, endpoint: str):
try:
response = await self._session.get(
API_BASE + endpoint, headers={"X-Csrf-Token": self.csrf}
)
headers = {**self._headers}
if self.csrf:
headers["X-Csrf-Token"] = self.csrf

response = await self._session.get(API_BASE + endpoint, headers=headers)
if response.status == 204:
# no data
return None
Expand All @@ -111,9 +125,13 @@ async def get_endpoint(self, endpoint: str):

async def login(self) -> None:
"""Login to API"""
headers = {**self._headers}

login_response = await (
await self._session.get(
f"{API_BASE}/Auth/SignIn?email={self._username}", allow_redirects=True
f"{API_BASE}/Auth/SignIn?email={self._username}",
headers=headers,
allow_redirects=True,
)
).text()

Expand All @@ -138,9 +156,12 @@ async def login(self) -> None:
)
self.csrf = sign_in_config.csrf

headers = {**self._headers}
headers["X-Csrf-Token"] = sign_in_config.csrf

self_asserted_response = await self._session.post(
f"{LOGIN_BASE}/SelfAsserted",
headers={"X-Csrf-Token": sign_in_config.csrf},
headers=headers,
params={
"tx": "StateProperties=" + sign_in_config.transId,
"p": "B2C_1A_SignUpOrSigninOnline",
Expand All @@ -161,6 +182,7 @@ async def login(self) -> None:

confirmed_response = await self._session.get(
f"{LOGIN_BASE}/api/CombinedSigninAndSignup/confirmed",
headers=headers,
params={
"csrf_token": sign_in_config.csrf,
"tx": "StateProperties=" + sign_in_config.transId,
Expand Down Expand Up @@ -197,7 +219,9 @@ async def submit_form(self, response: str) -> bool:
form_data.add_field("state", login_state.attrs["value"])
form_data.add_field("code", login_code.attrs["value"])

login_response = await self._session.post(action, data=form_data)
login_response = await self._session.post(
action, data=form_data, headers=self._headers
)

if login_response.status != 200:
raise IOError(f"Bad api login response: {login_response.status}")
Expand Down
4 changes: 2 additions & 2 deletions custom_components/generac/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def redact(data: Any, redact_all: bool):
if is_ipv4(data):
return "REDACTED_IPV4"
if is_ipv6(data):
return "REDACTED_IPV4"
return "REDACTED_IPV6"
if redact_all:
return "REDACTED"
return data
Expand All @@ -58,7 +58,7 @@ def is_ipv4(s: str):

def is_ipv6(s: str):
try:
ipaddress.IPv4Network(s)
ipaddress.IPv6Network(s)
return True
except ValueError:
return False
Expand Down
2 changes: 1 addition & 1 deletion custom_components/generac/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import ATTRIBUTION
Expand Down
2 changes: 1 addition & 1 deletion custom_components/generac/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
):
"""Setup binary_sensor platform."""
"""Setup image platform."""
coordinator: GeneracDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
data = coordinator.data
if isinstance(data, dict):
Expand Down
Loading