Set up thermometer

This commit is contained in:
hodasemi 2023-10-19 16:26:09 +02:00
parent 11e4a22307
commit d69610fc01
17 changed files with 312 additions and 118 deletions

View file

@ -18,3 +18,4 @@ chrono = "0.4.31"
actix-web = "4.4.0"
midea = { git = "https://gavania.de/hodasemi/Midea.git" }
actix-cors = "0.6.4"
dns-lookup = "2.0.4"

View file

@ -4,5 +4,14 @@
["Tasmota-Plug-2", false],
["Tasmota-Plug-3", true],
["Tasmota-Plug-4", true]
],
"thermostat": [
"shellytrv-8CF681A1F886",
"shellytrv-8CF681E9BAEE",
"shellytrv-B4E3F9D9E2A1"
],
"thermometer": [
"shellyplusht-d4d4da7d85b4",
"shellyplusht-80646fc9db9c"
]
}

View file

@ -30,14 +30,14 @@ class Category {
final Map<String, List<dynamic>> json =
Map.castFrom(jsonDecode(jsonDecode(response.body)));
for (MapEntry<String, List<dynamic>> entry in json.entries) {
for (final MapEntry<String, List<dynamic>> entry in json.entries) {
final Category category = Category(entry.key);
for (dynamic device_info_dyn in entry.value) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in entry.value) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices
.add(DeviceIdOnly(device_info['id'], device_info['desc']));
.add(DeviceIdOnly(deviceInfo['id'], deviceInfo['desc']));
}
if (category.devices.isNotEmpty) {
@ -65,10 +65,10 @@ class Category {
final Category category = Category('plugs');
final List<dynamic> plugs = json['plugs']!;
for (dynamic device_info_dyn in plugs) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in plugs) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await Plug.create(device_info));
category.devices.add(await Plug.create(deviceInfo));
}
categories.add(category);
@ -79,10 +79,10 @@ class Category {
final Category category = Category('thermostat');
final List<dynamic> thermostats = json['thermostat']!;
for (dynamic device_info_dyn in thermostats) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in thermostats) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await Thermostat.create(device_info));
category.devices.add(await Thermostat.create(deviceInfo));
}
categories.add(category);
@ -91,13 +91,13 @@ class Category {
// create temperature_and_humidity
{
final Category category = Category('temperature_and_humidity');
final List<dynamic> temperature_and_humidities =
final List<dynamic> temperatureAndHumidities =
json['temperature_and_humidity']!;
for (dynamic device_info_dyn in temperature_and_humidities) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in temperatureAndHumidities) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await TemperatureHumidity.create(device_info));
category.devices.add(await TemperatureHumidity.create(deviceInfo));
}
categories.add(category);
@ -106,12 +106,12 @@ class Category {
// create dish_washer
{
final Category category = Category('dish_washer');
final List<dynamic> dish_washer = json['dish_washer']!;
final List<dynamic> dishWasher = json['dish_washer']!;
for (dynamic device_info_dyn in dish_washer) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in dishWasher) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await DishWasher.create(device_info));
category.devices.add(await DishWasher.create(deviceInfo));
}
categories.add(category);
@ -120,12 +120,12 @@ class Category {
// create washing_machines
{
final Category category = Category('washing_machines');
final List<dynamic> washing_machines = json['washing_machines']!;
final List<dynamic> washingMachines = json['washing_machines']!;
for (dynamic device_info_dyn in washing_machines) {
final Map<String, dynamic> device_info = device_info_dyn;
for (final dynamic device_info_dyn in washingMachines) {
final Map<String, dynamic> deviceInfo = device_info_dyn;
category.devices.add(await WashingMachine.create(device_info));
category.devices.add(await WashingMachine.create(deviceInfo));
}
categories.add(category);
@ -143,9 +143,9 @@ class Category {
}
class CategoryWidget extends StatelessWidget {
final Category category;
CategoryWidget({super.key, required this.category});
const CategoryWidget({super.key, required this.category});
final Category category;
@override
Widget build(BuildContext context) {
@ -169,6 +169,7 @@ class DeviceIdOnly extends Device {
final String device_id;
final String? device_descriptor;
@override
Widget create_widget(BuildContext context) {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class DishWasher extends Device {
static Future<DishWasher> create(Map<String, dynamic> device_info) async {
static Future<DishWasher> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -9,6 +9,9 @@ import '../states/plug_settings.dart';
import 'devices.dart';
class Plug extends Device {
Plug(this.device_id, this.device_descriptor, this.led_state, this.power_state,
this.power_draw, this.power_control);
final String device_id;
String? device_descriptor;
bool led_state;
@ -16,40 +19,37 @@ class Plug extends Device {
double power_draw;
bool power_control;
static Future<Plug> create(Map<String, dynamic> device_info) async {
final String device_id = device_info['id'];
final String? device_descriptor = device_info['desc'];
final bool power_control = device_info['toggle'];
static Future<Plug> create(Map<String, dynamic> deviceInfo) async {
final String deviceId = deviceInfo['id'];
final String? deviceDescriptor = deviceInfo['desc'];
final bool powerControl = deviceInfo['toggle'];
final response = await http
.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
.get(Uri.parse("${Constants.BASE_URL}/plug_state/$deviceId"));
if (response.statusCode != 200) {
throw Exception("Failed to fetch plug_state for $device_id");
throw Exception("Failed to fetch plug_state for $deviceId");
}
final Map<String, dynamic> device_state =
final Map<String, dynamic> deviceState =
Map.castFrom(jsonDecode(jsonDecode(response.body)));
return Plug(
device_id,
device_descriptor,
device_state["led"],
device_state["power"],
device_state["power_draw"],
power_control && device_state["power_draw"] < 15,
deviceId,
deviceDescriptor,
deviceState["led"],
deviceState["power"],
deviceState["power_draw"],
powerControl && deviceState["power_draw"] < 15,
);
}
Plug(this.device_id, this.device_descriptor, this.led_state, this.power_state,
this.power_draw, this.power_control);
@override
Widget create_widget(BuildContext context) {
const double header_height = 40;
const double info_height = 30;
const double info_width = 60;
const double x_offset = 0.9;
const double headerHeight = 40;
const double infoHeight = 30;
const double infoWidth = 60;
const double xOffset = 0.9;
return Table(
border: TableBorder(
@ -67,7 +67,7 @@ class Plug extends Device {
),
children: [
SizedBox(
height: header_height,
height: headerHeight,
child: Stack(
children: [
Align(
@ -92,7 +92,7 @@ class Plug extends Device {
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -101,16 +101,16 @@ class Plug extends Device {
textAlign: TextAlign.center,
"LED")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: PlugLed(
device_id: device_id, led_state: led_state))),
]))
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -119,9 +119,9 @@ class Plug extends Device {
textAlign: TextAlign.center,
"Power")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: PlugPower(
device_id: device_id,
power_state: power_state,
@ -130,7 +130,7 @@ class Plug extends Device {
]),
TableRow(children: [
SizedBox(
height: info_height,
height: infoHeight,
child: Stack(children: [
const Align(
alignment: Alignment(-0.9, 0.0),
@ -139,9 +139,9 @@ class Plug extends Device {
textAlign: TextAlign.center,
"Power Draw")),
Align(
alignment: const Alignment(x_offset, 0.0),
alignment: const Alignment(xOffset, 0.0),
child: SizedBox(
width: info_width,
width: infoWidth,
child: Text(
textAlign: TextAlign.center, "$power_draw W")))
]))
@ -151,10 +151,10 @@ class Plug extends Device {
}
class PlugLed extends StatefulWidget {
final String device_id;
bool led_state;
PlugLed({super.key, required this.device_id, required this.led_state});
final String device_id;
bool led_state;
@override
State<PlugLed> createState() => PlugLedState();
@ -164,17 +164,17 @@ class PlugLedState extends State<PlugLed> {
String _led_state_info = "";
void _toggle_led_state() {
String target_state;
String targetState;
if (widget.led_state) {
target_state = "off";
targetState = "off";
} else {
target_state = "on";
targetState = "on";
}
change_plug_state(widget.device_id, "led", target_state)
.then((device_state) {
widget.led_state = device_state["led"];
change_plug_state(widget.device_id, "led", targetState)
.then((deviceState) {
widget.led_state = deviceState["led"];
setState(() {
_led_state_info = widget.led_state ? "On" : "Off";
@ -193,15 +193,15 @@ class PlugLedState extends State<PlugLed> {
}
class PlugPower extends StatefulWidget {
final String device_id;
bool power_state;
bool power_control;
PlugPower(
{super.key,
required this.device_id,
required this.power_state,
required this.power_control});
final String device_id;
bool power_state;
bool power_control;
@override
State<PlugPower> createState() => PlugPowerState();
@ -211,19 +211,19 @@ class PlugPowerState extends State<PlugPower> {
String _power_state_info = "";
void _toggle_power_state() {
String target_state;
String targetState;
if (widget.power_state) {
target_state = "off";
targetState = "off";
} else {
target_state = "on";
targetState = "on";
}
change_plug_state(widget.device_id, "power", target_state)
.then((device_state) {
widget.power_state = device_state["power"];
change_plug_state(widget.device_id, "power", targetState)
.then((deviceState) {
widget.power_state = deviceState["power"];
widget.power_control = device_state["power_draw"] < 15;
widget.power_control = deviceState["power_draw"] < 15;
setState(() {
_power_state_info = widget.power_state ? "On" : "Off";
@ -246,19 +246,19 @@ class PlugPowerState extends State<PlugPower> {
}
Future<Map<String, dynamic>> change_plug_state(
String device_id, String module, String state) async {
String deviceId, String module, String state) async {
var response = await http.post(
Uri.parse("${Constants.BASE_URL}/plug/$device_id/${module}_$state"));
Uri.parse("${Constants.BASE_URL}/plug/$deviceId/${module}_$state"));
if (response.statusCode != 200) {
throw Exception("Failed to post new state");
}
response =
await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$device_id"));
await http.get(Uri.parse("${Constants.BASE_URL}/plug_state/$deviceId"));
if (response.statusCode != 200) {
throw Exception("Failed to fetch plug_state for $device_id");
throw Exception("Failed to fetch plug_state for $deviceId");
}
return Map.castFrom(jsonDecode(jsonDecode(response.body)));

View file

@ -4,7 +4,7 @@ import 'devices.dart';
class TemperatureHumidity extends Device {
static Future<TemperatureHumidity> create(
Map<String, dynamic> device_info) async {
Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class Thermostat extends Device {
static Future<Thermostat> create(Map<String, dynamic> device_info) async {
static Future<Thermostat> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -3,7 +3,7 @@ import 'package:flutter/src/widgets/framework.dart';
import 'devices.dart';
class WashingMachine extends Device {
static Future<WashingMachine> create(Map<String, dynamic> device_info) async {
static Future<WashingMachine> create(Map<String, dynamic> deviceInfo) async {
throw UnimplementedError();
}

View file

@ -1,10 +1,9 @@
import 'package:flutter/material.dart';
import 'constants.dart';
import 'states/graphs.dart';
import 'states/home_page.dart';
import 'states/plug_settings.dart';
import 'states/graphs.dart';
void main() {
runApp(const MyApp());
@ -24,8 +23,8 @@ class MyApp extends StatelessWidget {
),
home: const MyHomePage(title: 'Home Server'),
routes: <String, WidgetBuilder>{
'/plug_settings': (BuildContext context) => PlugSettings(),
'/graphs': (BuildContext context) => Graphs(),
'/plug_settings': (BuildContext context) => const PlugSettings(),
'/graphs': (BuildContext context) => const Graphs(),
});
}
}

View file

@ -173,45 +173,45 @@ class _GraphsState extends State<Graphs> {
ElevatedButton(
onPressed: () {
setState(() {
for (var device in devices) {
for (final device in devices) {
if (!device.enabled) {
device.data = List.empty();
continue;
}
int start_date;
int end_date;
int startDate;
int endDate;
try {
start_date = (DateFormat('dd.MM.yyyy')
startDate = (DateFormat('dd.MM.yyyy')
.parse(startDateController.text)
.millisecondsSinceEpoch /
1000) as int;
} on Exception catch (_) {
start_date = 0;
startDate = 0;
print('no start date set');
}
try {
end_date = (DateFormat('dd.MM.yyyy')
endDate = (DateFormat('dd.MM.yyyy')
.parse(endDateController.text)
.millisecondsSinceEpoch /
1000) as int;
} on Exception catch (_) {
end_date = 0;
endDate = 0;
print('no end date set');
}
if (start_date == 0 || end_date == 0) {
if (startDate == 0 || endDate == 0) {
device.data = List.empty();
continue;
}
const String filter_type = 'hourly';
const String filterType = 'hourly';
http
.get(Uri.parse(
"${Constants.BASE_URL}/plug_data/${device.device.device_id}/$start_date/$end_date/$filter_type"))
"${Constants.BASE_URL}/plug_data/${device.device.device_id}/$startDate/$endDate/$filterType"))
.then((response) {
if (response.statusCode != 200) {
throw Exception("Failed to fetch data");
@ -220,14 +220,14 @@ class _GraphsState extends State<Graphs> {
final data = jsonDecode(jsonDecode(response.body))
as List<dynamic>;
final List<ChartData> chart_data = [];
final List<ChartData> chartData = [];
for (final entry in data) {
final pair = entry as List<dynamic>;
chart_data.add(ChartData(pair[0], pair[1]));
chartData.add(ChartData(pair[0], pair[1]));
}
device.data = chart_data;
device.data = chartData;
});
}
});
@ -258,7 +258,6 @@ class _GraphsState extends State<Graphs> {
// titlesData: FlTitlesData(bottomTitles: AxisTitles(sideTitles: ))
),
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
)
])));
});

View file

@ -28,15 +28,15 @@ class _MyHomePageState extends State<MyHomePage> {
}
final data = categories.data!;
final category_count = data.length;
final categoryCount = data.length;
if (category_count > expanded.length) {
final int diff = category_count - expanded.length;
if (categoryCount > expanded.length) {
final int diff = categoryCount - expanded.length;
final List<bool> diff_list = List<bool>.filled(diff, true);
expanded.addAll(diff_list);
} else if (category_count < expanded.length) {
final int diff = expanded.length - category_count;
final List<bool> diffList = List<bool>.filled(diff, true);
expanded.addAll(diffList);
} else if (categoryCount < expanded.length) {
final int diff = expanded.length - categoryCount;
expanded = List<bool>.filled(diff, false);
}

View file

@ -11,7 +11,7 @@ class PlugSettingsArguments {
}
class PlugSettings extends StatefulWidget {
PlugSettings({super.key});
const PlugSettings({super.key});
@override
State<PlugSettings> createState() => _PlugSettingsState();

View file

@ -33,7 +33,7 @@ impl DataBase {
)?;
self.sql.execute(
"CREATE TABLE IF NOT EXISTS devices(
"CREATE TABLE IF NOT EXISTS devices (
id INTEGER PRIMARY KEY,
device VARCHAR(60) NOT NULL,
type VARCHAR(30) NOT NULL,
@ -47,7 +47,8 @@ impl DataBase {
"CREATE TABLE IF NOT EXISTS data (
id INTEGER PRIMARY KEY,
time BIGINT NOT NULL,
watts REAL NOT NULL,
name VARCHAR(30) NOT NULL,
value REAL NOT NULL,
device_id INTEGER NOT NULL,
FOREIGN KEY(device_id) REFERENCES devices(id)
)",
@ -116,16 +117,50 @@ impl DataBase {
)?;
}
for device in devices.thermostat.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermostat\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
for device in devices.thermometer.iter() {
self.sql.execute(
&format!(
"INSERT INTO devices (device, type, control)
SELECT \"{device}\", \"thermometer\", false
WHERE
NOT EXISTS (
SELECT device
FROM devices
WHERE device=\"{device}\"
)
"
),
[],
)?;
}
Ok(())
}
pub fn write(&self, device_name: &str, time: u64, watts: f32) -> Result<()> {
let params: &[&dyn ToSql] = &[&time, &watts];
pub fn write(&self, device_name: &str, time: u64, name: &str, value: f32) -> Result<()> {
let params: &[&dyn ToSql] = &[&time, &value];
self.sql.execute(
&format!(
"INSERT INTO data (time, watts, device_id)
VALUES (?1, ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
"INSERT INTO data (time, name, value, device_id)
VALUES (?1, \"{name}\", ?2, (SELECT id FROM devices WHERE device=\"{device_name}\") )"
),
params,
)?;
@ -156,6 +191,16 @@ impl DataBase {
desc: name,
toggle: control != 0,
}),
"thermostat" => devices.thermostat.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
"thermometer" => devices.temperature_and_humidity.push(DeviceWithName {
id: device,
desc: name,
toggle: control != 0,
}),
_ => panic!(),
}
@ -164,6 +209,19 @@ impl DataBase {
Ok(devices)
}
pub fn device_exists(&self, device_id: &str) -> Result<bool> {
Ok(self
.sql
.prepare(&format!(
"
SELECT *
FROM devices
WHERE device=\"{device_id}\"
"
))?
.exists([])?)
}
pub fn change_device_name(&self, device: &str, description: &str) -> Result<()> {
self.sql.execute(
&format!(
@ -182,7 +240,7 @@ impl DataBase {
pub fn read(&self, device: &str) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
SELECT data.time, data.watts
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
@ -194,7 +252,7 @@ impl DataBase {
pub fn read_range(&self, device: &str, start: u64, end: u64) -> Result<Vec<(u64, f32)>> {
self._read(&format!(
"
SELECT data.time, data.watts
SELECT data.time, data.value
FROM data
INNER JOIN devices
ON data.device_id=devices.id
@ -293,6 +351,8 @@ mod test {
db.register_devices(&Devices {
plugs: vec![("test".to_string(), true)],
thermostat: Vec::new(),
thermometer: Vec::new(),
})?;
fs::remove_file("startup_test.db")?;
@ -308,9 +368,11 @@ mod test {
db.register_devices(&Devices {
plugs: vec![(device_name.to_string(), true)],
thermostat: Vec::new(),
thermometer: Vec::new(),
})?;
db.write(device_name, 0, 5.5)?;
db.write(device_name, 0, "watts", 5.5)?;
let device_descriptor = "udo";
db.change_device_name(device_name, device_descriptor)?;

View file

@ -7,6 +7,8 @@ use serde_json::{from_str, to_string, to_string_pretty};
#[derive(Clone, PartialEq, Eq, Deserialize, Serialize, Debug)]
pub struct Devices {
pub plugs: Vec<(String, bool)>,
pub thermostat: Vec<String>,
pub thermometer: Vec<String>,
}
impl Devices {
@ -55,6 +57,8 @@ mod test {
fn create_conf() -> Result<()> {
let devices = Devices {
plugs: vec![("Dev1".to_string(), true), ("Dev2".to_string(), false)],
thermostat: Vec::new(),
thermometer: Vec::new(),
};
devices.save("test_devices.conf")

View file

@ -12,6 +12,7 @@ mod db;
mod devices;
mod midea_helper;
mod tasmota;
mod temperature;
mod tibber_handler;
mod web_server;
@ -40,7 +41,7 @@ fn read_power_usage(
if let Ok(usage) = plug.read_power_usage().await {
db.lock()
.unwrap()
.write(plug.name(), since_epoch()?, usage)?;
.write(plug.name(), since_epoch()?, "watts", usage)?;
}
Ok::<(), anyhow::Error>(())
@ -81,6 +82,8 @@ async fn run_web_server(
.service(change_device_name)
.service(plug_data)
.service(plug_data_range)
.service(push_temperature)
.service(push_humidity)
})
.bind((IP, PORT))
.map_err(|err| anyhow::Error::msg(format!("failed binding to address: {err:#?}")))?

76
src/temperature.rs Normal file
View file

@ -0,0 +1,76 @@
use std::{
net::IpAddr,
sync::{Arc, Mutex},
};
use actix_web::web::Data;
use anyhow::{bail, Result};
use dns_lookup::{lookup_addr, lookup_host};
use reqwest::Client;
use crate::{db::DataBase, since_epoch};
pub struct Thermostat {
device: String,
}
impl Thermostat {
pub fn new(device: impl ToString) -> Self {
Self {
device: device.to_string(),
}
}
pub async fn set_temperature(&self, temperature: f32) -> Result<()> {
let ips = lookup_host(&self.device)?;
if ips.is_empty() {
bail!("could not resolve device name {}", self.device);
}
let resp = Client::new()
.post(format!("http://{}/ext_t?temp={}", ips[0], temperature))
.send()
.await?;
if !resp.status().is_success() {
bail!("response error");
}
Ok(())
}
}
#[derive(Debug)]
pub enum ThermometerChange {
Temperature(f32),
Humidity(f32),
}
pub struct Thermometer;
impl Thermometer {
pub fn push_change(
change: ThermometerChange,
ip: IpAddr,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<()> {
let db_lock = db.lock().unwrap();
let device_id = lookup_addr(&ip)?.trim_end_matches(".fritz.box").to_string();
if db_lock.device_exists(&device_id)? {
match change {
ThermometerChange::Temperature(temp) => {
db_lock.write(&device_id, since_epoch()?, "temperature", temp)?;
// maybe push to thermostate
}
ThermometerChange::Humidity(humid) => {
db_lock.write(&device_id, since_epoch()?, "humidity", humid)?;
}
}
}
Ok(())
}
}

View file

@ -1,13 +1,17 @@
use actix_web::{
get, post,
web::{Data, Json, Path},
Error, Responder, ResponseError,
Error, HttpRequest, Responder, ResponseError,
};
use chrono::{Datelike, NaiveDateTime, Timelike};
use serde::Serialize;
use serde_json::to_string;
use crate::{db::DataBase, tasmota::Tasmota};
use crate::{
db::DataBase,
tasmota::Tasmota,
temperature::{Thermometer, ThermometerChange},
};
use std::{
collections::HashMap,
@ -226,6 +230,42 @@ async fn plug_data_range(
)?))
}
#[get("/push_temp/{temperature}")]
async fn push_temperature(
param: Path<f32>,
req: HttpRequest,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<impl Responder, Error> {
if let Some(val) = req.peer_addr() {
Thermometer::push_change(
ThermometerChange::Temperature(param.into_inner()),
val.ip(),
db,
)
.map_err(|err| MyError::from(err))?;
}
Ok("Ok")
}
#[get("/push_humid/{humidity}")]
async fn push_humidity(
param: Path<f32>,
req: HttpRequest,
db: Data<Arc<Mutex<DataBase>>>,
) -> Result<impl Responder, Error> {
if let Some(val) = req.peer_addr() {
Thermometer::push_change(
ThermometerChange::Humidity(param.into_inner()),
val.ip(),
db,
)
.map_err(|err| MyError::from(err))?;
}
Ok("Ok")
}
fn collapse_data<F>(data: Vec<(u64, f32)>, f: F) -> Vec<(u64, f32)>
where
F: Fn(NaiveDateTime) -> NaiveDateTime,