192 lines
4.8 KiB
TypeScript
192 lines
4.8 KiB
TypeScript
import {
|
|
Injectable,
|
|
InternalServerErrorException,
|
|
Logger,
|
|
OnApplicationShutdown,
|
|
} from '@nestjs/common';
|
|
import WS1080 from './module/ws1080';
|
|
import { Between, LessThan, MoreThan, Repository } from 'typeorm';
|
|
import { WeatherEntity } from './entities/weather.entity';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Cron } from '@nestjs/schedule';
|
|
import { HistoryQueryDto } from './dtos/history-query.dto';
|
|
import { GraphQueryDto } from './dtos/graph-query.dto';
|
|
|
|
@Injectable()
|
|
export class AppService implements OnApplicationShutdown {
|
|
private logger = new Logger(AppService.name);
|
|
public station?: WS1080;
|
|
|
|
constructor(
|
|
@InjectRepository(WeatherEntity)
|
|
private readonly weatherRepository: Repository<WeatherEntity>,
|
|
) {}
|
|
|
|
/**
|
|
* Get current weather information.
|
|
* @returns Weather data
|
|
*/
|
|
async getWeather() {
|
|
try {
|
|
// Retrieve from USB
|
|
await this.createStation();
|
|
const data = await this.station.read();
|
|
|
|
// Save weather data to database
|
|
const entity = this.weatherRepository.create({
|
|
date: new Date(),
|
|
...data,
|
|
});
|
|
|
|
await this.weatherRepository.save(entity);
|
|
entity.fresh = true;
|
|
entity.rain24h = await this.rainFall24h();
|
|
|
|
return entity;
|
|
} catch (error) {
|
|
// USB errors likely mean we are not connected anymore
|
|
if (error.message?.includes('USB')) {
|
|
try {
|
|
this.station?.close();
|
|
} catch {}
|
|
this.station = null;
|
|
}
|
|
|
|
this.logger.error('Failed to retrieve weather data:', error.stack);
|
|
|
|
// Retrieve previous entry on error
|
|
const previous = await this.getPreviousEntry();
|
|
if (!previous) throw new InternalServerErrorException();
|
|
|
|
return previous;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get previous weather entry.
|
|
* @returns Previous weather data entry
|
|
*/
|
|
async getPreviousEntry() {
|
|
const [previous] = await this.weatherRepository.find({
|
|
order: { date: -1 },
|
|
take: 1,
|
|
});
|
|
|
|
if (!previous) return null;
|
|
|
|
previous.rain24h = await this.rainFall24h(previous.date);
|
|
previous.fresh = false;
|
|
return previous;
|
|
}
|
|
|
|
/**
|
|
* Get weather history.
|
|
* @param query Weather history search
|
|
* @returns Paginated history list
|
|
*/
|
|
async getWeatherHistory(
|
|
query: HistoryQueryDto,
|
|
select?: (keyof WeatherEntity)[],
|
|
) {
|
|
const pageSize = Number(query.pageSize) || 100;
|
|
const page = Number(query.page) || 1;
|
|
const [list, rowCount] = await this.weatherRepository.findAndCount({
|
|
select,
|
|
where:
|
|
query.since || query.until
|
|
? {
|
|
date:
|
|
query.since && query.until
|
|
? Between(new Date(query.since), new Date(query.until))
|
|
: query.since
|
|
? MoreThan(new Date(query.since))
|
|
: LessThan(new Date(query.until)),
|
|
}
|
|
: undefined,
|
|
order: { date: -1 },
|
|
take: pageSize,
|
|
skip: (page - 1) * pageSize,
|
|
});
|
|
const pageCount = Math.ceil(rowCount / pageSize);
|
|
|
|
return {
|
|
list,
|
|
pagination: {
|
|
page,
|
|
pageSize,
|
|
pageCount,
|
|
rowCount,
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get graph datasets for weather.
|
|
* @param query Weather history search
|
|
* @returns Paginated graph dataset
|
|
*/
|
|
async graphWeatherHistory(query: GraphQueryDto) {
|
|
const { list, pagination } = await this.getWeatherHistory(query, [
|
|
...(query.columns as (keyof WeatherEntity)[]),
|
|
'date',
|
|
]);
|
|
|
|
const datasets = query.columns.reduce(
|
|
(mass, key: keyof WeatherEntity) => ({
|
|
...mass,
|
|
[key]: { label: String(key), data: [] },
|
|
}),
|
|
{},
|
|
);
|
|
|
|
list.forEach((entry) => {
|
|
Object.keys(entry)
|
|
.filter((key) => !['id', 'date'].includes(key))
|
|
.forEach((key) => {
|
|
datasets[key].data.push({
|
|
x: entry.date.getTime(),
|
|
y: entry[key],
|
|
});
|
|
});
|
|
});
|
|
|
|
return {
|
|
list: Object.values(datasets),
|
|
pagination,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get rainfall in the last 24h since `since` start point.
|
|
* @param since Time start point
|
|
* @returns Rainfall in mm
|
|
*/
|
|
async rainFall24h(since = new Date()) {
|
|
const { rainfall } = await this.weatherRepository
|
|
.createQueryBuilder('weather')
|
|
.select('SUM(rainDiff)', 'rainfall')
|
|
.where('date >= :date', {
|
|
date: new Date(since.getTime() - 24 * 60 * 60 * 1000),
|
|
})
|
|
.getRawOne();
|
|
return Number(rainfall) || 0;
|
|
}
|
|
|
|
/**
|
|
* Create station instance.
|
|
*/
|
|
async createStation() {
|
|
if (this.station) return;
|
|
this.station = WS1080.fromDevice();
|
|
|
|
const previous = await this.getPreviousEntry();
|
|
if (previous) {
|
|
this.station.previousRain = previous.totalRain;
|
|
}
|
|
}
|
|
|
|
onApplicationShutdown() {
|
|
this.station?.close();
|
|
}
|
|
}
|