From ad8bd7c90cccf84431d33603b4309c1c1feed8c5 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:43:57 -0600 Subject: [PATCH 01/22] Allowing changing the polling rate through GUI --- custom_components/bitaxe/__init__.py | 21 +++++++-- custom_components/bitaxe/config_flow.py | 63 ++++++++++++++++++------- custom_components/bitaxe/const.py | 6 +++ 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/custom_components/bitaxe/__init__.py b/custom_components/bitaxe/__init__.py index 59b55fc..fcc16d3 100644 --- a/custom_components/bitaxe/__init__.py +++ b/custom_components/bitaxe/__init__.py @@ -6,19 +6,21 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.event import async_track_time_interval -DOMAIN = "bitaxe" +from .const import DOMAIN, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): ip_address = entry.data["ip_address"] device_id = entry.unique_id or ip_address + scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) coordinator = DataUpdateCoordinator( hass, _LOGGER, name=f"BitAxe Sensor Data ({device_id})", update_method=lambda: fetch_bitaxe_data(ip_address), - update_interval=timedelta(seconds=30), + update_interval=timedelta(seconds=scan_interval), ) await coordinator.async_refresh() @@ -27,16 +29,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEnt hass.data[DOMAIN] = {} hass.data[DOMAIN][device_id] = {"coordinator": coordinator} + # Add option to edit settings after creating instance + entry.async_on_unload( + entry.add_update_listener(_options_update_listener) + ) + await hass.config_entries.async_forward_entry_setups(entry, ["sensor"]) async_track_time_interval( hass, _update_coordinator(coordinator), - timedelta(seconds=30) + timedelta(seconds=scan_interval) ) return True +async def _options_update_listener(hass, entry): + await hass.config_entries.async_reload(entry.entry_id) + +async def async_unload_entry(hass, entry): + unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor"]) + hass.data[DOMAIN].pop(entry.unique_id, None) + return unload_ok + def _update_coordinator(coordinator: DataUpdateCoordinator): async def refresh(now): await coordinator.async_request_refresh() diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 8529271..8b00d5f 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -1,9 +1,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback -import ipaddress # Import für IP-Adressenvalidierung +import ipaddress -from .const import DOMAIN +from .const import ( + DOMAIN, MIN_SCAN_INTERVAL, MAX_SCAN_INTERVAL, + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL +) class BitAxeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 @@ -15,31 +18,37 @@ async def async_step_user(self, user_input=None): if user_input is not None: ip_address = user_input["ip_address"] device_name = user_input["device_name"] + scan_interval = user_input[CONF_SCAN_INTERVAL] - # Validierung der IP-Adresse + # Validate IP address try: ipaddress.ip_address(ip_address) except ValueError: - errors["ip_address"] = "Invalid IP address format." + errors["ip_address"] = "invalid_ip" return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), - errors=errors + errors=errors, ) - # Entry mit IP-Adresse und Gerätenamen erstellen - await self.async_set_unique_id(ip_address) # Einzigartige ID auf IP-Adresse setzen - self._abort_if_unique_id_configured() # Sicherstellen, dass die IP-Adresse nicht doppelt hinzugefügt wird + await self.async_set_unique_id(ip_address) + self._abort_if_unique_id_configured() return self.async_create_entry( title=device_name, - data={"ip_address": ip_address, "device_name": device_name} + data={ + "ip_address": ip_address, + "device_name": device_name, + }, + options={ + CONF_SCAN_INTERVAL: scan_interval, + }, ) return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), - errors=errors + errors=errors, ) def get_data_schema(self): @@ -47,18 +56,38 @@ def get_data_schema(self): return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, + vol.Optional( + CONF_SCAN_INTERVAL, + default=DEFAULT_SCAN_INTERVAL, + ): vol.All( + vol.Coerce(int), + vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), }) @staticmethod @callback def async_get_options_flow(config_entry): - return BitAxeOptionsFlowHandler(config_entry) + return BitAxeOptionsFlowHandler() -class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): - def __init__(self, config_entry): - """Initialize options flow.""" - self.config_entry = config_entry +class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): - """Manage the options.""" - return self.async_show_form(step_id="init") \ No newline at end of file + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema({ + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, + ), + ): vol.All( + vol.Coerce(int), + vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), + }), + ) diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index 4d33878..4e04af2 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1 +1,7 @@ DOMAIN = "bitaxe" + +CONF_SCAN_INTERVAL = "scan_interval (s)" + +DEFAULT_SCAN_INTERVAL = 60 # seconds +MIN_SCAN_INTERVAL = 30 +MAX_SCAN_INTERVAL = 3600 From 874654419a232273c670fd2436169a04daf59e1f Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:46:54 -0600 Subject: [PATCH 02/22] Bumping version number --- README.md | 2 +- hacs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9845aec..700298f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To set up the integration, follow these steps: 5. Complete the setup. ## Version -This is version (v1.1.0) compatible with HACS! +This is version (v1.2.0) compatible with HACS! ## Screenshots diff --git a/hacs.json b/hacs.json index 6f34860..9c5e9c6 100644 --- a/hacs.json +++ b/hacs.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "render_readme": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.1.0", + "version": "1.2.0", "zip_release": false, "filename": "custom_components/bitaxe", "codeowners": ["@DerMiika"] From 46e279b76a8bf2d2036f57f4e80195e94af55817 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:55:34 -0600 Subject: [PATCH 03/22] Readding the original comments --- custom_components/bitaxe/config_flow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 8b00d5f..868af8a 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -1,7 +1,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback -import ipaddress +import ipaddress # Import für IP-Adressenvalidierung from .const import ( DOMAIN, MIN_SCAN_INTERVAL, MAX_SCAN_INTERVAL, @@ -20,19 +20,20 @@ async def async_step_user(self, user_input=None): device_name = user_input["device_name"] scan_interval = user_input[CONF_SCAN_INTERVAL] - # Validate IP address + # Validierung der IP-Adresse try: ipaddress.ip_address(ip_address) except ValueError: - errors["ip_address"] = "invalid_ip" + errors["ip_address"] = "Invalid IP address format." return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), errors=errors, ) - await self.async_set_unique_id(ip_address) - self._abort_if_unique_id_configured() + # Entry mit IP-Adresse und Gerätenamen erstellen + await self.async_set_unique_id(ip_address) # Einzigartige ID auf IP-Adresse setzen + self._abort_if_unique_id_configured() # Sicherstellen, dass die IP-Adresse nicht doppelt hinzugefügt wird return self.async_create_entry( title=device_name, From d3bfa51d09785551dfef4068413f208eb9e6dc86 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:02:28 -0600 Subject: [PATCH 04/22] Trying to make it report an error with invalid input --- custom_components/bitaxe/config_flow.py | 14 ++++++++------ custom_components/bitaxe/const.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 868af8a..956ea34 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -51,21 +51,23 @@ async def async_step_user(self, user_input=None): data_schema=self.get_data_schema(), errors=errors, ) + + def validate_scan_interval(value): + value = int(value) + if value < MIN_SCAN_INTERVAL or value > MAX_SCAN_INTERVAL: + raise vol.Invalid("Polling rate must be between 5 and 3600 seconds") + return value def get_data_schema(self): - """Return the schema for user input.""" return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL, - ): vol.All( - vol.Coerce(int), - vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), - ), + ): self.validate_scan_interval, }) - + @staticmethod @callback def async_get_options_flow(config_entry): diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index 4e04af2..f3e4b49 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1,6 +1,6 @@ DOMAIN = "bitaxe" -CONF_SCAN_INTERVAL = "scan_interval (s)" +CONF_SCAN_INTERVAL = "Polling Rate (s)" DEFAULT_SCAN_INTERVAL = 60 # seconds MIN_SCAN_INTERVAL = 30 From b400c8f42a8458a3342131c2d5000eb56aa1ce94 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:23:08 -0600 Subject: [PATCH 05/22] Preventing user inputting invalid polling --- custom_components/bitaxe/config_flow.py | 14 ++++++-------- custom_components/bitaxe/const.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 956ea34..c26128e 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -51,12 +51,6 @@ async def async_step_user(self, user_input=None): data_schema=self.get_data_schema(), errors=errors, ) - - def validate_scan_interval(value): - value = int(value) - if value < MIN_SCAN_INTERVAL or value > MAX_SCAN_INTERVAL: - raise vol.Invalid("Polling rate must be between 5 and 3600 seconds") - return value def get_data_schema(self): return vol.Schema({ @@ -65,7 +59,10 @@ def get_data_schema(self): vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL, - ): self.validate_scan_interval, + ): vol.All( + vol.Coerce(int), + vol.Range(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), }) @staticmethod @@ -90,7 +87,8 @@ async def async_step_init(self, user_input=None): ), ): vol.All( vol.Coerce(int), - vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + vol.Range(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), ), }), ) + diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index f3e4b49..a41141d 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1,6 +1,6 @@ DOMAIN = "bitaxe" -CONF_SCAN_INTERVAL = "Polling Rate (s)" +CONF_SCAN_INTERVAL = "Polling_Rate_(s)" DEFAULT_SCAN_INTERVAL = 60 # seconds MIN_SCAN_INTERVAL = 30 From 9ee856134f7545f3504b0e752fe3d5cb5662c758 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:28:29 -0600 Subject: [PATCH 06/22] Readding comments that got removed --- custom_components/bitaxe/config_flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index c26128e..25b2b56 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -53,6 +53,7 @@ async def async_step_user(self, user_input=None): ) def get_data_schema(self): + """Return the schema for user input and check for validity.""" return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, @@ -73,6 +74,7 @@ def async_get_options_flow(config_entry): class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): + """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) From 5ead8bf8e804554ccd0984356725e20876dba6ee Mon Sep 17 00:00:00 2001 From: DerMiika Date: Mon, 9 Feb 2026 18:52:35 +0100 Subject: [PATCH 07/22] Fix difficulty sensors when value is string or float --- README.md | 2 +- custom_components/bitaxe/hacs.json | 2 +- custom_components/bitaxe/manifest.json | 2 +- custom_components/bitaxe/sensor.py | 13 ++++++++++--- hacs.json | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9845aec..3dbbe3a 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To set up the integration, follow these steps: 5. Complete the setup. ## Version -This is version (v1.1.0) compatible with HACS! +This is version (v1.1.1) compatible with HACS! ## Screenshots diff --git a/custom_components/bitaxe/hacs.json b/custom_components/bitaxe/hacs.json index 2cd6512..a98f857 100644 --- a/custom_components/bitaxe/hacs.json +++ b/custom_components/bitaxe/hacs.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "render_readme": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.1.0", + "version": "1.1.1", "zip_release": false, "filename": "custom_components/bitaxe", "codeowners": ["@DerMiika"] diff --git a/custom_components/bitaxe/manifest.json b/custom_components/bitaxe/manifest.json index 45d3308..1f8d57b 100644 --- a/custom_components/bitaxe/manifest.json +++ b/custom_components/bitaxe/manifest.json @@ -1,7 +1,7 @@ { "domain": "bitaxe", "name": "Bitaxe Home Assistant Integration", - "version": "v1.1.0", + "version": "v1.1.1", "config_flow": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", "issue_tracker": "https://github.com/DerMiika/Bitaxe-HA-Integration/issues", diff --git a/custom_components/bitaxe/sensor.py b/custom_components/bitaxe/sensor.py index 470926d..c633bb8 100644 --- a/custom_components/bitaxe/sensor.py +++ b/custom_components/bitaxe/sensor.py @@ -44,11 +44,17 @@ async def async_setup_entry(hass, entry, async_add_entities): async_add_entities(sensors, update_before_add=True) -def format_difficulty(value: int) -> str: - """Convert difficulty values into human-readable units.""" +def format_difficulty(value) -> str | None: + """Convert difficulty values into human-readable units (k, M, G, T, P, E).""" if value is None: return None + # AxeOS may return difficulty as string or float + try: + value = float(value) + except (ValueError, TypeError): + return str(value) + units = [ (1e18, "E"), (1e15, "P"), @@ -62,7 +68,8 @@ def format_difficulty(value: int) -> str: if value >= factor: return f"{value / factor:.2f} {suffix}" - return str(value) + return str(int(value)) + class BitAxeSensor(Entity): diff --git a/hacs.json b/hacs.json index 6f34860..c282b53 100644 --- a/hacs.json +++ b/hacs.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "render_readme": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.1.0", + "version": "1.1.1", "zip_release": false, "filename": "custom_components/bitaxe", "codeowners": ["@DerMiika"] From 82936b575dcef114c0ee4fff5205cc683f1bcb77 Mon Sep 17 00:00:00 2001 From: DerMiika Date: Mon, 9 Feb 2026 19:03:00 +0100 Subject: [PATCH 08/22] updated hacs.json --- custom_components/bitaxe/hacs.json | 14 -------------- hacs.json | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 custom_components/bitaxe/hacs.json diff --git a/custom_components/bitaxe/hacs.json b/custom_components/bitaxe/hacs.json deleted file mode 100644 index a98f857..0000000 --- a/custom_components/bitaxe/hacs.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "Bitaxe Home Assistant Integration", - "content_in_root": false, - "country": "DE", - "description": "Integration for monitoring BitAxe miners in Home Assistant", - "domains": ["bitaxe"], - "iot_class": "local_polling", - "render_readme": true, - "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.1.1", - "zip_release": false, - "filename": "custom_components/bitaxe", - "codeowners": ["@DerMiika"] -} diff --git a/hacs.json b/hacs.json index c282b53..85204ea 100644 --- a/hacs.json +++ b/hacs.json @@ -10,6 +10,6 @@ "version": "1.1.1", "zip_release": false, "filename": "custom_components/bitaxe", - "codeowners": ["@DerMiika"] + "codeowners": ["@DerMiika"], "logo": "custom_components/bitaxe/images/bitaxe_icon.png" } From b3fbab4354e460dfc2be4d24b852a2d1088c0e03 Mon Sep 17 00:00:00 2001 From: DerMiika Date: Mon, 9 Feb 2026 20:08:01 +0100 Subject: [PATCH 09/22] Add HACS validation workflow --- .github/workflows/hacs.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/hacs.yml diff --git a/.github/workflows/hacs.yml b/.github/workflows/hacs.yml new file mode 100644 index 0000000..9e555c7 --- /dev/null +++ b/.github/workflows/hacs.yml @@ -0,0 +1,18 @@ +name: HACS Validation + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + hacs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: HACS validation + uses: hacs/action@main + with: + category: integration From fddfad629aed45b3c23bbfd30d531a534a283364 Mon Sep 17 00:00:00 2001 From: DerMiika Date: Tue, 10 Feb 2026 19:00:25 +0100 Subject: [PATCH 10/22] Fix HACS validation errors HACS renamed to "Bitaxe Monitoring" --- README.md | 2 +- custom_components/bitaxe/manifest.json | 2 +- hacs.json | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3dbbe3a..40bd39e 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To set up the integration, follow these steps: 5. Complete the setup. ## Version -This is version (v1.1.1) compatible with HACS! +This is version (v1.1.2) compatible with HACS! ## Screenshots diff --git a/custom_components/bitaxe/manifest.json b/custom_components/bitaxe/manifest.json index 1f8d57b..4d41adf 100644 --- a/custom_components/bitaxe/manifest.json +++ b/custom_components/bitaxe/manifest.json @@ -1,7 +1,7 @@ { "domain": "bitaxe", "name": "Bitaxe Home Assistant Integration", - "version": "v1.1.1", + "version": "v1.1.2", "config_flow": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", "issue_tracker": "https://github.com/DerMiika/Bitaxe-HA-Integration/issues", diff --git a/hacs.json b/hacs.json index 85204ea..a063373 100644 --- a/hacs.json +++ b/hacs.json @@ -1,15 +1,14 @@ { - "name": "Bitaxe Home Assistant Integration", + "name": "Bitaxe Monitoring", "content_in_root": false, "country": "DE", - "description": "Integration for monitoring BitAxe miners in Home Assistant", + "description": "Integration for monitoring Bitaxe miners in Home Assistant", "domains": ["bitaxe"], "iot_class": "local_polling", "render_readme": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.1.1", + "version": "1.1.2", "zip_release": false, "filename": "custom_components/bitaxe", - "codeowners": ["@DerMiika"], - "logo": "custom_components/bitaxe/images/bitaxe_icon.png" + "codeowners": ["DerMiika"] } From 3c56defbcb906b0853dcc1347c30f27ca1dc46af Mon Sep 17 00:00:00 2001 From: Mika <109022499+DerMiika@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:20:13 +0100 Subject: [PATCH 11/22] Update hacs.json deleted codeowners --- hacs.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hacs.json b/hacs.json index a063373..1b528db 100644 --- a/hacs.json +++ b/hacs.json @@ -9,6 +9,5 @@ "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", "version": "1.1.2", "zip_release": false, - "filename": "custom_components/bitaxe", - "codeowners": ["DerMiika"] + "filename": "custom_components/bitaxe" } From 6790dc3a2149e24670b09ac00ed1e30927c53ae7 Mon Sep 17 00:00:00 2001 From: Mika <109022499+DerMiika@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:28:48 +0100 Subject: [PATCH 12/22] Update hacs.json --- hacs.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/hacs.json b/hacs.json index 1b528db..2609475 100644 --- a/hacs.json +++ b/hacs.json @@ -1,8 +1,6 @@ { "name": "Bitaxe Monitoring", "content_in_root": false, - "country": "DE", - "description": "Integration for monitoring Bitaxe miners in Home Assistant", "domains": ["bitaxe"], "iot_class": "local_polling", "render_readme": true, From fe8c3e5aafe284907ce99d2f03612472800e7795 Mon Sep 17 00:00:00 2001 From: Mika <109022499+DerMiika@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:59:29 +0100 Subject: [PATCH 13/22] Update hacs.json --- hacs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/hacs.json b/hacs.json index 2609475..bab2a85 100644 --- a/hacs.json +++ b/hacs.json @@ -4,7 +4,6 @@ "domains": ["bitaxe"], "iot_class": "local_polling", "render_readme": true, - "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", "version": "1.1.2", "zip_release": false, "filename": "custom_components/bitaxe" From a77dd2b80b518822dca54a20ce0db2bc1c9bd456 Mon Sep 17 00:00:00 2001 From: Mika <109022499+DerMiika@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:07:08 +0100 Subject: [PATCH 14/22] Update hacs.json --- hacs.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/hacs.json b/hacs.json index bab2a85..07750ed 100644 --- a/hacs.json +++ b/hacs.json @@ -1,8 +1,6 @@ { "name": "Bitaxe Monitoring", "content_in_root": false, - "domains": ["bitaxe"], - "iot_class": "local_polling", "render_readme": true, "version": "1.1.2", "zip_release": false, From 85d2a32a5beec7fac37d5fbb50976c0bca0f3156 Mon Sep 17 00:00:00 2001 From: Mika <109022499+DerMiika@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:15:50 +0100 Subject: [PATCH 15/22] Update hacs.json --- hacs.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hacs.json b/hacs.json index 07750ed..5e87e2f 100644 --- a/hacs.json +++ b/hacs.json @@ -1,8 +1,7 @@ { "name": "Bitaxe Monitoring", "content_in_root": false, + "filename": "custom_components/bitaxe", "render_readme": true, - "version": "1.1.2", - "zip_release": false, - "filename": "custom_components/bitaxe" + "zip_release": false } From 7699030da86e9323573bf34da9c8a64afb6784d7 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:43:57 -0600 Subject: [PATCH 16/22] Allowing changing the polling rate through GUI --- custom_components/bitaxe/__init__.py | 21 +++++++-- custom_components/bitaxe/config_flow.py | 63 ++++++++++++++++++------- custom_components/bitaxe/const.py | 6 +++ 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/custom_components/bitaxe/__init__.py b/custom_components/bitaxe/__init__.py index 59b55fc..fcc16d3 100644 --- a/custom_components/bitaxe/__init__.py +++ b/custom_components/bitaxe/__init__.py @@ -6,19 +6,21 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.event import async_track_time_interval -DOMAIN = "bitaxe" +from .const import DOMAIN, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL + _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEntry): ip_address = entry.data["ip_address"] device_id = entry.unique_id or ip_address + scan_interval = entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) coordinator = DataUpdateCoordinator( hass, _LOGGER, name=f"BitAxe Sensor Data ({device_id})", update_method=lambda: fetch_bitaxe_data(ip_address), - update_interval=timedelta(seconds=30), + update_interval=timedelta(seconds=scan_interval), ) await coordinator.async_refresh() @@ -27,16 +29,29 @@ async def async_setup_entry(hass: HomeAssistant, entry: config_entries.ConfigEnt hass.data[DOMAIN] = {} hass.data[DOMAIN][device_id] = {"coordinator": coordinator} + # Add option to edit settings after creating instance + entry.async_on_unload( + entry.add_update_listener(_options_update_listener) + ) + await hass.config_entries.async_forward_entry_setups(entry, ["sensor"]) async_track_time_interval( hass, _update_coordinator(coordinator), - timedelta(seconds=30) + timedelta(seconds=scan_interval) ) return True +async def _options_update_listener(hass, entry): + await hass.config_entries.async_reload(entry.entry_id) + +async def async_unload_entry(hass, entry): + unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor"]) + hass.data[DOMAIN].pop(entry.unique_id, None) + return unload_ok + def _update_coordinator(coordinator: DataUpdateCoordinator): async def refresh(now): await coordinator.async_request_refresh() diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 8529271..8b00d5f 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -1,9 +1,12 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback -import ipaddress # Import für IP-Adressenvalidierung +import ipaddress -from .const import DOMAIN +from .const import ( + DOMAIN, MIN_SCAN_INTERVAL, MAX_SCAN_INTERVAL, + CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL +) class BitAxeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 @@ -15,31 +18,37 @@ async def async_step_user(self, user_input=None): if user_input is not None: ip_address = user_input["ip_address"] device_name = user_input["device_name"] + scan_interval = user_input[CONF_SCAN_INTERVAL] - # Validierung der IP-Adresse + # Validate IP address try: ipaddress.ip_address(ip_address) except ValueError: - errors["ip_address"] = "Invalid IP address format." + errors["ip_address"] = "invalid_ip" return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), - errors=errors + errors=errors, ) - # Entry mit IP-Adresse und Gerätenamen erstellen - await self.async_set_unique_id(ip_address) # Einzigartige ID auf IP-Adresse setzen - self._abort_if_unique_id_configured() # Sicherstellen, dass die IP-Adresse nicht doppelt hinzugefügt wird + await self.async_set_unique_id(ip_address) + self._abort_if_unique_id_configured() return self.async_create_entry( title=device_name, - data={"ip_address": ip_address, "device_name": device_name} + data={ + "ip_address": ip_address, + "device_name": device_name, + }, + options={ + CONF_SCAN_INTERVAL: scan_interval, + }, ) return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), - errors=errors + errors=errors, ) def get_data_schema(self): @@ -47,18 +56,38 @@ def get_data_schema(self): return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, + vol.Optional( + CONF_SCAN_INTERVAL, + default=DEFAULT_SCAN_INTERVAL, + ): vol.All( + vol.Coerce(int), + vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), }) @staticmethod @callback def async_get_options_flow(config_entry): - return BitAxeOptionsFlowHandler(config_entry) + return BitAxeOptionsFlowHandler() -class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): - def __init__(self, config_entry): - """Initialize options flow.""" - self.config_entry = config_entry +class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): - """Manage the options.""" - return self.async_show_form(step_id="init") \ No newline at end of file + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema({ + vol.Optional( + CONF_SCAN_INTERVAL, + default=self.config_entry.options.get( + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, + ), + ): vol.All( + vol.Coerce(int), + vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), + }), + ) diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index 4d33878..4e04af2 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1 +1,7 @@ DOMAIN = "bitaxe" + +CONF_SCAN_INTERVAL = "scan_interval (s)" + +DEFAULT_SCAN_INTERVAL = 60 # seconds +MIN_SCAN_INTERVAL = 30 +MAX_SCAN_INTERVAL = 3600 From eab611dd5967bbe8d2a57f221b4cdf58ba430ecb Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:46:54 -0600 Subject: [PATCH 17/22] Bumping version number --- README.md | 2 +- hacs.json | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 40bd39e..700298f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To set up the integration, follow these steps: 5. Complete the setup. ## Version -This is version (v1.1.2) compatible with HACS! +This is version (v1.2.0) compatible with HACS! ## Screenshots diff --git a/hacs.json b/hacs.json index 5e87e2f..411edf6 100644 --- a/hacs.json +++ b/hacs.json @@ -1,7 +1,13 @@ { "name": "Bitaxe Monitoring", "content_in_root": false, - "filename": "custom_components/bitaxe", + "country": "DE", + "description": "Integration for monitoring BitAxe miners in Home Assistant", + "domains": ["bitaxe"], + "iot_class": "local_polling", "render_readme": true, - "zip_release": false + "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", + "version": "1.2.0", + "zip_release": false, + "filename": "custom_components/bitaxe", } From e85b504429d7d93399abca164315e4113cef81a5 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 16:55:34 -0600 Subject: [PATCH 18/22] Readding the original comments --- custom_components/bitaxe/config_flow.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 8b00d5f..868af8a 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -1,7 +1,7 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.core import callback -import ipaddress +import ipaddress # Import für IP-Adressenvalidierung from .const import ( DOMAIN, MIN_SCAN_INTERVAL, MAX_SCAN_INTERVAL, @@ -20,19 +20,20 @@ async def async_step_user(self, user_input=None): device_name = user_input["device_name"] scan_interval = user_input[CONF_SCAN_INTERVAL] - # Validate IP address + # Validierung der IP-Adresse try: ipaddress.ip_address(ip_address) except ValueError: - errors["ip_address"] = "invalid_ip" + errors["ip_address"] = "Invalid IP address format." return self.async_show_form( step_id="user", data_schema=self.get_data_schema(), errors=errors, ) - await self.async_set_unique_id(ip_address) - self._abort_if_unique_id_configured() + # Entry mit IP-Adresse und Gerätenamen erstellen + await self.async_set_unique_id(ip_address) # Einzigartige ID auf IP-Adresse setzen + self._abort_if_unique_id_configured() # Sicherstellen, dass die IP-Adresse nicht doppelt hinzugefügt wird return self.async_create_entry( title=device_name, From d4ed73541651b068f197dc04ac80099866074e37 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:02:28 -0600 Subject: [PATCH 19/22] Trying to make it report an error with invalid input --- custom_components/bitaxe/config_flow.py | 14 ++++++++------ custom_components/bitaxe/const.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 868af8a..956ea34 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -51,21 +51,23 @@ async def async_step_user(self, user_input=None): data_schema=self.get_data_schema(), errors=errors, ) + + def validate_scan_interval(value): + value = int(value) + if value < MIN_SCAN_INTERVAL or value > MAX_SCAN_INTERVAL: + raise vol.Invalid("Polling rate must be between 5 and 3600 seconds") + return value def get_data_schema(self): - """Return the schema for user input.""" return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL, - ): vol.All( - vol.Coerce(int), - vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), - ), + ): self.validate_scan_interval, }) - + @staticmethod @callback def async_get_options_flow(config_entry): diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index 4e04af2..f3e4b49 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1,6 +1,6 @@ DOMAIN = "bitaxe" -CONF_SCAN_INTERVAL = "scan_interval (s)" +CONF_SCAN_INTERVAL = "Polling Rate (s)" DEFAULT_SCAN_INTERVAL = 60 # seconds MIN_SCAN_INTERVAL = 30 From 6f4475b69d702a34ec389c0236ed0bd6cd3385e8 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:23:08 -0600 Subject: [PATCH 20/22] Preventing user inputting invalid polling --- custom_components/bitaxe/config_flow.py | 14 ++++++-------- custom_components/bitaxe/const.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index 956ea34..c26128e 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -51,12 +51,6 @@ async def async_step_user(self, user_input=None): data_schema=self.get_data_schema(), errors=errors, ) - - def validate_scan_interval(value): - value = int(value) - if value < MIN_SCAN_INTERVAL or value > MAX_SCAN_INTERVAL: - raise vol.Invalid("Polling rate must be between 5 and 3600 seconds") - return value def get_data_schema(self): return vol.Schema({ @@ -65,7 +59,10 @@ def get_data_schema(self): vol.Optional( CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL, - ): self.validate_scan_interval, + ): vol.All( + vol.Coerce(int), + vol.Range(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + ), }) @staticmethod @@ -90,7 +87,8 @@ async def async_step_init(self, user_input=None): ), ): vol.All( vol.Coerce(int), - vol.Clamp(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), + vol.Range(min=MIN_SCAN_INTERVAL, max=MAX_SCAN_INTERVAL), ), }), ) + diff --git a/custom_components/bitaxe/const.py b/custom_components/bitaxe/const.py index f3e4b49..a41141d 100644 --- a/custom_components/bitaxe/const.py +++ b/custom_components/bitaxe/const.py @@ -1,6 +1,6 @@ DOMAIN = "bitaxe" -CONF_SCAN_INTERVAL = "Polling Rate (s)" +CONF_SCAN_INTERVAL = "Polling_Rate_(s)" DEFAULT_SCAN_INTERVAL = 60 # seconds MIN_SCAN_INTERVAL = 30 From c1c50fba5955847e2db85b43f9d6c3b7138035b9 Mon Sep 17 00:00:00 2001 From: Anthony Date: Wed, 4 Feb 2026 17:28:29 -0600 Subject: [PATCH 21/22] Readding comments that got removed --- custom_components/bitaxe/config_flow.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/custom_components/bitaxe/config_flow.py b/custom_components/bitaxe/config_flow.py index c26128e..25b2b56 100644 --- a/custom_components/bitaxe/config_flow.py +++ b/custom_components/bitaxe/config_flow.py @@ -53,6 +53,7 @@ async def async_step_user(self, user_input=None): ) def get_data_schema(self): + """Return the schema for user input and check for validity.""" return vol.Schema({ vol.Required("ip_address"): str, vol.Required("device_name"): str, @@ -73,6 +74,7 @@ def async_get_options_flow(config_entry): class BitAxeOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_init(self, user_input=None): + """Manage the options.""" if user_input is not None: return self.async_create_entry(title="", data=user_input) From b91a8907a45db0d95e0dade816c28eed98a8daa5 Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 10 Feb 2026 21:16:45 -0600 Subject: [PATCH 22/22] Bumping version number for configurable polling rate. --- README.md | 2 +- hacs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 700298f..6bc841f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To set up the integration, follow these steps: 5. Complete the setup. ## Version -This is version (v1.2.0) compatible with HACS! +This is version (v1.3.0) compatible with HACS! ## Screenshots diff --git a/hacs.json b/hacs.json index 411edf6..592e9af 100644 --- a/hacs.json +++ b/hacs.json @@ -7,7 +7,7 @@ "iot_class": "local_polling", "render_readme": true, "documentation": "https://github.com/DerMiika/Bitaxe-HA-Integration", - "version": "1.2.0", + "version": "1.3.0", "zip_release": false, "filename": "custom_components/bitaxe", }