add 24 hr max scales, improve readmes, reorganize pred card layout

This commit is contained in:
Cyberes 2024-08-18 15:38:47 -06:00
parent 85636814ed
commit 57fbbe4627
9 changed files with 262 additions and 74 deletions

View File

@ -1,58 +1,5 @@
# ha-noaa-space-weather-sensor # ha-noaa-space-weather
*MQTT sensor to send NOAA space weather data to Home Assistant.* *NOAA space weather data in Home Assistant.*
## Install This project consists of an external MQTT server, a custom component, and custom dashboard cards.
1. Create an account at <https://urs.earthdata.nasa.gov>
2. `pip install -r requirements.txt`
3. `sudo apt install p7zip-full`
### Google Chrome
If you don't have Google Chrome installed (used to log into the NASA site), here's how to install it.
```shell
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install ./google-chrome-stable_current_amd64.deb
```
## Run
The lat/lon range is used to pick the region of the planet for generating statistics, for example your home state. To
```shell
LAT_RANGE_MIN=<lower range for lat bounding box> \
LAT_RANGE_MAX=<upper range for lat bounding box> \
LON_RANGE_MIN=<lower range for lon bounding box> \
LON_RANGE_MAX=<upper range for lon bounding box> \
CDDIS_USERNAME=<username> CDDIS_PASSWORD=<password> \
MQTT_BROKER_HOST="<Home Assistant IP>" MQTT_BROKER_PORT=1883 MQTT_USERNAME="user" MQTT_PASSWORD="<password>" \
python3 main.py
```
An example systemd service file is provided.
### Home Assistant MQTT Config
```yaml
- state_topic: "space-weather/vtec"
name: "VTEC"
unit_of_measurement: "(10^16 el) / m^2"
state_class: measurement
unique_id: space_weather_vtec
```
## Data
### VTEC
<https://www.spaceweather.gov/products/us-total-electron-content>
Unit: `(10^16 el) / m^2`
VTEC, or Vertical TEC, is a specific type of TEC measurement that is taken along a path extending
vertically from the Earth's surface to the edge of the atmosphere. So essentially, VTEC is a subset of TEC, with the
difference lying in the specific path along which the measurement is taken.
Updated hourly.

View File

@ -0,0 +1,4 @@
## Install
1. Copy `space_weather` to `config/custom_components`
2. Restart Home Assistant

View File

