add personal-weather and rearrange pages

This commit is contained in:
Matt Walsh
2025-10-21 03:08:48 +00:00
parent 8b076db25d
commit 5b5b313786
17 changed files with 252 additions and 12 deletions

View File

@@ -205,7 +205,7 @@ const formatTimesForColumn = (times) => {
};
// register display
const display = new Almanac(9, 'almanac');
const display = new Almanac(10, 'almanac');
registerDisplay(display);
export default display.getSun.bind(display);

View File

@@ -209,4 +209,4 @@ const shortenExtendedForecastText = (long) => {
};
// register display
registerDisplay(new ExtendedForecast(8, 'extended-forecast'));
registerDisplay(new ExtendedForecast(9, 'extended-forecast'));

View File

@@ -148,4 +148,4 @@ const drawPath = (path, ctx, options) => {
const formatTime = (time) => time.setZone(timeZone()).toFormat('ha').slice(0, -1);
// register display
registerDisplay(new HourlyGraph(4, 'hourly-graph'));
registerDisplay(new HourlyGraph(5, 'hourly-graph'));

View File

@@ -255,7 +255,7 @@ const expand = (data, maxHours = 24) => {
};
// register display
const display = new Hourly(3, 'hourly', false);
const display = new Hourly(4, 'hourly', false);
registerDisplay(display);
export default display.getHourlyData.bind(display);

View File

@@ -205,4 +205,4 @@ const shortenCurrentConditions = (_condition) => {
return condition;
};
// register display
registerDisplay(new LatestObservations(2, 'latest-observations'));
registerDisplay(new LatestObservations(3, 'latest-observations'));

View File

@@ -262,4 +262,4 @@ const parse = (forecast, forecastUrl) => {
}));
};
// register display
registerDisplay(new LocalForecast(7, 'local-forecast'));
registerDisplay(new LocalForecast(8, 'local-forecast'));

View File

