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
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.
This project consists of an external MQTT server, a custom component, and custom dashboard cards.

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):
session = async_get_clientsession(hass)
async_add_entities([
SpaceWeatherScaleSensor(session, CONF_URL, "R"),
SpaceWeatherScaleSensor(session, CONF_URL, "S"),
SpaceWeatherScaleSensor(session, CONF_URL, "G"),
SpaceWeatherScaleSensor(session, CONF_URL, "R", '0', ''),
SpaceWeatherScaleSensor(session, CONF_URL, "S", '0', ''),
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", "MajorProb", "pred_r_major"),
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):
def __init__(self, session, url, scale_key):
def __init__(self, session, url, scale_key, data_selector, trailing):
self._session = session
self._url = url
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._data = None
self._data_selector = data_selector
self._trailing = trailing
@property
def name(self):
@ -42,7 +47,7 @@ class SpaceWeatherScaleSensor(Entity):
@property
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
def state(self):
@ -63,7 +68,7 @@ class SpaceWeatherScaleSensor(Entity):
async with self._session.get(self._url) as response:
if response.status == 200:
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"]}'
else:
_LOGGER.error(f"Error fetching data from {self._url}")
@ -178,7 +183,7 @@ class SpaceWeatherDateStampSensor(Entity):
tomorrow_data = v
assert len(tomorrow_data.keys()) is not None
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:
_LOGGER.error(f"Error fetching data from {self._url}")
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 */
.prediction-container {
display: flex;
flex-direction: column;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
align-items: center;
justify-items: center;
}
.prediction-item {
text-align: center;
margin-bottom: 16px;
/*margin-bottom: 16px;*/
cursor: pointer;
}
@ -96,7 +98,7 @@ class SpaceWeatherPredictionCard extends HTMLElement {
<ha-card>
<div class="card-header">Space Weather Predictions</div>
<div class="card-subheader">
${this._getStateValue('sensor.space_weather_prediction_date_stamp')}
For ${this._getStateValue('sensor.space_weather_prediction_date_stamp')}
</div>
<div class="card-content">
<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
User=homeassistant
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
RestartSec=5s