@ -15,9 +15,12 @@ SCAN_INTERVAL = timedelta(minutes=30)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
session = async_get_clientsession(hass) session = async_get_clientsession(hass)
async_add_entities([ async_add_entities([
SpaceWeatherScaleSensor(session, CONF_URL, "R"), SpaceWeatherScaleSensor(session, CONF_URL, "R", '0', ''),
SpaceWeatherScaleSensor(session, CONF_URL, "S"), SpaceWeatherScaleSensor(session, CONF_URL, "S", '0', ''),
SpaceWeatherScaleSensor(session, CONF_URL, "G"), SpaceWeatherScaleSensor(session, CONF_URL, "G", '0', ''),
SpaceWeatherScaleSensor(session, CONF_URL, "R", '-1', '_24hr'),
SpaceWeatherScaleSensor(session, CONF_URL, "S", '-1', '_24hr'),
SpaceWeatherScaleSensor(session, CONF_URL, "G", '-1', '_24hr'),
SpaceWeatherPredictionSensor(session, CONF_URL, "R", "MinorProb", "pred_r_minor"), SpaceWeatherPredictionSensor(session, CONF_URL, "R", "MinorProb", "pred_r_minor"),
SpaceWeatherPredictionSensor(session, CONF_URL, "R", "MajorProb", "pred_r_major"), SpaceWeatherPredictionSensor(session, CONF_URL, "R", "MajorProb", "pred_r_major"),
SpaceWeatherPredictionSensor(session, CONF_URL, "S", "Scale", "pred_s_scale"), SpaceWeatherPredictionSensor(session, CONF_URL, "S", "Scale", "pred_s_scale"),
@ -28,13 +31,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
class SpaceWeatherScaleSensor(Entity): class SpaceWeatherScaleSensor(Entity):
def __init__(self, session, url, scale_key): def __init__(self, session, url, scale_key, data_selector, trailing):
self._session = session self._session = session
self._url = url self._url = url
self._scale_key = scale_key self._scale_key = scale_key
self._name = f'Space Weather Scale {scale_key}' self._name = f'Space Weather Scale {scale_key} {trailing}'
self._state = None self._state = None
self._data = None self._data = None
self._data_selector = data_selector
self._trailing = trailing
@property @property
def name(self): def name(self):
@ -42,7 +47,7 @@ class SpaceWeatherScaleSensor(Entity):
@property @property
def unique_id(self): def unique_id(self):
return f"space_weather_scale_{self._scale_key.lower()}" return f"space_weather_scale_{self._scale_key.lower()}{self._trailing.replace('_', '')}"
@property @property
def state(self): def state(self):
@ -63,7 +68,7 @@ class SpaceWeatherScaleSensor(Entity):
async with self._session.get(self._url) as response: async with self._session.get(self._url) as response:
if response.status == 200: if response.status == 200:
data = await response.json() data = await response.json()
self._data = data["-1"] self._data = data[self._data_selector]
self._state = f'{self._scale_key}{self._data[self._scale_key]["Scale"]}' self._state = f'{self._scale_key}{self._data[self._scale_key]["Scale"]}'
else: else:
_LOGGER.error(f"Error fetching data from {self._url}") _LOGGER.error(f"Error fetching data from {self._url}")
@ -178,7 +183,7 @@ class SpaceWeatherDateStampSensor(Entity):
tomorrow_data = v tomorrow_data = v
assert len(tomorrow_data.keys()) is not None assert len(tomorrow_data.keys()) is not None
self._data = tomorrow_data self._data = tomorrow_data
self._state = datetime.strptime(f'{self._data["DateStamp"]} {self._data["TimeStamp"]}', "%Y-%m-%d %H:%M:%S").strftime('%m-%d-%Y %H:%M') self._state = datetime.strptime(f'{self._data["DateStamp"]}', "%Y-%m-%d").strftime('%m-%d-%Y')
else: else:
_LOGGER.error(f"Error fetching data from {self._url}") _LOGGER.error(f"Error fetching data from {self._url}")
except aiohttp.ClientError as err: except aiohttp.ClientError as err:

17
dashboard/README.md Normal file
View File

@ -0,0 +1,17 @@
## Install
1. Copy files from `www` to `config/www`
2. Dashboard > Edit > 3 button menu > Manage resources
3. Enter these 3 resources:
```
/local/space-weather-24hr-max-card.js?v=1
/local/space-weather-card.js?v=1
/local/space-weather-pred-card.js?v=1
```
## Use
To add these custom cards, create a card of the "Manual" type.
```
type: space-weather-card-current
type: space-weather-prediction-card-1day
type: space-weather-card-24hr-max
```

View File

@ -0,0 +1,157 @@
class SpaceWeather24hrMaxCard extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
setConfig(config) {
this._config = config;
}
set hass(hass) {
this._hass = hass;
this.render();
}
render() {
if (!this.shadowRoot) return;
this.shadowRoot.innerHTML = `
<style>
.scale-container {
display: flex;
justify-content: space-around;
}
.scale-item {
text-align: center;
}
.scale-label {
font-weight: bold;
margin-bottom: 8px;
}
.scale-value {
font-size: 24px;
font-weight: bold;
margin-bottom: 4px;
padding: 8px;
border-radius: 4px;
}
.scale-text {
font-size: 14px;
}
.noaa_scale_bg_5 {
background-color: #C80000;
}
.noaa_scale_bg_4 {
background-color: #FF0000;
}
.noaa_scale_bg_3 {
background-color: #FF9600;
}
.noaa_scale_bg_2 {
background-color: #FFC800;
}
.noaa_scale_bg_1 {
background-color: #F6EB14;
}
.noaa_scale_bg_0 {
background-color: #92D050;
}
a {
text-decoration: none;
color: inherit;
}
.card-header {
font-size: 20px;
font-weight: bold;
padding: 16px;
margin-bottom: 16px;
text-align: center;
}
.scale-item {
cursor: pointer;
}
</style>
<ha-card>
<div class="card-header">
<a href="https://www.spaceweather.gov/noaa-scales-explanation" target="_blank">Space Weather 24-Hour Maximums</a>
</div>
<div class="card-content">
<div class="scale-container">
<div class="scale-item" data-entity-id="sensor.space_weather_scale_r_24hr">
<div class="scale-value noaa_scale_bg_${this._getNumericState('sensor.space_weather_scale_r_24hr')}">
${this._getStateValue('sensor.space_weather_scale_r_24hr')}
</div>
<div class="scale-text">
${this._getStateAttribute('sensor.space_weather_scale_r_24hr', 'text')}
</div>
</div>
<div class="scale-item" data-entity-id="sensor.space_weather_scale_s_24hr">
<div class="scale-value noaa_scale_bg_${this._getNumericState('sensor.space_weather_scale_s_24hr')}">
${this._getStateValue('sensor.space_weather_scale_s_24hr')}
</div>
<div class="scale-text">
${this._getStateAttribute('sensor.space_weather_scale_s_24hr', 'text')}
</div>
</div>
<div class="scale-item" data-entity-id="sensor.space_weather_scale_g_24hr">
<div class="scale-value noaa_scale_bg_${this._getNumericState('sensor.space_weather_scale_g_24hr')}">
${this._getStateValue('sensor.space_weather_scale_g_24hr')}
</div>
<div class="scale-text">
${this._getStateAttribute('sensor.space_weather_scale_g_24hr', 'text')}
</div>
</div>
</div>
</div>
</ha-card>
`;
this._attachClickListeners();
}
_getStateValue(entityId) {
const state = this._hass.states[entityId];
return state ? state.state : '';
}
_getStateAttribute(entityId, attribute) {
const state = this._hass.states[entityId];
return state ? state.attributes[attribute] || '' : '';
}
_getNumericState(entityId) {
const stateValue = this._getStateValue(entityId);
return stateValue.substring(1);
}
_attachClickListeners() {
const scaleItems = this.shadowRoot.querySelectorAll('.scale-item');
scaleItems.forEach(item => {
item.addEventListener('click', () => {
const entityId = item.dataset.entityId;
this._handleClick(entityId);
});
});
}
_handleClick(entityId) {
const event = new Event('hass-more-info', {composed: true});
event.detail = {entityId};
this.dispatchEvent(event);
}
getCardSize() {
return 3;
}
}
customElements.define('space-weather-card-24hr-max', SpaceWeather24hrMaxCard);

View File

@ -157,4 +157,4 @@ class SpaceWeatherCard extends HTMLElement {
} }
} }
customElements.define('space-weather-card', SpaceWeatherCard); customElements.define('space-weather-card-current', SpaceWeatherCard);