@@ -0,0 +1,159 @@
// current weather conditions display
import STATUS from './status.mjs';
import { safeJson } from './utils/fetch.mjs';
import { directionToNSEW } from './utils/calc.mjs';
import { locationCleanup } from './utils/string.mjs';
import WeatherDisplay from './weatherdisplay.mjs';
import { registerDisplay } from './navigation.mjs';
import {
temperature, windSpeed, pressure, distanceMeters, distanceKilometers,
} from './utils/units.mjs';
import { debugFlag } from './utils/debug.mjs';
import Setting from './utils/setting.mjs';
class PersonalWeather extends WeatherDisplay {
constructor(navId, elemId) {
super(navId, elemId, 'Personal Weather Station', true);
}
async getData(weatherParameters, refresh) {
// always load the data for use in the lower scroll
const superResult = super.getData(weatherParameters, refresh);
const dataUrl = '/ambient-relay/api/latest';
let personalData;
try {
personalData = await safeJson(dataUrl, {
retryCount: 3,
stillWaiting: () => this.stillWaiting(),
});
} catch (e) {
console.error(`Unexpected error getting personal weather station data from: ${dataUrl}: ${error.message}`);
}
// test for data received
if (!personalData) {
if (this.isEnabled) this.setStatus(STATUS.failed);
// send failed to subscribers
this.getDataCallback(undefined);
return;
}
// we only get here if there was no error above
this.data = parseData(personalData);
this.getDataCallback();
// stop here if we're disabled
if (!superResult) return;
// Data is available, ensure we're enabled for display
this.timing.totalScreens = 1;
this.setStatus(STATUS.loaded);
}
async drawCanvas() {
super.drawCanvas();
const wind = (typeof this.data.WindSpeed === 'number') ? this.data.WindDirection.padEnd(3, '') + this.data.WindSpeed.toString().padStart(3, ' ') : this.data.WindSpeed;
// get location (city name) from StationInfo if available (allows for overrides)
const location = (StationInfo[this.data.station.properties.stationIdentifier]?.city ?? locationCleanup(this.data.station.properties.name)).substr(0, 20);
const fill = {
temp: this.data.Temperature + String.fromCharCode(176),
condition,
wind,
location,
humidity: `${this.data.Humidity}%`,
dewpoint: this.data.DewPoint + String.fromCharCode(176),
ceiling: (this.data.Ceiling === 0 ? 'Unlimited' : this.data.Ceiling + this.data.CeilingUnit),
visibility: this.data.Visibility + this.data.VisibilityUnit,
pressure: `${this.data.Pressure} ${this.data.PressureDirection}`,
icon: { type: 'img', src: this.data.Icon },
};
if (this.data.WindGust !== '-') fill['wind-gusts'] = `Gusts to ${this.data.WindGust}`;
if (this.data.observations.heatIndex.value && this.data.HeatIndex !== this.data.Temperature) {
fill['heat-index-label'] = 'Heat Index:';
fill['heat-index'] = this.data.HeatIndex + String.fromCharCode(176);
} else if (this.data.observations.windChill.value && this.data.WindChill !== '' && this.data.WindChill < this.data.Temperature) {
fill['heat-index-label'] = 'Wind Chill:';
fill['heat-index'] = this.data.WindChill + String.fromCharCode(176);
}
const area = this.elem.querySelector('.main');
area.innerHTML = '';
area.append(this.fillTemplate('weather', fill));
this.finishDraw();
}
// make data available outside this class
// promise allows for data to be requested before it is available
async getCurrentWeather(stillWaiting) {
// an external caller has requested data, set up auto reload
this.setAutoReload();
if (stillWaiting) this.stillWaitingCallbacks.push(stillWaiting);
return new Promise((resolve) => {
if (this.data) resolve(this.data);
// data not available, put it into the data callback queue
this.getDataCallbacks.push(() => resolve(this.data));
});
}
}
// format the received data
const parseData = (data) => {
// get the unit converter
const windConverter = windSpeed('us');
const temperatureConverter = temperature('us');
const metersConverter = distanceMeters('us');
const kilometersConverter = distanceKilometers('us');
const pressureConverter = pressure('us');
const observations = data.features[0].properties;
// values from api are provided in metric
data.observations = observations;
data.Temperature = temperatureConverter(observations.temperature.value);
data.TemperatureUnit = temperatureConverter.units;
data.DewPoint = temperatureConverter(observations.dewpoint.value);
data.Ceiling = metersConverter(observations.cloudLayers[0]?.base?.value ?? 0);
data.CeilingUnit = metersConverter.units;
data.Visibility = kilometersConverter(observations.visibility.value);
data.VisibilityUnit = kilometersConverter.units;
data.Pressure = pressureConverter(observations.barometricPressure.value);
data.PressureUnit = pressureConverter.units;
data.HeatIndex = temperatureConverter(observations.heatIndex.value);
data.WindChill = temperatureConverter(observations.windChill.value);
data.WindSpeed = windConverter(observations.windSpeed.value);
data.WindDirection = directionToNSEW(observations.windDirection.value);
data.WindGust = windConverter(observations.windGust.value);
data.WindUnit = windConverter.units;
data.Humidity = Math.round(observations.relativeHumidity.value);
// Get the large icon, but provide a fallback if it returns false
const iconResult = getLargeIcon(observations.icon);
data.Icon = iconResult || observations.icon; // Use original icon if getLargeIcon returns false
data.PressureDirection = '';
data.TextConditions = observations.textDescription;
// set wind speed of 0 as calm
if (data.WindSpeed === 0) data.WindSpeed = 'Calm';
// if two measurements are available, use the difference (in pascals) to determine pressure trend
if (data.features.length > 1 && data.features[1].properties.barometricPressure?.value) {
const pressureDiff = (observations.barometricPressure.value - data.features[1].properties.barometricPressure.value);
if (pressureDiff > 150) data.PressureDirection = 'R';
if (pressureDiff < -150) data.PressureDirection = 'F';
}
return data;
};
const display = new PersonalWeather(2, 'personal-weather');
registerDisplay(display);
// export default display.getPersonalWeather.bind(display);

View File

@@ -231,4 +231,4 @@ class Radar extends WeatherDisplay {
}
// register display
registerDisplay(new Radar(11, 'radar'));
registerDisplay(new Radar(12, 'radar'));

View File

@@ -235,4 +235,4 @@ const getAndFormatPoint = async (lat, lon) => {
};
// register display
registerDisplay(new RegionalForecast(6, 'regional-forecast'));
registerDisplay(new RegionalForecast(7, 'regional-forecast'));

View File

@@ -146,4 +146,4 @@ class SpcOutlook extends WeatherDisplay {
}
// register display
registerDisplay(new SpcOutlook(10, 'spc-outlook'));
registerDisplay(new SpcOutlook(11, 'spc-outlook'));

View File

@@ -222,4 +222,4 @@ const getTravelCitiesDayName = (cities) => cities.reduce((dayName, city) => {
}, '');
// register display, not active by default
registerDisplay(new TravelForecast(5, 'travel', false));
registerDisplay(new TravelForecast(6, 'travel', false));