//////////////////////////////////////////////////////////////////////
// Copyright (C) Hiroshi SUGIMURA 2023.08.26
//////////////////////////////////////////////////////////////////////
/**
* @module mainCo2s
*/
'use strict'
//////////////////////////////////////////////////////////////////////
// 基本ライブラリ
const Store = require('electron-store');
const co2s = require('usb-ud-co2s');
const cron = require('node-cron');
require('date-utils'); // for log
const { Sequelize, Op, roomEnvModel } = require('./models/localDBModels'); // DBデータと連携
const { objectSort, getNow, getToday, isObjEmpty, mergeDeeply } = require('./mainSubmodule');
let sendIPCMessage = null;
const store = new Store();
let config = {
enabled: false,
debug: false,
place: 'Room'
};
let persist = {};
//////////////////////////////////////////////////////////////////////
// mainCo2s
let mainCo2s = {
isRun: false,
observationJob: null,
storeJob: null,
//////////////////////////////////////////////////////////////////////
//
/**
* @func start
* @desc Co2sセンサの処理開始
* @param {Object} _sendIPCMessage
* @return void
* @throw error
*/
start: function (_sendIPCMessage) {
sendIPCMessage = _sendIPCMessage;
if (mainCo2s.isRun) { // 重複起動対策
sendIPCMessage("renewCo2sConfigView", config);
sendIPCMessage("renewCo2s", persist);
mainCo2s.sendTodayRoomEnv(); // 現在のデータを送っておく
return;
}
config.enabled = store.get('config.Co2s.enabled', config.enabled);
config.place = store.get('config.Co2s.place', config.place);
config.debug = store.get('config.Co2s.debug', config.debug);
persist = store.get('persist.Co2s', persist);
sendIPCMessage("renewCo2sConfigView", config);
if (config.enabled == false) {
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.start() usb-ud-co2s is disabled.') : 0;
mainCo2s.isRun = false;
return;
}
mainCo2s.isRun = true;
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.start()') : 0;
try {
co2s.start((sensorData, error) => {
if (error) {
// それ以外のエラーは良く知らないのでエラーとして出す
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.co2s.start()', error);
return;
}
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.start() sensorData:', '\x1b[32m', sensorData, '\x1b[0m') : 0;
switch (sensorData.state) {
case 'OK':
break;
case 'connected':
persist.time = new Date().toFormat("YYYY-MM-DD HH24:MI:SS");
persist.temperature = sensorData.TMP;
persist.humidity = sensorData.HUM;
persist.co2 = sensorData.CO2;
sendIPCMessage("renewCo2s", persist);
break;
}
});
} catch (e) {
}
mainCo2s.storeJob = cron.schedule('*/1 * * * *', async () => {
try {
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.cron.schedule() every 1min') : 0;
let dt = new Date();
//------------------------------------------------------------
// 部屋の環境を記録、Co2s
if (config.enabled && persist.length != 0) {
// config.debug ? console.log( new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| main.cron.schedule() Store Co2s'):0;
let n = persist;
if (n) {
roomEnvModel.create({
dateTime: dt,
srcType: 'Co2s',
place: config.place ? config.place : 'Room',
temperature: n.temperature,
humidity: n.humidity,
CO2: n.co2
});
}
} else {
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.cron.schedule() persist:', persist) : 0;
}
mainCo2s.sendTodayRoomEnv(); // 本日のデータの定期的送信
} catch (error) {
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.cron.schedule() each 1min, error:', error);
}
});
sendIPCMessage("renewCo2s", persist);
mainCo2s.sendTodayRoomEnv(); // 現在のデータを送っておく
mainCo2s.storeJob.start();
},
/**
* @func stop
* @desc stop
* @async
* @param {void}
* @return void
* @throw error
*/
stop: async function () {
mainCo2s.isRun = false;
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.stop()') : 0;
if (mainCo2s.observationJob) {
await mainCo2s.observationJob.stop();
mainCo2s.observationJob = null;
}
await mainCo2s.setConfig(config);
await store.set('persist.Co2s', persist);
await co2s.stop();
},
/**
* @func stopWithoutSave
* @desc stopWithoutSave
* @async
* @param {void}
* @return void
* @throw error
*/
stopWithoutSave: async function () {
mainCo2s.isRun = false;
config.debug ? console.log(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.stopWithoutSave()') : 0;
if (mainCo2s.observationJob) {
await mainCo2s.observationJob.stop();
mainCo2s.observationJob = null;
}
await co2s.stop();
},
/**
* @func setConfig
* @desc setConfig
* @async
* @param {void}
* @return void
* @throw error
*/
setConfig: async function (_config) {
if (_config) {
config = mergeDeeply(config, _config);
}
await store.set('config.Co2s', config);
sendIPCMessage("renewCo2sConfigView", config);
sendIPCMessage("configSaved", 'Co2s');// 保存したので画面に通知
},
/**
* @func getConfig
* @desc getConfig
* @async
* @param {void}
* @return void
* @throw error
*/
getConfig: function () {
return config;
},
/**
* @func getPersist
* @desc getPersist
* @async
* @param {void}
* @return void
* @throw error
*/
getPersist: function () {
return persist;
},
//////////////////////////////////////////////////////////////////////
// inner functions
/**
* @func getCases
* @desc getCases
* @async
* @param {void}
* @return void
* @throw error
*/
/*
getCases
input
date: Date="2023-01-06"
output
when createdAt >= "2023-01-05 23:57" and createdAt < "2023-01-06 00:00" then "00:00"
when createdAt >= "2023-01-06 00:00" and createdAt < "2023-01-06 00:03" then "00:03"
when createdAt >= "2023-01-06 00:03" and createdAt < "2023-01-06 00:06" then "00:06"
...
when createdAt >= "2023-01-06 23:54" and createdAt < "2023-01-06 23:57" then "23:57"
else "24:00"
*/
getCases: function (date) {
let T1 = new Date(date);
let T2 = new Date(date);
let T3 = new Date(date);
let T4 = new Date(date);
// UTCだがStringにて表現しているので、なんか複雑
T1.setHours(T1.getHours() - T1.getHours() - 10, 57, 0, 0); // 前日の14時57分xx秒 14:57:00 .. 15:00:00 --> 00:00
T2.setHours(T1.getHours() - T1.getHours() - 10, 58, 0, 0); // T1 + 1min
T3.setHours(T1.getHours() - T1.getHours() - 10, 59, 0, 0); // T1 + 2min
T4.setHours(T1.getHours() - T1.getHours(), 0, 0, 0); // 集約先
let ret = "";
for (let t = 0; t < 480; t += 1) { // 24h * 20 times (= 60min / 3min)
// console.log( T1.toISOString(), ':', T1.toFormat('YYYY-MM-DD HH24:MI'), ', ', T4.toFormat('HH24:MI') );
ret += `WHEN "createdAt" LIKE "${T1.toFormat('YYYY-MM-DD HH24:MI')}%" OR "createdAt" LIKE "${T2.toFormat('YYYY-MM-DD HH24:MI')}%" OR "createdAt" LIKE "${T3.toFormat('YYYY-MM-DD HH24:MI')}%" THEN "${T4.toFormat('HH24:MI')}" \n`;
T1.setMinutes(T1.getMinutes() + 3); // + 3 min
T2.setMinutes(T2.getMinutes() + 3); // + 3 min
T3.setMinutes(T3.getMinutes() + 3); // + 3 min
T4.setMinutes(T4.getMinutes() + 3); // + 3 min
}
return ret + 'ELSE "24:00"';
},
/**
* @func getRows
* @desc getRows
* @async
* @param {void}
* @return void
* @throw error
*/
// DBからテーブル取得
getRows: async function () {
try {
let now = new Date(); // 現在
let begin = new Date(now); // 現在時刻UTCで取得
begin.setHours(begin.getHours() - begin.getHours() - 1, 57, 0, 0); // 前日の23時57分0秒にする
let end = new Date(begin); // 現在時刻UTCで取得
end.setHours(begin.getHours() + 25, 0, 0, 0); // 次の日の00:00:00にする
let cases = mainCo2s.getCases(now);
let subQuery = `CASE ${cases} END`;
// 3分毎データ
let rows = await roomEnvModel.findAll({
attributes: ['id',
[Sequelize.fn('AVG', Sequelize.col('temperature')), 'avgTemperature'],
[Sequelize.fn('AVG', Sequelize.col('humidity')), 'avgHumidity'],
[Sequelize.fn('AVG', Sequelize.col('CO2')), 'avgCO2'],
'createdAt',
[Sequelize.literal(subQuery), 'timeunit']
],
where: {
srcType: 'Co2s',
dateTime: { [Op.between]: [begin.toISOString(), end.toISOString()] }
},
group: ['timeunit']
});
return rows;
} catch (error) {
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.getTodayRoomEnvCo2s()', error);
}
},
/**
* @func getTodayRoomEnv
* @desc getTodayRoomEnv
* @async
* @param {void}
* @return void
* @throw error
*/
getTodayRoomEnv: async function () {
// 画面に今日のデータを送信するためのデータ作る
try {
let rows = await mainCo2s.getRows();
let T1 = new Date();
T1.setHours(0, 0, 0);
let array = [];
for (let t = 0; t < 480; t += 1) {
let row = rows.find((row) => row.dataValues.timeunit == T1.toFormat('HH24:MI'));
if (row) {
array.push({
id: t,
time: T1.toISOString(),
srcType: 'Co2s',
temperature: row.dataValues.avgTemperature,
humidity: row.dataValues.avgHumidity,
CO2: row.dataValues.avgCO2
});
} else {
array.push({
id: t,
time: T1.toISOString(),
srcType: 'Co2s',
temperature: null,
humidity: null,
CO2: null
});
}
T1.setMinutes(T1.getMinutes() + 3); // + 3 min
}
return array;
} catch (error) {
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.getTodayRoomEnvCo2s()', error);
}
},
/**
* @func sendTodayRoomEnv
* @desc sendTodayRoomEnv
* @async
* @param {void}
* @return void
* @throw error
*/
sendTodayRoomEnv: async function () {
let arg = {};
if (config.enabled) {
arg = await mainCo2s.getTodayRoomEnv();
sendIPCMessage('renewRoomEnvCo2s', JSON.stringify(arg));
} else {
console.error(new Date().toFormat("YYYY-MM-DDTHH24:MI:SS"), '| mainCo2s.sendTodayRoomEnv() config.enabled:', config.enabled);
}
}
};
module.exports = mainCo2s;
//////////////////////////////////////////////////////////////////////
// EOF
//////////////////////////////////////////////////////////////////////