View File

@ -21,14 +21,16 @@ class SpaceWeatherPredictionCard extends HTMLElement {
/* TODO: unify this with the other card */ /* TODO: unify this with the other card */
.prediction-container { .prediction-container {
display: flex; display: grid;
flex-direction: column; grid-template-columns: repeat(4, 1fr);
gap: 16px;
align-items: center; align-items: center;
justify-items: center;
} }
.prediction-item { .prediction-item {
text-align: center; text-align: center;
margin-bottom: 16px; /*margin-bottom: 16px;*/
cursor: pointer; cursor: pointer;
} }
@ -96,7 +98,7 @@ class SpaceWeatherPredictionCard extends HTMLElement {
<ha-card> <ha-card>
<div class="card-header">Space Weather Predictions</div> <div class="card-header">Space Weather Predictions</div>
<div class="card-subheader"> <div class="card-subheader">
${this._getStateValue('sensor.space_weather_prediction_date_stamp')} For ${this._getStateValue('sensor.space_weather_prediction_date_stamp')}
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="prediction-container"> <div class="prediction-container">
@ -179,4 +181,4 @@ class SpaceWeatherPredictionCard extends HTMLElement {
} }
} }
customElements.define('space-weather-prediction-card', SpaceWeatherPredictionCard); customElements.define('space-weather-prediction-card-1day', SpaceWeatherPredictionCard);

56
feeder-mqtt/README.md Normal file
View File

@ -0,0 +1,56 @@
This is an MQTT sensor to send NOAA space weather data to Home Assistant.
## Install
1. Create an account at <https://urs.earthdata.nasa.gov>
2. `pip install -r requirements.txt`
3. `sudo apt install p7zip-full`
### Google Chrome
If you don't have Google Chrome installed (used to log into the NASA site), here's how to install it.
```shell
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install ./google-chrome-stable_current_amd64.deb
```
## Run
The lat/lon range is used to pick the region of the planet for generating statistics, for example your home state. To
```shell
LAT_RANGE_MIN=<lower range for lat bounding box> \
LAT_RANGE_MAX=<upper range for lat bounding box> \
LON_RANGE_MIN=<lower range for lon bounding box> \
LON_RANGE_MAX=<upper range for lon bounding box> \
CDDIS_USERNAME=<username> CDDIS_PASSWORD=<password> \
MQTT_BROKER_HOST="<Home Assistant IP>" MQTT_BROKER_PORT=1883 MQTT_USERNAME="user" MQTT_PASSWORD="<password>" \
python3 main.py
```
An example systemd service file is provided.
### Home Assistant MQTT Config
```yaml
- state_topic: "space-weather/vtec"
name: "VTEC"
unit_of_measurement: "(10^16 el) / m^2"
state_class: measurement
unique_id: space_weather_vtec
```
## Data
### VTEC
<https://www.spaceweather.gov/products/us-total-electron-content>
Unit: `(10^16 el) / m^2`
VTEC, or Vertical TEC, is a specific type of TEC measurement that is taken along a path extending
vertically from the Earth's surface to the edge of the atmosphere. So essentially, VTEC is a subset of TEC, with the
difference lying in the specific path along which the measurement is taken.
Updated hourly.

View File

@ -6,7 +6,7 @@ After=network.target
Type=simple Type=simple
User=homeassistant User=homeassistant
EnvironmentFile=/etc/secrets/space-weather EnvironmentFile=/etc/secrets/space-weather
ExecStart=/srv/space-weather/ha-noaa-space-weather-sensor/venv/bin/python /srv/space-weather/ha-noaa-space-weather-sensor/feeder-mqtt/main.py ExecStart=/srv/space-weather/ha-noaa-space-weather/venv/bin/python /srv/space-weather/ha-noaa-space-weather-sensor/feeder-mqtt/main.py
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s