4 Commits 0e935dab23 ... 628be38039

Autor SHA1 Mensagem Data
  Jason Tarka 628be38039 Include robots.txt to tell search engines to not index. 1 ano atrás
  Jason Tarka de7dba2060 Allow larger diffs when removing too points in a row 1 ano atrás
  Jason Tarka fdd0ffa6d8 Log entries to console + increase max time + linear light 1 ano atrás
  Jason Tarka 0fe812bbbd Max humidity graph at 100% + better high/low display 2 anos atrás

+ 2 - 2
web-view/package.json

@@ -7,11 +7,11 @@
     "test": "ts-mocha src/**/*.test.ts",
     "clean": "rm -rf build",
     "build": "npm run clean && npm run build:js && npm run build:html",
-    "build:html": "cp -r ./src/*.html ./build",
+    "build:html": "cp ./src/*.* ./build",
     "build:js": "webpack --mode=production",
     "watch:js": "webpack --mode=development --watch",
     "watch:html": "npm-watch",
-    "deploy": "npm run clean && npm run build && scp -r build/* storm:/mnt2/nginx-storage/weather/"
+    "deploy": "npm run build && scp -r build/* storm:/mnt2/nginx-storage/weather/"
   },
   "watch": {
     "build:html": {

+ 1 - 0
web-view/src/index.html

@@ -77,6 +77,7 @@
 			Hours to show:
 			<select id="hours-ago"></select>
 		</label>
+<!--		<button id="log-entries">Log raw entries</button>-->
 	</div>
 
 	<!--suppress HtmlUnknownTarget -->

+ 69 - 1
web-view/src/js/cleanData.test.ts

@@ -143,6 +143,74 @@ describe('Clean data', () => {
 
 			const actual = removeSpikes(input);
 			should(actual).deepEqual(input);
-		})
+		});
+
+		it('Does not remove too many spikes', () => {
+			// Test that it doesn't keep removing more and more spikes, such
+			// as if there was a power outage, or the temperature actually
+			// started to rise quickly.
+
+			// Values taken from 2024-04-26 07:49 to 10:12 EDT.
+			const input: TimePoint[] = [
+				{ time: 1, value: 0.95 },
+				{ time: 2, value: -0.52 },
+				{ time: 3, value: -0.88 },
+				{ time: 4, value: -0.82 },
+				{ time: 5, value: -0.74 },
+				{ time: 6, value: -0.5 },
+				{ time: 7, value: 3.36 },
+				{ time: 8, value: 7.98 }, // Some delay happened here.
+				{ time: 9, value: 8.3 }, // And continued to affect here.
+				{ time: 10, value: 3.94 }, // Seemingly normal temperature.
+				{ time: 11, value: 7.23 }, // Long delay happened here.
+				{ time: 12, value: 15.04 }, // But was really resolved here.
+				{ time: 13, value: 8.86 }, // Device cooled back down.
+				{ time: 14, value: 6.41 }, // Normal temperatures mostly resumed here.
+				{ time: 15, value: 5.72 },
+				{ time: 16, value: 5.77 },
+				{ time: 17, value: 5.85 },
+				{ time: 18, value: 6.07 },
+				{ time: 19, value: 6.33 },
+				{ time: 20, value: 6.64 },
+				{ time: 21, value: 6.99 },
+				{ time: 22, value: 7.26 },
+				{ time: 23, value: 7.54 },
+				{ time: 24, value: 7.84 },
+				{ time: 25, value: 10.09 },
+			];
+			const expected: TimePoint[] = [
+				{ time: 1, value: 0.95 },
+				{ time: 2, value: -0.52 },
+				{ time: 3, value: -0.88 },
+				{ time: 4, value: -0.82 },
+				{ time: 5, value: -0.74 },
+				{ time: 6, value: -0.5 },
+				{ time: 7, value: 3.36 },
+				// These two are definite spikes, but the difference to #7 is within normal range.
+				{ time: 8, value: 7.98 }, // Some delay happened here.
+				{ time: 9, value: 8.3 }, // And continued to affect here.
+				// This one should be included, but the "compare to previous diff" sees too big of a drop.
+				// TODO: Figure out how to include this one.
+				// { time: 10, value: 3.94 },
+				{ time: 11, value: 7.23 }, // Long delay happened here.
+				// Remove 12, since it was way too big.
+				{ time: 13, value: 8.86 }, // Device cooled back down.
+				{ time: 14, value: 6.41 }, // Normal temperatures mostly resumed here.
+				{ time: 15, value: 5.72 },
+				{ time: 16, value: 5.77 },
+				{ time: 17, value: 5.85 },
+				{ time: 18, value: 6.07 },
+				{ time: 19, value: 6.33 },
+				{ time: 20, value: 6.64 },
+				{ time: 21, value: 6.99 },
+				{ time: 22, value: 7.26 },
+				{ time: 23, value: 7.54 },
+				{ time: 24, value: 7.84 },
+				{ time: 25, value: 10.09 },
+			];
+
+			const actual = removeSpikes(input);
+			should(actual).deepEqual(expected);
+		});
 	});
 });

