add 24 hr max scales, improve readmes, reorganize pred card layout
This commit is contained in:
parent
85636814ed
commit
57fbbe4627
59
README.md
59
README.md
|
@ -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.
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
## Install
|
||||||
|
|
||||||
|
1. Copy `space_weather` to `config/custom_components`
|
||||||
|
2. Restart Home Assistant
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
```
|
|
@ -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);
|
|
@ -157,4 +157,4 @@ class SpaceWeatherCard extends HTMLElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('space-weather-card', SpaceWeatherCard);
|
customElements.define('space-weather-card-current', SpaceWeatherCard);
|
||||||
|
|
|
@ -20,15 +20,17 @@ class SpaceWeatherPredictionCard extends HTMLElement {
|
||||||
<style>
|
<style>
|
||||||
/* 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);
|
||||||
align-items: center;
|
gap: 16px;
|
||||||
}
|
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);
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue