2023-09-17 06:54:01 +00:00
|
|
|
import {
|
|
|
|
Injectable,
|
|
|
|
InternalServerErrorException,
|
|
|
|
Logger,
|
|
|
|
OnApplicationShutdown,
|
|
|
|
} from '@nestjs/common';
|
2023-09-16 19:45:26 +00:00
|
|
|
import WS1080 from './module/ws1080';
|
2023-09-17 07:05:52 +00:00
|
|
|
import { MoreThan, Repository } from 'typeorm';
|
2023-09-17 06:54:01 +00:00
|
|
|
import { WeatherEntity } from './entities/weather.entity';
|
|
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
|
|
import { Cron } from '@nestjs/schedule';
|
2023-09-17 07:05:52 +00:00
|
|
|
import { HistoryQueryDto } from './dtos/history-query.dto';
|
2023-09-16 19:45:26 +00:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class AppService implements OnApplicationShutdown {
|
2023-09-17 06:54:01 +00:00
|
|
|
private logger = new Logger(AppService.name);
|
2023-09-16 19:45:26 +00:00
|
|
|
public station?: WS1080;
|
|
|
|
|
2023-09-17 06:54:01 +00:00
|
|
|
constructor(
|
|
|
|
@InjectRepository(WeatherEntity)
|
|
|
|
private readonly weatherRepository: Repository<WeatherEntity>,
|
|
|
|
) {}
|
|
|
|
|
2023-09-16 19:45:26 +00:00
|
|
|
async getWeather() {
|
2023-09-17 06:54:01 +00:00
|
|
|
try {
|
|
|
|
// Retrieve from USB
|
|
|
|
if (!this.station) this.station = WS1080.fromDevice();
|
|
|
|
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;
|
2023-09-17 17:49:17 +00:00
|
|
|
entity.rain24h = await this.rainFall24h();
|
2023-09-17 06:54:01 +00:00
|
|
|
|
|
|
|
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.weatherRepository.find({
|
|
|
|
order: { date: -1 },
|
|
|
|
take: 1,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!previous) throw new InternalServerErrorException();
|
|
|
|
|
2023-09-17 17:49:17 +00:00
|
|
|
previous.rain24h = await this.rainFall24h(previous.date);
|
2023-09-17 06:54:01 +00:00
|
|
|
previous.fresh = false;
|
|
|
|
return previous;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-17 07:05:52 +00:00
|
|
|
async getWeatherHistory(query: HistoryQueryDto) {
|
|
|
|
const pageSize = Number(query.pageSize) || 100;
|
|
|
|
const page = Number(query.page) || 1;
|
|
|
|
const [list, rowCount] = await this.weatherRepository.findAndCount({
|
|
|
|
where: query.since
|
|
|
|
? { date: MoreThan(new Date(query.since)) }
|
|
|
|
: undefined,
|
|
|
|
order: { date: -1 },
|
|
|
|
take: pageSize,
|
|
|
|
skip: (page - 1) * pageSize,
|
|
|
|
});
|
|
|
|
const pageCount = Math.ceil(rowCount / pageSize);
|
|
|
|
|
|
|
|
return {
|
|
|
|
list,
|
|
|
|
pagination: {
|
|
|
|
page,
|
|
|
|
pageSize,
|
|
|
|
pageCount,
|
|
|
|
rowCount,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-09-17 17:49:17 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-09-17 06:54:01 +00:00
|
|
|
@Cron('0 * * * *')
|
|
|
|
scheduledPulls() {
|
|
|
|
this.getWeather().catch(() => {
|
|
|
|
// do nothing
|
|
|
|
});
|
2023-09-16 19:45:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
onApplicationShutdown() {
|
|
|
|
this.station?.close();
|
|
|
|
}
|
|
|
|
}
|