├── .github ├── FUNDING.yml └── workflows │ └── hassfest.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── custom_components └── sensorpush │ ├── __init__.py │ ├── __pycache__ │ ├── __init__.cpython-37.pyc │ └── sensor.cpython-37.pyc │ ├── const.py │ ├── manifest.json │ ├── sensor.py │ └── services.yaml ├── docs ├── example.png ├── sensorpush-entities.png └── sensorpush-graph.png ├── hacs.json ├── install └── pyproject.toml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: rsnodgrass 2 | patreon: rsnodgrass 3 | custom: ['https://buymeacoffee.com/DYks67r','https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=WREP29UDAMB6G'] 4 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | name: Validate with hassfest 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - uses: home-assistant/actions/hassfest@master 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | *.log 4 | __pycache__ 5 | install 6 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # pre-commit autoupdate 2 | 3 | fail_fast: true 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v5.0.0 8 | hooks: 9 | - id: trailing-whitespace 10 | - id: end-of-file-fixer 11 | 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.11.10 14 | hooks: 15 | - id: ruff 16 | args: [ --fix ] # run linter 17 | - id: ruff-format # run formatter 18 | 19 | 20 | #### OPTIONAL: for keeping syntax more current 21 | 22 | - repo: https://github.com/asottile/pyupgrade 23 | rev: v3.19.1 24 | hooks: 25 | - id: pyupgrade 26 | args: [--py311-plus] # NOTE: SET TO HOME ASSISTANT VERSION 27 | 28 | - repo: https://github.com/dosisod/refurb 29 | rev: v2.1.0 30 | hooks: 31 | - id: refurb # NOTE: Update pyproject.toml to match HA Python version 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 (2019-09-02) 2 | 3 | Initial release of SensorPush for Home Assistant 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Apache 2.0 License 2 | 3 | Copyright (c) 2019 Ryan Snodgrass 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SensorPush Integration for Home Assistant 2 | 3 | [🥈](https://www.home-assistant.io/docs/quality_scale/) 4 | ![beta_badge](https://img.shields.io/badge/maturity-Beta-yellow.png) 5 | ![release_badge](https://img.shields.io/github/v/release/rsnodgrass/hass-sensorpush.svg) 6 | ![release_date](https://img.shields.io/github/release-date/rsnodgrass/hass-sensorpush.svg) 7 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/hacs/integration) 8 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 9 | 10 | Home Assistant integration for wireless SensorPush temperature and humidity/hygrometer sensors. 11 | 12 | ## NATIVE HOME ASSISTANT SUPPORT 13 | 14 | As of the Home Assistant 2022.08 release [SensorPush](https://rc.home-assistant.io/integrations/sensorpush) sensors are natively supported WITHOUT require a gateway! 15 | 16 | ## Hardware Supported 17 | 18 | | Model | Temp | Humidity | Pressure | Dewpoint | VPD | 19 | | --------------------------------- |:----:|:--------:|:--------:|:--------:|:---:| 20 | | [HT.w](https://amzn.to/3kHq02j) | X | X | | X | X | 21 | | [HTP.xw](https://amzn.to/2MH4gXx) | X | X | X | X | X | 22 | | [HT1](https://amzn.to/3b9GWLB) | X | X | | X | X | 23 | 24 | 1. For constant updates of sensor data without opening the iOS or Android app to synchronize data, the [SensorPush G1 WiFi Gateway](https://amzn.to/30b4ycg) is required to continually stream data from the sensors to SensorPush's cloud service However, SensorPush sensors can also synchronize historical data over Bluetooth when nearby an iOS or Android device with the SensorPush app). 25 | 26 | 2. If you register a new physical sensor with SensorPush, you must restart Home Assistant to discover the new device(s). 27 | 28 | ## Support 29 | 30 | If you have trouble with installation and configuration, visit the [SensorPush Home Assistant community discussion](https://community.home-assistant.io/t/sensorpush-humidity-and-temperature-sensors/105711). 31 | 32 | This integration was developed to cover use cases for my home integration and released as a contribution to the community. Implementing new features beyond what exists is the responsibility of the community to contribute. 33 | 34 | ## Installation 35 | 36 | ### Step 1: Install Custom Components 37 | 38 | Make sure you have installed [Home Assistant Community Store (HACS)](https://github.com/custom-components/hacs), then add the "Integration" repository: *rsnodgrass/hass-sensorpush*. 39 | 40 | #### Versions 41 | 42 | The 'master' branch of this custom component is considered unstable, alpha quality, and not guaranteed to work. 43 | Please make sure to use one of the official release branches when installing using HACS, see [what has changed in each version](https://github.com/rsnodgrass/hass-sensorpush/releases). 44 | 45 | ### Step 2: Enable API Access 46 | 47 | If you've never accessed the [SensorPush Gateway account dashboard](https://beta.sensorpush.com) you must sign in once to agree to Sensorpush terms before this integration can access data in your SensorPush account. (If you've done this any time in the past, you may skip this step.) If you see errors in your log stating `[pysensorpush] Could not authenticate to SensorPush service with and password`, and you've checked to ensure your credentials are correct, you probably need to do this. 48 | 49 | ### Step 3: Configure SensorPush 50 | 51 | Example configuration.yaml entry: 52 | 53 | ```yaml 54 | sensorpush: 55 | username: your@email.com 56 | password: your_password 57 | 58 | sensor: 59 | - platform: sensorpush 60 | ``` 61 | 62 | #### Lovelace 63 | 64 | ![Lovelace Example](https://github.com/rsnodgrass/hass-sensorpush/blob/master/docs/sensorpush-entities.png?raw=true) 65 | 66 | ```yaml 67 | entities: 68 | - entity: sensor.warehouse_humidity 69 | - entity: sensor.warehouse_temperature 70 | show_header_toggle: false 71 | title: SensorPush 72 | type: entities 73 | ``` 74 | 75 | Lovelace gauge example: 76 | 77 | ```yaml 78 | entity: sensor.warehouse_humidity 79 | max: 100 80 | min: 0 81 | name: Office 82 | severity: 83 | green: 45 84 | red: 15 85 | yellow: 25 86 | theme: Backend-selected 87 | type: gauge 88 | ``` 89 | 90 | More complex example using mini-graph-card and color thresholds: 91 | 92 | ![Lovelace Example](https://github.com/rsnodgrass/hass-sensorpush/blob/master/docs/sensorpush-graph.png?raw=true) 93 | 94 | ```yaml 95 | cards: 96 | - color_thresholds: 97 | - color: '#00ff00' 98 | value: 0 99 | - color: '#abf645' 100 | value: 30 101 | - color: '#FFD500' 102 | value: 50 103 | - color: '#ff0000' 104 | value: 60 105 | decimals: 0 106 | entities: 107 | - entity: sensor.warehouse_humidity 108 | name: Humidity 109 | font_size: 75 110 | hours_to_show: 12 111 | line_color: blue 112 | line_width: 8 113 | points_per_hour: 2 114 | show: 115 | fill: true 116 | icon: false 117 | type: 'custom:mini-graph-card' 118 | - color_thresholds: 119 | - color: '#abf645' 120 | value: 0 121 | decimals: 0 122 | entities: 123 | - entity: sensor.warehouse_temperature 124 | name: Temperature 125 | font_size: 75 126 | hours_to_show: 12 127 | line_color: var(--accent-color) 128 | line_width: 8 129 | points_per_hour: 2 130 | show: 131 | icon: false 132 | type: 'custom:mini-graph-card' 133 | type: horizontal-stack 134 | ``` 135 | 136 | #### Tracking Battery 137 | 138 | The battery level of sensors are attributes on each sensor, a separate sensor is not provided. However, if you wish to track battery levels, you can add a template sensor. 139 | 140 | ```yaml 141 | sensor: 142 | - platform: template 143 | sensors: 144 | fridge_sensor_battery_voltage: 145 | friendly_name: 'Fridge SensorPush battery voltage' 146 | unit_of_measurement: 'V' 147 | value_template: '{{ state_attr("sensor.fridge_humidity", "battery_voltage") }}' 148 | ``` 149 | 150 | ## See Also 151 | 152 | * [Community support for Home Assistant SensorPush integration](https://community.home-assistant.io/t/sensorpush-humidity-and-temperature-sensors/105711) 153 | * [pysensorpush](https://github.com/rsnodgrass/pysensorpush) - Python interface to SensorPush cloud API 154 | * [SensorPush](https://sensorpush.com) (official product page) 155 | * [ReviewGeek's review of SensorPush](https://www.reviewgeek.com/3291/sensor-push-review-the-best-smart-hygrometer-and-thermometer-around/) 156 | 157 | ## Out of Scope 158 | 159 | No plans to implement the following at this time. However, community contributions to add these features would be greatly appreciated! 160 | 161 | - [applying calibration adjustments to individual sensors made within the SensorPush app](https://github.com/rsnodgrass/hass-sensorpush/issues/18) 162 | - poll data directly from sensors via Bluetooth (no cloud dependency required) 163 | - supporting multiple SensorPush accounts within a single Home Assistance instance 164 | 165 | #### Alternative Devices 166 | 167 | The following hardware is not supported. These are just recorded here as these devices share the same internal design; were tested and approved on the same day; but likely require custom firmware flash to be able to communicate with SensorPush. 168 | 169 | - [Mitsubishi Kumo Cloud temp/humidity sensor (PAC-USWHS003-TH-1)](https://www.ecomfort.com/Mitsubishi-PAC-USWHS003-TH-1/p81573.html?gclid=CjwKCAiA6vXwBRBKEiwAYE7iSxgq2RjFPeO1yAODQGvRlAAGtobvCq7w2Ay8R7yU9WY4CbK3jVnBxhoCjZ8QAvD_BwE) 170 | - Oasis OH-31 HT Tracker (FCC Grantee [2AL92](https://fccid.io/2AL92-OH31/Test-Report/Test-Report-3428874), ID: 2AL92-OH31) like the SensorPush (FCC Grantee 2AL9W and [2AL9X HT1](https://fccid.io/2AL9X-HT1/Test-Report/Test-Report-3433404)) 171 | - [iBeTag Beacon IB004NPLUSSHT](https://fccid.io/2AB4P-IB004NPLUSSHT/External-Photos/External-photos-3446863) (FCC Grantee [2AB4P](https://fccid.io/2AB4P)) 172 | - [Jaalee Beacon IB004NPLUSSHT](https://fccid.io/2ABRO-IB004NPLUSSHT/Test-Report/Test-Report-3431944) (FCC Grantee 2ABRO) 173 | - [Saalee iB004N-Plus-SHT](https://www.dhgate.com/product/wireless-digital-bluetooth-sensor-beacon/451751881.html?skuid=568611302727536642) 174 | - [AnkhMaway iB004N-Plus-SHT LT](https://ankhmaway.en.alibaba.com/product/60602605562-806002398/Ble_Beacon_With_Temperature_and_Humidity_Sensor_Bluetooth_Programmable_iBeacon.html) / (https://www.beaconzone.co.uk/iB004NPLUSLight) 175 | - [BLW Eddystone iBeacon](https://www.alibaba.com/product-detail/BLE-Eddystone-iBeacon-Temperature-And-Humidity_60611834273.html?spm=a2700.details.maylikeexp.2.12f71911uMO9SV) 176 | - [iBeTag AKMW-iB004N-5](https://www.globalsources.com/si/AS/Shenzhen-AnkhMaway/6008840431707/pdtl/Apple-Certified-iBeacon-NRF51822-Low-Energy-Blueto/1100456449.htm) 177 | -------------------------------------------------------------------------------- /custom_components/sensorpush/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | SensorPush for Home Assistant 3 | See https://github.com/rsnodgrass/hass-sensorpush 4 | """ 5 | 6 | import logging 7 | 8 | import voluptuous as vol 9 | from datetime import timedelta 10 | from requests.exceptions import HTTPError, ConnectTimeout 11 | 12 | from pysensorpush import PySensorPush 13 | from pysensorpush.const import QUERY_SAMPLES_ENDPOINT as qURL 14 | 15 | from homeassistant.core import callback 16 | from homeassistant.helpers import config_validation as cv 17 | from homeassistant.helpers.dispatcher import dispatcher_send, async_dispatcher_connect 18 | from homeassistant.helpers.event import track_time_interval 19 | from homeassistant.helpers.restore_state import RestoreEntity 20 | from homeassistant.const import ( 21 | CONF_USERNAME, 22 | CONF_PASSWORD, 23 | CONF_SCAN_INTERVAL, 24 | ) 25 | 26 | from .const import ( 27 | ATTR_BATTERY_VOLTAGE, 28 | ATTR_OBSERVED_TIME, 29 | ATTR_ATTRIBUTION, 30 | ATTRIBUTION, 31 | MEASURES, 32 | CONF_MAXIMUM_AGE, 33 | ATTR_ALERT_MIN, 34 | ATTR_ALERT_MAX, 35 | ATTR_ALERT_ENABLED, 36 | SENSORPUSH_DOMAIN, 37 | UNIT_SYSTEMS, 38 | ) 39 | 40 | LOG = logging.getLogger(__name__) 41 | 42 | SENSORPUSH_SERVICE = 'sensorpush_service' 43 | SENSORPUSH_SAMPLES = 'sensorpush_samples' 44 | SIGNAL_SENSORPUSH_UPDATED = 'sensorpush_updated' 45 | 46 | NOTIFICATION_ID = 'sensorpush_notification' 47 | NOTIFICATION_TITLE = 'SensorPush' 48 | 49 | DATA_UPDATED = 'sensorpush_data_updated' 50 | 51 | MIN_SCAN_INTERVAL_IN_SECONDS = 30 52 | 53 | CONFIG_SCHEMA = vol.Schema( 54 | { 55 | SENSORPUSH_DOMAIN: vol.Schema( 56 | { 57 | vol.Required(CONF_USERNAME): cv.string, 58 | vol.Required(CONF_PASSWORD): cv.string, 59 | vol.Optional(CONF_SCAN_INTERVAL, default=60): vol.All( 60 | vol.Coerce(int), vol.Range(min=MIN_SCAN_INTERVAL_IN_SECONDS) 61 | ), 62 | vol.Optional(CONF_MAXIMUM_AGE, default=60): cv.positive_int, 63 | } 64 | ) 65 | }, 66 | extra=vol.ALLOW_EXTRA, 67 | ) 68 | 69 | qxParams = { 70 | 'limit': 1, 71 | 'measures': ['temperature', 'humidity', 'vpd', 'dewpoint', 'barometric_pressure'], 72 | } 73 | 74 | 75 | def setup(hass, config): 76 | """Initialize the SensorPush integration""" 77 | hass.data[SENSORPUSH_DOMAIN] = {} 78 | conf = config[SENSORPUSH_DOMAIN] 79 | 80 | username = conf.get(CONF_USERNAME) 81 | password = conf.get(CONF_PASSWORD) 82 | 83 | try: 84 | sensorpush_service = PySensorPush(username, password) 85 | hass.data[SENSORPUSH_SERVICE] = sensorpush_service 86 | 87 | # if not sensorpush_service.is_connected: 88 | # return False 89 | # FIXME: log warning if no sensors found? 90 | 91 | hass.data[SENSORPUSH_SAMPLES] = sensorpush_service.query( 92 | url=qURL, extra_params=qxParams 93 | ) 94 | 95 | # FIXME: trigger automatic setup of sensors 96 | 97 | except (ConnectTimeout, HTTPError) as ex: 98 | LOG.error('Unable to connect to SensorPush: %s', str(ex)) 99 | hass.components.persistent_notification.create( 100 | f'Error: {ex}
You will need to restart Home Assistant after fixing.', 101 | title=NOTIFICATION_TITLE, 102 | notification_id=NOTIFICATION_ID, 103 | ) 104 | return False 105 | 106 | def refresh_sensorpush_data(event_time): 107 | """Call SensorPush service to refresh latest data""" 108 | 109 | # TODO: discovering new devices (and auto-configuring HASS sensors) is not supported 110 | # hass.data[SENSORPUSH_SERVICE].update(update_devices=True) 111 | 112 | # retrieve the latest samples from the SensorPush cloud service 113 | try: 114 | latest_samples = hass.data[SENSORPUSH_SERVICE].query( 115 | url=qURL, extra_params=qxParams 116 | ) 117 | if latest_samples: 118 | hass.data[SENSORPUSH_SAMPLES] = latest_samples 119 | 120 | # notify all listeners (sensor entities) that they may have new data 121 | dispatcher_send(hass, SIGNAL_SENSORPUSH_UPDATED) 122 | else: 123 | LOG.warn('Unable to fetch latest samples from SensorPush cloud') 124 | except Exception as ex: 125 | LOG.warn( 126 | f'Unable to fetch latest samples from SensorPush cloud. Error: {ex}' 127 | ) 128 | 129 | # subscribe for notifications that an update should be triggered 130 | hass.services.register(SENSORPUSH_DOMAIN, 'update', refresh_sensorpush_data) 131 | 132 | # automatically update SensorPush data (samples) on the scan interval 133 | scan_interval = timedelta(seconds=conf.get(CONF_SCAN_INTERVAL)) 134 | track_time_interval(hass, refresh_sensorpush_data, scan_interval) 135 | 136 | return True 137 | 138 | 139 | class SensorPushEntity(RestoreEntity): 140 | """Base Entity class for SensorPush devices""" 141 | 142 | def __init__(self, hass, config, name_suffix, sensor_info, measure): 143 | self.hass = hass 144 | 145 | self._field_name = measure 146 | self._sensor_info = sensor_info 147 | self._max_age = 7 * 1440 148 | self._device_id = sensor_info.get('id') 149 | 150 | self._attrs = {} 151 | self._name = f'{sensor_info.get("name")} {name_suffix}' 152 | 153 | @property 154 | def name(self): 155 | """Return the display name for this sensor""" 156 | return self._name 157 | 158 | @property 159 | def icon(self): 160 | return MEASURES[self._field_name].get('icon') or 'mdi:gauge' 161 | 162 | @property 163 | def device_class(self): 164 | return self._field_name 165 | 166 | @property 167 | def native_unit_of_measurement(self): 168 | return UNIT_SYSTEMS[self._field_name] 169 | 170 | @property 171 | def native_value(self): 172 | return self._state 173 | 174 | @property 175 | def extra_state_attributes(self): 176 | """Return the device state attributes.""" 177 | return self._attrs 178 | 179 | @callback 180 | def _update_callback(self): 181 | samples = self.hass.data[SENSORPUSH_SAMPLES] 182 | sensor_results = samples.get('sensors') 183 | 184 | sensor_data = sensor_results[self._device_id] 185 | latest_result = sensor_data[0] 186 | observed_time = latest_result['observed'] 187 | 188 | # FIXME: check data['observed'] time against config[CONF_MAXIMUM_AGE], ignoring stale entries 189 | # observed = dateutil.parser.isoparse(observed_time) 190 | # delta = datetime.now(datetime.timezone.utc) - datetime.fromtimestamp(observed, datetime.timezone.utc) 191 | # age_in_minutes = delta.total_seconds() / 60 192 | # if age_in_minutes > self._max_age: 193 | # LOG.warning(f"Stale data {self._device_id} detected ({age_in_minutes} min > {self._max_age} min)") 194 | 195 | # FIXME: Note that _sensor_info does not refresh except on restarts. Need to 196 | # add support for this to enable alert changes and voltage to be reflected. 197 | 198 | self._state = float(latest_result.get(self._field_name)) 199 | self._attrs.update( 200 | { 201 | # ATTR_AGE : age_in_minutes, 202 | ATTR_OBSERVED_TIME: observed_time, 203 | ATTR_BATTERY_VOLTAGE: self._sensor_info.get(ATTR_BATTERY_VOLTAGE), 204 | ATTR_ATTRIBUTION: ATTRIBUTION, 205 | } 206 | ) 207 | 208 | if alerts := self._sensor_info.get('alerts').get(self._field_name): 209 | if alert_min := alerts.get('min'): 210 | alert_max = alerts.get('max') 211 | 212 | self._attrs.update({ 213 | ATTR_ALERT_MIN: alert_min, 214 | ATTR_ALERT_MAX: alert_max, 215 | ATTR_ALERT_ENABLED: alerts.get('enabled'), 216 | }) 217 | 218 | # LOG.info(f"{self._state} ... {self._attrs} ... {sensor_data} ... {self._sensor_info}") 219 | 220 | # let Home Assistant know that SensorPush data for this entity has been updated 221 | self.async_schedule_update_ha_state() 222 | 223 | async def async_added_to_hass(self) -> None: 224 | await super().async_added_to_hass() 225 | 226 | # register callback when cached SensorPush data has been updated 227 | async_dispatcher_connect( 228 | self.hass, SIGNAL_SENSORPUSH_UPDATED, self._update_callback 229 | ) 230 | 231 | async_dispatcher_connect( 232 | self.hass, DATA_UPDATED, self._schedule_immediate_update 233 | ) 234 | 235 | @callback 236 | def _schedule_immediate_update(self): 237 | self.async_schedule_update_ha_state(True) 238 | -------------------------------------------------------------------------------- /custom_components/sensorpush/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/custom_components/sensorpush/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/sensorpush/__pycache__/sensor.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/custom_components/sensorpush/__pycache__/sensor.cpython-37.pyc -------------------------------------------------------------------------------- /custom_components/sensorpush/const.py: -------------------------------------------------------------------------------- 1 | SENSORPUSH_DOMAIN = 'sensorpush' 2 | 3 | ATTRIBUTION = 'Data by SensorPush' 4 | ATTR_ATTRIBUTION = 'attribution' 5 | 6 | ATTR_AGE = 'age' 7 | ATTR_BATTERY_VOLTAGE = 'battery_voltage' 8 | ATTR_DEVICE_ID = 'device_id' 9 | ATTR_OBSERVED_TIME = 'observed_time' 10 | ATTR_ALERT_MIN = 'alert_min' 11 | ATTR_ALERT_MAX = 'alert_max' 12 | ATTR_ALERT_ENABLED = 'alert_enabled' 13 | 14 | CONF_MAXIMUM_AGE = ( 15 | 'maximum_age' # maximum age (in minutes) of observations before they expire 16 | ) 17 | 18 | MEASURE_TEMP = 'temperature' 19 | MEASURE_HUMIDITY = 'humidity' 20 | MEASURE_DEWPOINT = 'dewpoint' 21 | MEASURE_BAROMETRIC_PRESSURE = 'barometric_pressure' 22 | MEASURE_VPD = 'vpd' 23 | 24 | ICON = 'icon' 25 | NAME = 'name' 26 | 27 | # FIXME: read this from a *localized* JSON configuration map! 28 | MEASURES = { 29 | MEASURE_TEMP: {NAME: 'Temperature', ICON: 'mdi:thermometer'}, 30 | MEASURE_HUMIDITY: { # Relative Humidity (Rh %) 31 | NAME: 'Humidity', 32 | ICON: 'mdi:water-percent', 33 | }, 34 | MEASURE_DEWPOINT: {NAME: 'Dewpoint', ICON: 'mdi:thermometer'}, 35 | MEASURE_BAROMETRIC_PRESSURE: {NAME: 'Barometric Pressure', ICON: 'mdi:nature'}, 36 | MEASURE_VPD: {NAME: 'Vapor Pressure Deficit', ICON: 'mdi:thermometer'}, 37 | } 38 | 39 | UNIT_SYSTEMS = { 40 | 'system': 'imperial', 41 | MEASURE_BAROMETRIC_PRESSURE: 'inHg', 42 | MEASURE_DEWPOINT: '°F', 43 | MEASURE_HUMIDITY: '%', # 'Rh' 44 | MEASURE_TEMP: '°F', 45 | MEASURE_VPD: 'kPa', 46 | } 47 | -------------------------------------------------------------------------------- /custom_components/sensorpush/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "sensorpush", 3 | "name": "SensorPush", 4 | "documentation": "https://github.com/rsnodgrass/hass-sensorpush/", 5 | "issue_tracker": "https://github.com/rsnodgrass/hass-sensorpush/issues", 6 | "requirements": ["pysensorpush>=0.1.7"], 7 | "version": "0.1.6", 8 | "dependencies": [], 9 | "codeowners": ["@rsnodgrass"], 10 | "iot_class": "cloud_polling", 11 | "config_flow": false 12 | } 13 | -------------------------------------------------------------------------------- /custom_components/sensorpush/sensor.py: -------------------------------------------------------------------------------- 1 | """ 2 | SensorPush Home Assistant sensors 3 | 4 | FUTURE: 5 | - support Celsius and Fahrenheit (based on SensorPush's cloud responses) 6 | """ 7 | 8 | import logging 9 | 10 | from homeassistant.components.sensor import SensorEntity 11 | 12 | from . import SensorPushEntity, SENSORPUSH_SERVICE 13 | 14 | from .const import MEASURES, MEASURE_BAROMETRIC_PRESSURE 15 | 16 | LOG = logging.getLogger(__name__) 17 | 18 | DEPENDENCIES = ['sensorpush'] 19 | 20 | 21 | # pylint: disable=unused-argument 22 | def setup_platform(hass, config, add_entities_callback, discovery_info=None): 23 | """Create all the SensorPush sensors""" 24 | 25 | sensorpush_service = hass.data.get(SENSORPUSH_SERVICE) 26 | if not sensorpush_service: 27 | LOG.error( 28 | 'NOT setting up SensorPush -- SENSORPUSH_SERVICE has not been initialized' 29 | ) 30 | return 31 | 32 | hass_sensors = [] 33 | for sensor_info in sensorpush_service.sensors.values(): 34 | LOG.info(f'SensorInfo: {sensor_info} -- {type(sensor_info)}') 35 | # supported_measurements = sensor_info["calibration"].keys() 36 | 37 | if sensor_info.get('active') == 'False': # FIXME 38 | LOG.warn(f"Ignoring inactive SensorPush sensor '{sensor_info.get('name')}") 39 | continue 40 | 41 | LOG.info(f'Instantiating SensorPush sensors: {sensor_info}') 42 | for measure in MEASURES: 43 | # only include measurements supported by this sensor 44 | if ( 45 | sensor_info.get('type') == 'HTP.xw' 46 | or measure != MEASURE_BAROMETRIC_PRESSURE 47 | ): 48 | sensor = SensorPushMeasurement(hass, config, sensor_info, measure) 49 | hass_sensors.append(sensor) 50 | 51 | # execute callback to add new entities 52 | add_entities_callback(hass_sensors, True) 53 | 54 | 55 | # pylint: disable=too-many-instance-attributes 56 | class SensorPushMeasurement(SensorPushEntity, SensorEntity): 57 | """Measurement sensor for a SensorPush device""" 58 | 59 | def __init__(self, hass, config, sensor_info, measure): 60 | self._name = MEASURES[measure]['name'] 61 | self._state = None 62 | super().__init__(hass, config, self._name, sensor_info, measure) 63 | 64 | @property 65 | def unique_id(self): 66 | return f'sensorpush_{self._field_name}_{self._device_id}' 67 | -------------------------------------------------------------------------------- /custom_components/sensorpush/services.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/custom_components/sensorpush/services.yaml -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/docs/example.png -------------------------------------------------------------------------------- /docs/sensorpush-entities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/docs/sensorpush-entities.png -------------------------------------------------------------------------------- /docs/sensorpush-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsnodgrass/hass-sensorpush/0da2bcfaf615bf898003812d868c72fd03242412/docs/sensorpush-graph.png -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SensorPush", 3 | "domains": ["sensor"], 4 | "render_readme": true 5 | } 6 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | scp -r custom_components/* ha-cabin:/config/custom_components/ 2 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.isort] 2 | force_to_top = [ "logging" ] 3 | balanced_wrapping = true 4 | 5 | [tool.ruff] 6 | line-length = 88 7 | indent-width = 4 8 | lint.ignore = [ 9 | "F403", 10 | "F405" # * imports 11 | ] 12 | 13 | [tool.ruff.format] 14 | quote-style = "single" # Use a single quote instead of double 15 | 16 | # import sorting settings (replaces isort) 17 | [tool.ruff.lint.isort] 18 | force-single-line = false 19 | force-sort-within-sections = true 20 | known-first-party = ["custom_components"] 21 | section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] 22 | combine-as-imports = true 23 | force-to-top = ["logging"] 24 | 25 | [tool.pycln] 26 | all = true 27 | 28 | [tool.refurb] 29 | python_version = "3.13" 30 | quiet = true 31 | ignore = [ "FURB184" ] 32 | --------------------------------------------------------------------------------