+ 42 - 3
web-view/src/js/cleanData.ts

@@ -145,8 +145,14 @@ export function removeSpikes(
 		return data;
 	}
 
+	let removed: TimePoint[] = [];
+	let currMinChange = minChange;
+
 	for(let i = 0; i < data.length; i++) {
-		let prevDiff:number, thisDiff:number;
+		let prevDiff:number,
+			thisDiff:number,
+			thisValue: number,
+			prevValue: number;
 
 		// General case for most of an array
 		if(i > 2) {
@@ -158,14 +164,32 @@ export function removeSpikes(
 			thisDiff = Math.abs(data[i].value - data[i + 1].value);
 		}
 		const isCooling = i > 0 && data[i].value < data[i - 1].value;
-		const minChangeToUse = isCooling ? minChange * 2 : minChange;
+
+		// Minimum change required before removing the spike.
+		const minChangeToUse = isCooling ? currMinChange * 2 : currMinChange;
 
 		const tooBig = (thisDiff - prevDiff) > minChangeToUse
 					&& (thisDiff / prevDiff) > minDiffMultiple;
 		if(tooBig) {
-			console.log(`Removing spike element ${formatTime(data[i].time)}: Value: ${data[i].value}; prevDiff: ${prevDiff}; thisDiff: ${thisDiff}`);
+			console.log(`Removing spike element ${formatTime(data[i].time)}: Value: ${data[i].value}; Prev value: ${data[i-1].value}; prevDiff: ${prevDiff}; thisDiff: ${thisDiff}`);
+
+			// Remove the element, and store it so it can be re-added if needed.
+			removed.push(data[i]);
 			data = removeElement(data, i);
 			i--; // Removed the element, make sure we don't skip the next
+		} else {
+			// Held onto an element, reset the removed list.
+			removed = [];
+			currMinChange = minChange;
+		}
+
+		// Don't allow removing too many elements in a row.
+		if(removed.length >= 4) {
+			console.log('Removed too many elements. Going to re-add some:', removed);
+			data = insertElements(data, removed, i + 1);
+
+			currMinChange *= 1.1; // Allow reprocessed elements to be larger.
+			removed = [];
 		}
 	}
 
@@ -179,3 +203,18 @@ function removeElement<T>(arr:Array<T>, index:number):Array<T> {
 
 	return arr.slice(0, index).concat(arr.slice(index+1));
 }
+
+/**
+ * Insert a number of elements into the middle of an array.
+ * @param arr The array to add the elements to.
+ * @param toInsert The elements to add.
+ * @param index The index of `arr` to insert the elements.
+ *              The new elements will be inserted before the element currently at this index.
+ */
+function insertElements<T>(arr: Array<T>, toInsert: Array<T>, index: number): Array<T> {
+	if(index <= 0 || index >= arr.length) {
+		throw new Error(`Index ${index} out of bounds; Min 1, max ${arr.length-1}`);
+	}
+
+	return arr.slice(0, index).concat(toInsert).concat(arr.slice(index));
+}

+ 10 - 1
web-view/src/js/consts.ts

@@ -1,6 +1,6 @@
 export const HOURS_QUERY:string = 'hours';
 export const HOURS_DEFAULT:number = 12;
-export const MAX_HOURS_AGO = 672;
+export const MAX_HOURS_AGO:number = 1344; // 8 weeks
 
 /**
  * The total padding to add to a range on a graph to make tiny differences
@@ -28,6 +28,8 @@ export type FieldInfo = {
 	excludeGauge: boolean,
 	minValue: number,
 	maxValue: number,
+	/** If the maximum value should be stretched upwards. */
+	stretchMax: boolean,
 	gaugeColours: any,
 }
 
