diff --git a/Version b/Version index 2fe7c2a..cba7452 100644 --- a/Version +++ b/Version @@ -30,4 +30,5 @@ Version: 0.3.0 20230426 - Rename to Cryptoinfo Advanced Version: 0.3.1 20230426 - Fixes for HACS/HASS requirements Version: 0.3.2 20230507 - Add total unknown hash control sensor to chain control, mempool total fee calculated. Version: 0.3.3 20230521 - Fix a few errors during updates. -Version: 0.3.4 20240110 - Add mempool.space fees sensors. \ No newline at end of file +Version: 0.3.4 20240110 - Add mempool.space fees sensors. +Version: 0.3.5 20240110 - Add mempool.space Next Block sensors. \ No newline at end of file diff --git a/custom_components/cryptoinfo_advanced/__init__.py b/custom_components/cryptoinfo_advanced/__init__.py index 334b899..a8d4557 100644 --- a/custom_components/cryptoinfo_advanced/__init__.py +++ b/custom_components/cryptoinfo_advanced/__init__.py @@ -1 +1 @@ -__version__ = "0.3.4" +__version__ = "0.3.5" diff --git a/custom_components/cryptoinfo_advanced/const/const.py b/custom_components/cryptoinfo_advanced/const/const.py index 571a21f..d94c9b3 100644 --- a/custom_components/cryptoinfo_advanced/const/const.py +++ b/custom_components/cryptoinfo_advanced/const/const.py @@ -76,6 +76,15 @@ ATTR_MEMPOOL_FEES_60MIN = "mempool_fees_60min" ATTR_MEMPOOL_FEES_ECO = "mempool_fees_eco" ATTR_MEMPOOL_FEES_MINIMUM = "mempool_fees_minimum" +ATTR_MEMPOOL_NEXT_BLOCK_SIZE = "mempool_next_block_size" +ATTR_MEMPOOL_NEXT_BLOCK_SIZE_CALC = "mempool_next_block_size_calc" +ATTR_MEMPOOL_NEXT_BLOCK_TX_COUNT = "mempool_next_block_tx_count" +ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE = "mempool_next_block_total_fee" +ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE_CALC = "mempool_next_block_total_fee_calc" +ATTR_MEMPOOL_NEXT_BLOCK_MEDIAN_FEE = "mempool_next_block_median_fee" +ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MIN = "mempool_next_block_fee_range_min" +ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MAX = "mempool_next_block_fee_range_max" +ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_COMBINED = "mempool_next_block_fee_range_combined" PROPERTY_POOL_CONTROL_REMAINING = "remaining_percentage" @@ -99,6 +108,7 @@ API_ENDPOINT_NOMP_POOL_STATS = "https://{0}/api/stats" API_ENDPOINT_MEMPOOL_STATS = "{0}mempool" API_ENDPOINT_MEMPOOL_FEES = "{0}v1/fees/recommended" +API_ENDPOINT_MEMPOOL_NEXT_BLOCKS = "{0}v1/fees/mempool-blocks" DAY_SECONDS = 60 * 60 * 24 diff --git a/custom_components/cryptoinfo_advanced/crypto_sensor.py b/custom_components/cryptoinfo_advanced/crypto_sensor.py index 470a311..2eb5f7d 100644 --- a/custom_components/cryptoinfo_advanced/crypto_sensor.py +++ b/custom_components/cryptoinfo_advanced/crypto_sensor.py @@ -59,6 +59,15 @@ ATTR_MEMPOOL_FEES_60MIN, ATTR_MEMPOOL_FEES_ECO, ATTR_MEMPOOL_FEES_MINIMUM, + ATTR_MEMPOOL_NEXT_BLOCK_SIZE, + ATTR_MEMPOOL_NEXT_BLOCK_SIZE_CALC, + ATTR_MEMPOOL_NEXT_BLOCK_TX_COUNT, + ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE, + ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE_CALC, + ATTR_MEMPOOL_NEXT_BLOCK_MEDIAN_FEE, + ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_COMBINED, + ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MIN, + ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MAX, ATTR_MEMPOOL_TX_COUNT, ATTR_MEMPOOL_TOTAL_FEE, ATTR_MEMPOOL_TOTAL_FEE_CALC, @@ -76,6 +85,7 @@ API_ENDPOINT_CHAIN_BLOCK_TIME, API_ENDPOINT_NOMP_POOL_STATS, API_ENDPOINT_MEMPOOL_FEES, + API_ENDPOINT_MEMPOOL_NEXT_BLOCKS, API_ENDPOINT_MEMPOOL_STATS, CONF_DIFF_MULTIPLIER, CONF_BLOCK_TIME_MINUTES, @@ -199,6 +209,12 @@ def __init__( self._mempool_fees_60min = None self._mempool_fees_eco = None self._mempool_fees_minimum = None + self._mempool_next_block_size = None + self._mempool_next_block_tx_count = None + self._mempool_next_block_total_fee = None + self._mempool_next_block_median_fee = None + self._mempool_next_block_fee_range_min = None + self._mempool_next_block_fee_range_max = None @property def is_child_sensor(self): @@ -372,6 +388,24 @@ def mempool_total_fee_calc(self, unit_of_measurement): return round(float(self._mempool_total_fee) / currency_to_multiplier(unit_of_measurement), 4) + def mempool_next_block_size_calc(self, unit_of_measurement): + if self._mempool_next_block_size is None: + return None + + return round(float(self._mempool_next_block_size) / unit_to_multiplier(unit_of_measurement), 4) + + def mempool_next_block_total_fee_calc(self, unit_of_measurement): + if self._mempool_next_block_total_fee is None: + return None + + return round(float(self._mempool_next_block_total_fee) / currency_to_multiplier(unit_of_measurement), 4) + + def mempool_next_block_fee_range_combined(self, unit_of_measurement): + if self._mempool_next_block_fee_range_min is None or self._mempool_next_block_fee_range_max is None: + return None + + return f"{self._mempool_next_block_fee_range_min:.0f} - {self._mempool_next_block_fee_range_max:.0f}" + @property def all_time_high_distance(self): if self._all_time_high is None or self.state is None: @@ -446,6 +480,14 @@ def get_extra_state_attrs(self, full_attr_force=False): output_attrs[ATTR_MEMPOOL_FEES_ECO] = self._mempool_fees_eco output_attrs[ATTR_MEMPOOL_FEES_MINIMUM] = self._mempool_fees_minimum + if full_attr_force or self._fetch_type == CryptoInfoAdvDataFetchType.MEMPOOL_NEXT_BLOCK: + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_SIZE] = self._mempool_next_block_size + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_TX_COUNT] = self._mempool_next_block_tx_count + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE] = self._mempool_next_block_total_fee + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_MEDIAN_FEE] = self._mempool_next_block_median_fee + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MIN] = self._mempool_next_block_fee_range_min + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_MAX] = self._mempool_next_block_fee_range_max + return output_attrs @property @@ -514,6 +556,23 @@ def get_extra_sensor_attrs(self, full_attr_force=False, child_sensor=None): if child_sensor is None or child_sensor.attribute_key == ATTR_MEMPOOL_AVERAGE_FEE_PER_TX: output_attrs[ATTR_MEMPOOL_AVERAGE_FEE_PER_TX] = self.mempool_average_fee_per_tx + if full_attr_force or self._fetch_type == CryptoInfoAdvDataFetchType.MEMPOOL_NEXT_BLOCK: + + if child_sensor is None or child_sensor.attribute_key == ATTR_MEMPOOL_NEXT_BLOCK_SIZE_CALC: + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_SIZE_CALC] = self.mempool_next_block_size_calc( + child_sensor.unit_of_measurement if child_sensor is not None else None + ) + + if child_sensor is None or child_sensor.attribute_key == ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE_CALC: + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_TOTAL_FEE_CALC] = self.mempool_next_block_total_fee_calc( + child_sensor.unit_of_measurement if child_sensor is not None else None + ) + + if child_sensor is None or child_sensor.attribute_key == ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_COMBINED: + output_attrs[ATTR_MEMPOOL_NEXT_BLOCK_FEE_RANGE_COMBINED] = self.mempool_next_block_fee_range_combined( + child_sensor.unit_of_measurement if child_sensor is not None else None + ) + if full_attr_force or self._fetch_type == CryptoInfoAdvDataFetchType.PRICE_MAIN: if child_sensor is None or child_sensor.attribute_key == ATTR_ALL_TIME_HIGH_DISTANCE: @@ -757,6 +816,18 @@ def _extract_data_mempool_fees_full(self, json_data): def _extract_data_mempool_fees_primary(self, api_data): return int(api_data["fastestFee"]) + def _extract_data_mempool_next_block_full(self, json_data): + return json_data + + def _extract_data_mempool_next_block_primary(self, api_data): + return int(api_data[0]["nTx"]) + + def _extract_data_mempool_next_block_special(self, json_data): + if isinstance(json_data, list) and len(json_data) >= 2: + return list([json_data[0]["feeRange"][0], json_data[0]["feeRange"][-1]]) + + return None + async def _fetch_price_data_main(self, api_data=None): if not self._fetch_type == CryptoInfoAdvDataFetchType.PRICE_MAIN: raise ValueError() @@ -1023,6 +1094,37 @@ async def _fetch_mempool_fees(self, api_data=None): return self.data + async def _fetch_mempool_next_block(self, api_data=None): + self.check_valid_config() + + mempool_data, api_data = await self._async_api_fetch( + api_data, + API_ENDPOINT_MEMPOOL_NEXT_BLOCKS.format(API_BASE_URL_MEMPOOLSPACE), + self._extract_data_mempool_next_block_full, + self._extract_data_mempool_next_block_primary + ) + + if mempool_data is not None: + fee_ranges = self._extract_data_mempool_next_block_special(api_data) + + if fee_ranges is None: + raise ValueError() + + self._update_all_properties( + state=int(mempool_data), + mempool_next_block_size=int(api_data[0]["blockSize"]), + mempool_next_block_tx_count=int(api_data[0]["nTx"]), + mempool_next_block_total_fee=int(api_data[0]["totalFees"]), + mempool_next_block_median_fee=int(api_data[0]["medianFee"]), + mempool_next_block_fee_range_min=int(fee_ranges[0]), + mempool_next_block_fee_range_max=int(fee_ranges[1]), + ) + + else: + raise ValueError() + + return self.data + def _render_fetch_args(self): if self._fetch_args is None: return None @@ -1106,6 +1208,12 @@ def _update_all_properties( mempool_fees_60min=None, mempool_fees_eco=None, mempool_fees_minimum=None, + mempool_next_block_size=None, + mempool_next_block_tx_count=None, + mempool_next_block_total_fee=None, + mempool_next_block_median_fee=None, + mempool_next_block_fee_range_min=None, + mempool_next_block_fee_range_max=None, available=True, ): self._state = state @@ -1139,6 +1247,12 @@ def _update_all_properties( self._mempool_fees_60min = mempool_fees_60min self._mempool_fees_eco = mempool_fees_eco self._mempool_fees_minimum = mempool_fees_minimum + self._mempool_next_block_size = mempool_next_block_size + self._mempool_next_block_tx_count = mempool_next_block_tx_count + self._mempool_next_block_total_fee = mempool_next_block_total_fee + self._mempool_next_block_median_fee = mempool_next_block_median_fee + self._mempool_next_block_fee_range_min = mempool_next_block_fee_range_min + self._mempool_next_block_fee_range_max = mempool_next_block_fee_range_max self._attr_available = available self._update_child_sensors() @@ -1220,6 +1334,9 @@ async def _async_update(self): elif self._fetch_type == CryptoInfoAdvDataFetchType.MEMPOOL_FEES: api_data = await self._fetch_mempool_fees(api_data) + elif self._fetch_type == CryptoInfoAdvDataFetchType.MEMPOOL_NEXT_BLOCK: + api_data = await self._fetch_mempool_next_block(api_data) + else: api_data = await self._fetch_price_data_main(api_data) diff --git a/custom_components/cryptoinfo_advanced/manager.py b/custom_components/cryptoinfo_advanced/manager.py index 8bd7a74..c200bb1 100644 --- a/custom_components/cryptoinfo_advanced/manager.py +++ b/custom_components/cryptoinfo_advanced/manager.py @@ -78,6 +78,7 @@ class CryptoInfoAdvDataFetchType: NOMP_POOL_STATS = CryptoInfoAdvFetchProp("nomp_pool_stats") MEMPOOL_STATS = CryptoInfoAdvFetchProp("mempool_stats") MEMPOOL_FEES = CryptoInfoAdvFetchProp("mempool_fees") + MEMPOOL_NEXT_BLOCK = CryptoInfoAdvFetchProp("mempool_next_block") class CryptoInfoAdvEntityManager: @@ -108,6 +109,7 @@ def fetch_types(self): CryptoInfoAdvDataFetchType.NOMP_POOL_STATS, CryptoInfoAdvDataFetchType.MEMPOOL_STATS, CryptoInfoAdvDataFetchType.MEMPOOL_FEES, + CryptoInfoAdvDataFetchType.MEMPOOL_NEXT_BLOCK, ] @property @@ -165,6 +167,7 @@ def fetch_mempool_types(self): return [ CryptoInfoAdvDataFetchType.MEMPOOL_STATS, CryptoInfoAdvDataFetchType.MEMPOOL_FEES, + CryptoInfoAdvDataFetchType.MEMPOOL_NEXT_BLOCK, ] def get_extra_sensor_fetch_type_from_str(self, parent_sensor, attribute_key): diff --git a/custom_components/cryptoinfo_advanced/manifest.json b/custom_components/cryptoinfo_advanced/manifest.json index 5ded35c..152b97e 100644 --- a/custom_components/cryptoinfo_advanced/manifest.json +++ b/custom_components/cryptoinfo_advanced/manifest.json @@ -10,5 +10,5 @@ "iot_class": "cloud_polling", "issue_tracker": "https://github.com/TheHolyRoger/hass-cryptoinfo/issues", "requirements": [], - "version": "0.3.4" + "version": "0.3.5" } \ No newline at end of file diff --git a/custom_components/cryptoinfo_advanced/utils.py b/custom_components/cryptoinfo_advanced/utils.py index 3c20a28..1a29194 100644 --- a/custom_components/cryptoinfo_advanced/utils.py +++ b/custom_components/cryptoinfo_advanced/utils.py @@ -26,7 +26,7 @@ def unit_to_multiplier(unit_of_measurement): def currency_to_multiplier(currency): coin = str(currency).lower() if currency is not None else "" - if coin in ["btc", "bitcoin"]: + if coin in ["btc", "bitcoin", "bitcoins", "₿"]: return 1e8 else: return 1 diff --git a/example/configuration.yaml b/example/configuration.yaml index 926011c..e3ccce0 100644 --- a/example/configuration.yaml +++ b/example/configuration.yaml @@ -326,6 +326,26 @@ sensor: id: "BTC Mempool Fees - Minimum" unit_of_measurement: "sats" + - platform: cryptoinfo_advanced + id: "BTC Next Block - Total TX" + cryptocurrency_name: "btc" + unit_of_measurement: "tx" + update_frequency: 1 + api_mode: "mempool_next_block" + extra_sensors: + - property: mempool_next_block_size_calc + id: "BTC Next Block - Size" + unit_of_measurement: "MB" + - property: mempool_next_block_total_fee_calc + id: "BTC Next Block - Total Fees" + unit_of_measurement: "₿" + - property: mempool_next_block_median_fee + id: "BTC Next Block - Median Fee" + unit_of_measurement: "sat/vB" + - property: mempool_next_block_fee_range_combined + id: "BTC Next Block - Fee Range" + unit_of_measurement: "sat/vB" + # ROGER NOMP Pool stats - API: NOMP (Any NOMP based pool)