-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.js
438 lines (362 loc) · 17.8 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
require("dotenv").config();
const express = require('express');
const bodyParser = require('body-parser');
const cors = require("cors");
const path = require('path');
const mysql = require('mysql');
const ejs = require('ejs');
const moment = require('moment-timezone');
const cron = require('node-cron');
const { Configuration, OpenAIApi } = require('openai');
const timezone = 'Africa/Kampala';
const app = express();
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "https://caelumsense.derrickml.com/");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
//sets the content type header to application/javascript before sending the file, which should inform the browser that the file contains JavaScript code.
app.get('/assets/js/:filename', (req, res) => {
const filename = req.params.filename;
res.set('Content-Type', 'application/javascript');
res.sendFile(path.join(__dirname, `/public/assets/js/${filename}`));
});
// Set up OpenAI API configuration
const configuration = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});
// Create OpenAI API instance
const openai = new OpenAIApi(configuration);
// Set up MySQL connection
const dbCredentials = require('./db'); //Import file containing credentials
// Establish database connection using the credentials
const connection = mysql.createConnection({
host: dbCredentials.host,
user: dbCredentials.user,
password: dbCredentials.password,
database: dbCredentials.database
});
// Connect to MySQL
connection.connect((err) => {
if (err) throw err;
console.log('Connected to MySQL database');
});
// Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// Set up middleware for serving static files and parsing request body
app.use(express.static(path.join(__dirname, '/public/pages')));
// =============================ROUTES============================================================
// Route to fetch user input for number of datapoints and save to the database
app.route('/max-data')
.post((req, res) => {
number = req.body.number;
console.log(`The user entered ${number}`);
const truncateSql = `TRUNCATE TABLE max_data`;
const insertSql = `INSERT INTO max_data (max_data) VALUES (${number})`;
connection.query(truncateSql, (err, results) => {
if (err) throw err;
connection.query(insertSql, (err, results) => {
if (err) throw err;
console.log('Data inserted successfully');
});
});
res.json({ number });
number = 100;
});
// Route to fetch data from the database and send to the frontend
app.get('/chart-api', (req, res) => {
let maxData = 10;
const maxdataSql = `SELECT max_data FROM max_data`;
//Query the MaxData point db
connection.query(maxdataSql, (err, results) => {
if (err) throw err;
maxData = results[0].max_data;
console.log(`maxData = ${maxData}`);
let MaxData = {
max_data: results.map((entry) => entry.max_data)
};
const humiditySql = `SELECT * FROM (SELECT * FROM humidity_data ORDER BY dateTime DESC LIMIT ${maxData}) sub ORDER BY id ASC`;
const temperatureSql = `SELECT * FROM (SELECT * FROM temperature_data ORDER BY dateTime DESC LIMIT ${maxData}) sub ORDER BY id ASC`;
const lightSql = `SELECT * FROM (SELECT * FROM light_data ORDER BY dateTime DESC LIMIT ${maxData}) sub ORDER BY id ASC`;
let humidityData, temperatureData, lightData;
//Query the temperature db
connection.query(temperatureSql, (err, results) => {
if (err) throw err;
temperatureData = {
temperature: results.map((entry) => entry.temperature),
dateTime: results.map((entry) => moment.tz(entry.dateTime, 'UTC').tz(timezone).format('MM Do, h:mm a'))
};
//Query the humidity db
connection.query(humiditySql, (err, results) => {
if (err) throw err;
humidityData = {
humidity: results.map((entry) => entry.humidity),
dateTime: results.map((entry) => moment.tz(entry.dateTime, 'UTC').tz(timezone).format('MM Do, h:mm a'))
};
//Query the LightLevel db
connection.query(lightSql, (err, results) => {
if (err) throw err;
lightData = {
light: results.map((entry) => entry.light),
dateTime: results.map((entry) => moment.tz(entry.dateTime, 'UTC').tz(timezone).format('MM Do, h:mm a'))
};
const chartData = {
humidityData,
temperatureData,
lightData,
MaxData
};
res.json(chartData);
});
});
});
});
});
// Route handler for displaying paginated sensor data
app.get('/table', (req, res) => {
const pageSize = 10; // number of records to display per page
const currentPage = req.query.page || 1; // get current page from query parameter or default to page 1
const startIndex = (currentPage - 1) * pageSize; // calculate start index of records to display
// Query the database for sensor data
const sensorDataQuery = `SELECT * FROM sensor_data ORDER BY id DESC LIMIT ${startIndex},${pageSize}`;
connection.query(sensorDataQuery, (err, sensorDataResults) => {
if (err) throw err;
const sensorData = sensorDataResults.map(data => {
const localTime = moment.tz(data.dateTime, 'Africa/Kampala');
data.dateTime = localTime.format('Do[/]MM[/]YY - HH:mm:ss');
return data;
});
const sensorTotalCountQuery = 'SELECT COUNT(*) AS totalCount FROM sensor_data';
connection.query(sensorTotalCountQuery, (err, sensorTotalCountResult) => {
if (err) throw err;
const sensorTotalCount = sensorTotalCountResult[0].totalCount;
const sensorPageCount = Math.ceil(sensorTotalCount / pageSize);
// Query the database for temperature data
const tempPageSize = 5; // number of records to display per page
const tempCurrentPage = req.query.page || 1; // get current page from query parameter or default to page 1
const tempStartIndex = (tempCurrentPage - 1) * tempPageSize; // calculate start index of records to display
const tempDataQuery = `SELECT * FROM temperature_data ORDER BY id DESC LIMIT ${tempStartIndex},${tempPageSize}`;
connection.query(tempDataQuery, (err, tempDataResults) => {
if (err) throw err;
const tempData = tempDataResults.map(data => {
const localTime = moment.tz(data.dateTime, 'Africa/Kampala');
data.dateTime = localTime.format('Do[/]MM[/]YY - HH:mm');
return data;
});
const tempTotalCountQuery = 'SELECT COUNT(*) AS totalCount FROM temperature_data';
connection.query(tempTotalCountQuery, (err, tempTotalCountResult) => {
if (err) throw err;
const tempTotalCount = tempTotalCountResult[0].totalCount;
const tempPageCount = Math.ceil(tempTotalCount / tempPageSize);
// Query the database for humidity data
const humidPageSize = 5; // number of records to display per page
const humidCurrentPage = req.query.page || 1; // get current page from query parameter or default to page 1
const humidStartIndex = (humidCurrentPage - 1) * humidPageSize; // calculate start index of records to display
const humidDataQuery = `SELECT * FROM humidity_data ORDER BY id DESC LIMIT ${humidStartIndex},${humidPageSize}`;
connection.query(humidDataQuery, (err, humidDataResults) => {
if (err) throw err;
const humidData = humidDataResults.map(data => {
const localTime = moment.tz(data.dateTime, 'Africa/Kampala');
data.dateTime = localTime.format('Do[/]MM[/]YY - HH:mm');
return data;
});
const humidTotalCountQuery = 'SELECT COUNT(*) AS totalCount FROM humidity_data';
connection.query(humidTotalCountQuery, (err, humidTotalCountResult) => {
if (err) throw err;
const humidTotalCount = humidTotalCountResult[0].totalCount;
const humidPageCount = Math.ceil(humidTotalCount / humidPageSize);
// Query the database for humidity data
const lightPageSize = 5; // number of records to display per page
const lightCurrentPage = req.query.page || 1; // get current page from query parameter or default to page 1
const lightStartIndex = (lightCurrentPage - 1) * lightPageSize; // calculate start index of records to display
const lightDataQuery = `SELECT * FROM light_data ORDER BY id DESC LIMIT ${lightStartIndex},${lightPageSize}`;
connection.query(lightDataQuery, (err, lightDataResults) => {
if (err) throw err;
const lightData = lightDataResults.map(data => {
const localTime = moment.tz(data.dateTime, 'Africa/Kampala');
data.dateTime = localTime.format('Do[/]MM[/]YY - HH:mm');
return data;
});
const lightTotalCountQuery = 'SELECT COUNT(*) AS totalCount FROM light_data';
connection.query(lightTotalCountQuery, (err, lightTotalCountResult) => {
if (err) throw err;
const lightTotalCount = lightTotalCountResult[0].totalCount;
const lightPageCount = Math.ceil(lightTotalCount / lightPageSize);
res.render('table', {
sensorData,
sensorPageCount,
currentPage,
tempData,
tempPageCount,
tempCurrentPage,
humidData,
humidPageCount,
humidCurrentPage,
lightData,
lightPageCount,
lightCurrentPage,
});
});
});
});
});
});
});
});
});
});
// Endpoint to receive sensor data
app.post('/sensor-data', (req, res) => {
const { temperature, humidity, light } = req.body;
// Validate inputs
if (!temperature || !humidity) {
return res.status(400).json({ error: 'Missing input data' });
}
// Insert data into database tables
const tempSql = `INSERT INTO temperature_data (temperature) VALUES (${temperature})`;
const humiditySql = `INSERT INTO humidity_data (humidity) VALUES (${humidity})`;
const lightSql = `INSERT INTO light_data (light) VALUES (${light})`;
connection.query(tempSql, (err, results) => {
if (err) {
console.error('Error inserting temperature data into database:', err);
return res.status(500).json({ error: 'Server error' });
}
connection.query(humiditySql, (err, results) => {
if (err) {
console.error('Error inserting humidity data into database:', err);
return res.status(500).json({ error: 'Server error' });
}
connection.query(lightSql, (err, results) => {
if (err) {
console.error('Error inserting light data into database:', err);
return res.status(500).json({ error: 'Server error' });
}
res.status(200).json({ message: 'Sensor data saved successfully' });
});
});
});
});
// Endpoint to receive actuator data
app.post('/actuator-data', (req, res) => {
const { fanStatus, humidifierStatus, pumpStatus, lightStatus } = req.body;
// Validate inputs
if (!fanStatus || !humidifierStatus || !pumpStatus || !lightStatus) {
return res.status(400).json({ error: 'Missing input data' });
}
// Insert data into database
const sql = `INSERT INTO actuator_data (fanStatus, humidifierStatus, pumpStatus, lightStatus) VALUES ('${fanStatus}', '${humidifierStatus}', '${pumpStatus}', '${lightStatus}')`;
connection.query(sql, (err, results) => {
if (err) {
console.error('Error inserting data into database:', err);
return res.status(500).json({ error: 'Server error' });
}
res.status(200).json({ message: 'Actuator data saved successfully' });
});
});
/**====CHAT Establishment routes=================**/
// Define a global variable to store the sensor data
let sensorData = [];
// Define a function to update the sensor data
async function updateSensorData() {
async function getDataFromDatabase() {
return new Promise((resolve, reject) => {
connection.query('SELECT * FROM temp_humid ORDER BY dateTime DESC LIMIT 100', (err, results) => {
if (err) {
reject(err);
} else {
resolve(results);
}
});
});
}
try {
// Get the last 50 rows of sensor data from the database
const data = await getDataFromDatabase();
sensorData = data;
} catch (error) {
console.error("Error while getting sensor data:", error.message);
}
}
// Update the sensor data initially and every 30 seconds thereafter
updateSensorData();
setInterval(updateSensorData, 30000);
// API endpoint for the chatbot
app.post('/api/chat', async(req, res) => {
const question = req.body.question;
try {
// Reuse sensorData efficiently
const data = sensorData;
// Format the data for readability
const dataAsText = data.map(row => {
const dateTime = moment.tz(row.dateTime, 'Africa/Kampala').format('YYYY-MM-DD HH:mm:ss');
return `${dateTime}, ${row.temperature}, ${row.humidity}`;
}).join('\\n'); // Escape newlines
const dataDescription = `The table below shows the last 100 temperature and humidity sensor readings in the format "Timestamp (Africa/Kampala), Temperature, Humidity":\\n${dataAsText}\\n`;
let responseText = "";
let attempts = 0;
while (responseText.trim().length < 10 && attempts < 5) {
const response = await openai.createCompletion({
model: "text-davinci-003",
prompt: `I am a knowledgeable Caelum (AI) and created by Derrick L. Mayiku. I only so far have access to the following data (Temperature and Humdity) with the corresponding timestamps (Please note that I won't be able to list the data in a table format or listing format:x):\\n${dataDescription}\\nUser: ${question}\\nCaelum (AI):`,
max_tokens: 800,
n: 1,
stop: ["\\n"],
});
responseText = response.data.choices[0].text.trim();
attempts++;
}
// Convert escaped newlines back to actual newline characters
responseText = responseText.replace(/\\n/g, '\n');
// Return the response as a JSON object
res.json({ response: responseText });
} catch (error) {
console.error("Caelum is Turned off >> Error while running completion:", error.message);
res.status(500).json({ error: error.message });
}
});
//=======END CHAT ROUTE=========================
/**======UPDATE temp_humid TABLE=============**/
// query to create the new table
const createTableQuery = `
CREATE TABLE IF NOT EXISTS temp_humid (
id INT NOT NULL AUTO_INCREMENT,
temperature FLOAT NOT NULL,
humidity FLOAT NOT NULL,
dateTime TIMESTAMP NOT NULL,
PRIMARY KEY (id)
)
`;
// execute the query to create the new table
connection.query(createTableQuery, (error, results, fields) => {
if (error) throw error;
console.log('Table created successfully');
});
// query to insert data into the new table
const insertDataQuery = `
INSERT INTO temp_humid (temperature, humidity, dateTime)
SELECT t.temperature, h.humidity, t.dateTime
FROM temperature_data t
JOIN humidity_data h ON t.dateTime = h.dateTime
WHERE t.dateTime >= DATE_SUB(NOW(), INTERVAL 5 SECOND)
`;
// execute the query to insert data into the new table every 5 seconds
setInterval(() => {
connection.query(insertDataQuery, (error, results, fields) => {
if (error) throw error;
console.log('Data inserted successfully');
});
}, 5000);
/**======ENDO OF UPDATE temp_humid TABLE=============**/
// Set up view engine and template directory
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');
// Set up route for displaying the home page
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '/public/pages/dashboard.html'));
});
// Start the server
const port = 3000;
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});