@@ -51,6 +53,7 @@ export const FIELDS: FieldInfo[] = [
 		windowType: WindowType.AVERAGE,
 		minValue: -10,
 		maxValue: 30,
+		stretchMax: true,
 		excludeGauge: false,
 		gaugeColours: {
 			'-10': Colours.PaleBlue,
@@ -70,6 +73,7 @@ export const FIELDS: FieldInfo[] = [
 		windowType: WindowType.AVERAGE,
 		minValue: 0,
 		maxValue: 100,
+		stretchMax: false,
 		excludeGauge: false,
 		gaugeColours: {
 			20: Colours.Orange,
@@ -87,6 +91,7 @@ export const FIELDS: FieldInfo[] = [
 		windowType: WindowType.AVERAGE,
 		minValue: 960,
 		maxValue: 1030,
+		stretchMax: true,
 		excludeGauge: false,
 		gaugeColours: {
 			990: Colours.Blue,
@@ -103,6 +108,7 @@ export const FIELDS: FieldInfo[] = [
 		windowType: WindowType.AVERAGE,
 		minValue: 50,
 		maxValue: 8191,
+		stretchMax: false,
 		excludeGauge: false,
 		gaugeColours: {
 			0: Colours.Blue,
@@ -120,6 +126,7 @@ export const FIELDS: FieldInfo[] = [
 		windowType: WindowType.MAX,
 		minValue: 0,
 		maxValue: 10,
+		stretchMax: true,
 		excludeGauge: true,
 		gaugeColours: null,
 	},
@@ -135,4 +142,6 @@ export const HOUR_OPTIONS: Array<number|[number, string]> = [
 	[168, '1 week'],
 	[336, '2 weeks'],
 	[672, '4 weeks'],
+	[1008, '6 weeks'],
+	[1344, '8 weeks'],
 ];

+ 11 - 6
web-view/src/js/graphs.ts

@@ -41,7 +41,7 @@ function addCanvas(
 
 	const minMax: HTMLSpanElement = document.createElement('span');
 	let unit = fieldInfo.unit ? `&ThinSpace;${fieldInfo.unit}` : '';
-	minMax.innerHTML = `(${data.minimum}${unit} - ${data.maximum}${unit})`;
+	minMax.innerHTML = `(H: ${data.maximum}${unit} / L: ${data.minimum}${unit})`;
 
 	const title = document.createElement('div');
 	title.append(header, minMax);
@@ -92,16 +92,21 @@ function createGraph(
 			scales: {
 				y: {
 					suggestedMin: minMax.min - (RANGE_PADDING / 2),
-					suggestedMax: minMax.max < 0 ? 0 : minMax.max + (RANGE_PADDING / 2)
+					suggestedMax: (() => {
+						if (fieldInfo.stretchMax) {
+							return minMax.max < 0 ? 0 : minMax.max + (RANGE_PADDING / 2)
+						}
+						return minMax.max;
+					})()
 				}
 			}
 		}
 	};
 
-	if(fieldInfo.key === 'light') {
-		const scale = config.options.scales.y;
-		scale.type = 'logarithmic';
-	}
+	// if(fieldInfo.key === 'light') {
+	// 	const scale = config.options.scales.y;
+	// 	scale.type = 'logarithmic';
+	// }
 
 	new Chart(canvas, config);
 }

+ 31 - 0
web-view/src/js/main.ts

@@ -42,6 +42,8 @@ async function init() {
 	);
 
 	showHourSelection();
+	document.getElementById('log-entries')
+		.addEventListener('click', () => logReadings(readings));
 }
 
 function getHoursAgo():number {
@@ -119,3 +121,32 @@ async function getRecentData(
 		});
 	});
 }
+
+/**
+ * Log readings to the console in a way that can be imported to a spreadsheet.
+ * @param readings The list of readings to log.
+ */
+function logReadings(readings: Reading[]) {
+	const separator = '\t';
+	const header = [
+		'Time',
+		'Temperature (C)',
+		'Humidity (%)',
+		'Pressure (hPa)',
+		'Light',
+		'Delay (minutes)',
+	].join(separator);
+
+	const lines = readings.map(r => {
+		const time = new Date(r.time);
+		return [
+			`"${time.toLocaleDateString()} ${time.toLocaleTimeString()}"`,
+			r.temperature,
+			r.humidity,
+			r.pressure,
+			r.light,
+			r.delay,
+		].join(separator);
+	});
+	console.log(header + '\n' + lines.join('\n'));
+}

+ 5 - 0
web-view/src/robots.txt

@@ -0,0 +1,5 @@
+User-agent: *
+Disallow: /
+
+User-agent: AdsBot-Google
+Disallow: /