Subversion Repositories heating

Rev

Rev 52 | Rev 54 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3 andreas 1
/*
2
 * Copyright (C) 2015 by Andreas Theofilu. All rights reserved!
3
 *
4
 * All rights reserved. No warranty, explicit or implicit, provided.
5
 *
6
 * NOTICE:  All information contained herein is, and remains
7
 * the property of Andreas Theofilu and his suppliers, if any.
8
 * The intellectual and technical concepts contained
9
 * herein are proprietary to Andreas Theofilu and its suppliers and
10
 * may be covered by European and Foreign Patents, patents in process,
11
 * and are protected by trade secret or copyright law.
12
 * Dissemination of this information or reproduction of this material
13
 * is strictly forbidden unless prior written permission is obtained
14
 * from Andreas Theofilu.
15
 * 
16
 * Author: Andreas Theofilu <andreas@theosys.at>
17
 */
18
#include <iostream>
19
#include <fstream>
20
#include <string>
4 andreas 21
#include <iomanip>
8 andreas 22
#include <thread>
3 andreas 23
#include <cstring>
24
#include <cctype>
25
#include <cstddef>
26
#include <cstdio>
27
#include <cstdlib>
4 andreas 28
#include <ctime>
3 andreas 29
#include <fstream>
30
#include <cerrno>
8 andreas 31
#include <cmath>
3 andreas 32
#include <unistd.h>
33
#include <syslog.h>
34
#include <sys/stat.h>
35
#include <sys/types.h>
36
#include <fcntl.h>
4 andreas 37
#include <sqlite3.h>
9 andreas 38
#include <bcm2835.h>
3 andreas 39
#include "heating.h"
17 andreas 40
#include "html.h"
3 andreas 41
#include "config.h"
42
#include "helper.h"
43
 
44
using namespace std;
45
 
9 andreas 46
#define PIN1	RPI_V2_GPIO_P1_03
47
#define PIN2	RPI_V2_GPIO_P1_05
48
#define PIN3	RPI_V2_GPIO_P1_07
49
#define PIN4	RPI_V2_GPIO_P1_11
50
#define PIN5	RPI_V2_GPIO_P1_13
51
#define PIN6	RPI_V2_GPIO_P1_15
52
#define PIN7	RPI_V2_GPIO_P1_16
53
#define PIN8	RPI_V2_GPIO_P1_18
54
#define PIN9	RPI_V2_GPIO_P1_22
8 andreas 55
 
3 andreas 56
heating::heating()
57
{
58
	enableDebug(Configure.debug);
59
	glb_minimal = 16.0;
4 andreas 60
	glb_night = 18.0;
3 andreas 61
	rm = 0;
12 andreas 62
	outSensor = 0;
5 andreas 63
	rstop = false;
64
	rrun = false;
3 andreas 65
	HeatConf = nullptr;
46 andreas 66
	HtmlServer = nullptr;
5 andreas 67
 
68
	for (int i = 0; i < 100; i++)
69
		s1[i] = -1;
70
 
4 andreas 71
	initialized = false;
72
 
73
	if (readConf())
74
	{
75
		if (buildDB())
18 andreas 76
		{
4 andreas 77
			initialized = true;
18 andreas 78
			debug("Initialisation successfully done.");
79
		}
80
		else
81
			debug("Error initializing database");
4 andreas 82
	}
18 andreas 83
	else
84
		debug("Error reading config file.");
3 andreas 85
}
86
 
87
heating::~heating()
88
{
89
	destroyHConf();
90
}
91
 
9 andreas 92
void heating::pthrTemps()
8 andreas 93
{
94
	trun();
95
}
96
 
17 andreas 97
void heating::pthrHtml()
98
{
46 andreas 99
	if (HtmlServer != nullptr)
100
		return;
101
 
17 andreas 102
	HtmlServer = new html();
103
	HtmlServer->setGlobals(glb_night, glb_minimal, onoff);
49 andreas 104
	applyConfig();
105
 
106
	HtmlServer->run();
107
	delete HtmlServer;
108
	HtmlServer = nullptr;
109
}
110
 
111
void heating::applyConfig()
112
{
113
HCONF *akt = HeatConf;
114
html::HTCONF ht;
115
 
17 andreas 116
	while (akt)
117
	{
118
		strcpy (ht.rname, akt->rname);
119
		ht.rnum = akt->rnum;
120
		ht.soll = akt->soll;
121
		ht.night = akt->night;
122
		ht.minimal = akt->minimal;
35 andreas 123
		ht.start1 = akt->start;
124
		ht.end1 = akt->end;
125
		ht.start2 = akt->wstart;
126
		ht.end2 = akt->wend;
49 andreas 127
 
17 andreas 128
		HtmlServer->addConfig(&ht);
129
		akt = akt->next;
130
	}
131
}
132
 
4 andreas 133
void heating::run()
134
{
5 andreas 135
int i;
136
double tmp;
137
HCONF *akt;
52 andreas 138
time_t loctime = getTime();
4 andreas 139
 
5 andreas 140
	rstop = false;
141
	rrun = true;
142
	i = 0;
143
 
12 andreas 144
	debug ("heating::run() reached!");
8 andreas 145
	// First start the runtime routine of class temperature.
9 andreas 146
	// We need this to get our temperatures
147
	thread pthr_temps(&heating::pthrTemps, this);
148
	pthr_temps.detach();
12 andreas 149
	debug ("heating::run() started thread temperature::trun() and detached");
17 andreas 150
	// Then we start the HTML thread.
151
	thread pthr_html(&heating::pthrHtml, this);
152
	pthr_html.detach();
153
	debug("heating::run() started thread html");
9 andreas 154
	// Here we read the previous colected temperatures and evaluate them.
5 andreas 155
	while (!rstop)
156
	{
157
		akt = HeatConf;
9 andreas 158
 
46 andreas 159
		if (HtmlServer->hasChanged())
160
		{
161
			html::HTCONF *hta;
47 andreas 162
			string ans;
46 andreas 163
 
164
			glb_minimal = HtmlServer->getGlbAbsent();
165
			glb_night = HtmlServer->getGlbNight();
166
			onoff = HtmlServer->getRunMode();
47 andreas 167
			ans = "CONFIG:GLOBAL NIGHT:" + doubleToString(glb_night, 1) + ";";
168
			answer(ans);
169
			ans = "CONFIG:GLOBAL MINIMAL:" + doubleToString(glb_minimal, 1) + ";";
170
			answer(ans);
48 andreas 171
			ans = "POWER:";
172
			ans.append((onoff) ? "ON;" : "OFF;");
47 andreas 173
			answer(ans);
174
 
46 andreas 175
			hta = HtmlServer->getHConfig();
176
 
177
			while (hta)
178
			{
179
				akt = HeatConf;
180
 
181
				while (akt)
182
				{
183
					if (akt->rnum == hta->rnum)
184
					{
47 andreas 185
						stringstream stream;
186
 
46 andreas 187
						akt->soll = hta->soll;
188
						akt->night = hta->night;
189
						akt->minimal = hta->minimal;
190
						akt->start = hta->start1;
191
						akt->end = hta->end1;
192
						akt->wstart = hta->start2;
193
						akt->wend = hta->end2;
47 andreas 194
						stream << "CONFIG:ROOM:" << akt->rnum << ":" << string(akt->rname) << ":" << fixed <<
195
						setprecision(1) << akt->soll << ":" << akt->night << ":" <<
196
						akt->minimal << ":" << timeToStr(akt->start) << ":" << timeToStr(akt->end) << ":" <<
197
						timeToStr(akt->wstart) << ":" << timeToStr(akt->wend) << ";";
198
						answer(stream.str());
46 andreas 199
						break;
200
					}
201
 
202
					akt = akt->next;
203
				}
204
 
205
				hta = hta->next;
206
			}
207
 
208
			akt = HeatConf;
209
		}
210
 
5 andreas 211
		while (akt)
212
		{
52 andreas 213
			/// Set the mode for every room.
53 andreas 214
			if (!onoff)
215
			{
216
				if (loctime >= akt->start && loctime <= akt->end)
217
					akt->status = NORMAL;
218
				else
219
					akt->status = NIGHT;
220
			}
52 andreas 221
			else
53 andreas 222
				akt->status = OFF;
52 andreas 223
 
5 andreas 224
			tmp = getTemp(akt->tempsensor);
225
 
226
			if (tmp < 99.0 && tmp > -99.0)
227
				akt->ist = tmp;
228
 
9 andreas 229
			if (evaluateTemp(akt->rnum, tmp))			// Should we start heating?
230
				startHeat(akt->rnum);					// yes
231
			else										// Or should we stop heating
232
				stopHeat(akt->rnum);					// yes
233
 
5 andreas 234
			akt = akt->next;
235
		}
236
 
237
		i++;
238
 
239
		if (i > 60)
240
		{
241
			i = 0;
242
			akt = HeatConf;
243
 
244
			while (akt)
245
			{
47 andreas 246
				answer("IST:" + itostring(akt->rnum) + ":" + doubleToString(akt->ist, 1) + ";");
5 andreas 247
				akt = akt->next;
248
			}
249
		}
250
 
251
		sleep(1);
252
	}
253
 
254
	rrun = false;
4 andreas 255
}
256
 
12 andreas 257
void heating::controlHeat (int room, bool stst)
9 andreas 258
{
259
HCONF *akt = HeatConf;
260
int pin;
261
 
262
	while (akt)
263
	{
264
		if (akt->rnum == room)
265
			break;
266
 
267
		akt = akt->next;
268
	}
269
 
270
	if (!akt || akt->valve)
271
		return;
272
 
273
	switch (akt->gpio)
274
	{
275
		case 1: pin = PIN1; break;
276
		case 2: pin = PIN2; break;
277
		case 3: pin = PIN3; break;
278
		case 4: pin = PIN4; break;
279
		case 5: pin = PIN5; break;
280
		case 6: pin = PIN6; break;
281
		case 7: pin = PIN7; break;
282
		case 8: pin = PIN8; break;
283
		case 9: pin = PIN9; break;
284
		default:
285
			return;
286
	}
287
 
288
	// Enable the GPIO pin to start heating
289
	if (!bcm2835_init())
290
		return;
291
 
292
	// Set the pin to be an output
293
	bcm2835_gpio_fsel(pin, BCM2835_GPIO_FSEL_OUTP);
294
	// Turn it on
12 andreas 295
	bcm2835_gpio_write(pin, (stst)? HIGH : LOW);
296
	akt->valve = stst;
9 andreas 297
	bcm2835_close();
298
}
299
 
4 andreas 300
double heating::incSoll(int room)
301
{
302
HCONF *akt;
303
char *zErrMsg = nullptr;
304
int rc;
305
sqlite3 *db;
306
sqlite3_stmt *res;
307
string query;
308
 
309
	rc = sqlite3_open(Configure.home, &db);
310
 
311
	if (rc)
312
	{
313
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
314
		return 0.0;
315
	}
316
 
317
	if ((akt = findRoom(room)) != nullptr)
318
	{
319
		if (akt->soll >= 30.0)
320
		{
321
			sqlite3_close(db);
322
			return akt->soll;
323
		}
324
 
325
		akt->soll += 0.5;
49 andreas 326
		applyConfig();
47 andreas 327
		query = "update heating set soll = " + doubleToString(akt->soll, 1) + " where id = " + itostring(room);
4 andreas 328
 
5 andreas 329
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 330
		{
331
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
332
			sqlite3_free(zErrMsg);
333
			sqlite3_close(db);
334
			akt->soll -= 0.5;
335
			return akt->soll;
336
		}
337
 
338
		sqlite3_close(db);
5 andreas 339
		stringstream stream;
49 andreas 340
		stream << "SOLL:" << fixed << setprecision(1) << akt->soll << ";";
5 andreas 341
		answer(stream.str());
4 andreas 342
		return akt->soll;
343
	}
344
 
345
	return 0.0;
346
}
347
 
348
double heating::decSoll(int room)
349
{
350
HCONF *akt;
351
char *zErrMsg = nullptr;
352
int rc;
353
sqlite3 *db;
354
sqlite3_stmt *res;
355
string query;
356
 
357
	rc = sqlite3_open(Configure.home, &db);
358
 
359
	if (rc)
360
	{
361
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
362
		return 0.0;
363
	}
364
 
365
	if ((akt = findRoom(room)) != nullptr)
366
	{
367
		if (akt->soll <= 10.0)
368
		{
369
			sqlite3_close(db);
370
			return akt->soll;
371
		}
372
 
373
		akt->soll -= 0.5;
49 andreas 374
		applyConfig();
47 andreas 375
		query = "update heating set soll = " + doubleToString(akt->soll, 1) + " where id = " + itostring(room);
4 andreas 376
 
5 andreas 377
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 378
		{
379
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
380
			sqlite3_free(zErrMsg);
381
			sqlite3_close(db);
382
			akt->soll += 0.5;
383
			return akt->soll;
384
		}
385
 
386
		sqlite3_close(db);
5 andreas 387
		stringstream stream;
49 andreas 388
		stream << "SOLL:" << fixed << setprecision(1) << akt->soll << ";";
5 andreas 389
		answer(stream.str());
4 andreas 390
		return akt->soll;
391
	}
392
 
393
	return 0.0;
394
}
395
 
9 andreas 396
void heating::setSoll (int room, double val)
397
{
398
HCONF *akt = HeatConf;
399
char *zErrMsg = nullptr;
400
int rc;
401
sqlite3 *db;
402
sqlite3_stmt *res;
403
string query;
404
 
405
	if (val < 10.0 || val > 30.0)
406
	{
407
		syslog(LOG_INFO, "Invalid value for maximum room temperature!");
408
		return;
409
	}
410
 
411
	rc = sqlite3_open(Configure.home, &db);
412
 
413
	if (rc)
414
	{
415
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
416
		return;
417
	}
418
 
419
	if (room > 0 && (akt = findRoom(room)) != nullptr)
420
	{
421
		akt->soll = val;
49 andreas 422
		applyConfig();
47 andreas 423
		query = "update heating set soll = " + doubleToString(val, 1) + " where id = " + itostring(room);
9 andreas 424
 
425
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
426
		{
427
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
428
			sqlite3_free(zErrMsg);
429
			sqlite3_close(db);
430
			return;
431
		}
432
 
47 andreas 433
		query = "SOLL:" + itostring(room) + ":" + doubleToString(val, 1) + ";";
9 andreas 434
		answer(query);
435
	}
436
 
437
	sqlite3_close(db);
438
}
439
 
4 andreas 440
void heating::setNight(int room, double val)
441
{
442
HCONF *akt;
443
char *zErrMsg = nullptr;
444
int rc;
445
sqlite3 *db;
446
sqlite3_stmt *res;
447
string query;
448
 
449
	if (val < 10.0 || val > 25.0)
450
	{
451
		syslog(LOG_INFO, "Invalid value for the night!");
452
		return;
453
	}
454
 
455
	rc = sqlite3_open(Configure.home, &db);
456
 
457
	if (rc)
458
	{
459
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
460
		return;
461
	}
462
 
463
	if (room > 0 && (akt = findRoom(room)) != nullptr)
464
	{
465
		akt->night = val;
49 andreas 466
		applyConfig();
47 andreas 467
		query = "update heating set night = " + doubleToString(val, 1) + " where id = " + itostring(room);
5 andreas 468
 
469
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 470
		{
471
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
472
			sqlite3_free(zErrMsg);
473
			sqlite3_close(db);
474
			return;
475
		}
476
 
47 andreas 477
		query = "NIGHT:" + itostring(room) + ":" + doubleToString(val, 1) + ";";
5 andreas 478
		answer(query);
4 andreas 479
	}
480
	else if (room == 0)
481
	{
482
		glb_night = val;
49 andreas 483
 
484
		if (HtmlServer != nullptr)
485
			HtmlServer->setGlobals(glb_night, glb_minimal, onoff);
486
 
47 andreas 487
		query = "update glbheat set night = " + doubleToString(val, 1);
4 andreas 488
 
5 andreas 489
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 490
		{
491
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
492
			sqlite3_free(zErrMsg);
493
			sqlite3_close(db);
494
			return;
495
		}
5 andreas 496
 
47 andreas 497
		query = "CONFIG:GLOBAL NIGHT:" + doubleToString(val, 1) + ";";
5 andreas 498
		answer(query);
4 andreas 499
	}
500
 
501
	sqlite3_close(db);
502
}
503
 
504
void heating::setMinimal(int room, double val)
505
{
506
HCONF *akt;
507
char *zErrMsg = nullptr;
508
int rc;
509
sqlite3 *db;
510
sqlite3_stmt *res;
511
string query;
512
 
513
	if (val < 10.0 || val > 20.0)
514
	{
515
		syslog(LOG_INFO, "Invalid value for the night!");
516
		return;
517
	}
518
 
519
	rc = sqlite3_open(Configure.home, &db);
520
 
521
	if (rc)
522
	{
523
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
524
		return;
525
	}
526
 
527
	if (room > 0 && (akt = findRoom(room)) != nullptr)
528
	{
529
		akt->minimal = val;
49 andreas 530
		applyConfig();
47 andreas 531
		query = "update heating set minimal = " + doubleToString(val, 1) + " where id = " + itostring(room);
4 andreas 532
 
5 andreas 533
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 534
		{
535
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
536
			sqlite3_free(zErrMsg);
537
			sqlite3_close(db);
538
			return;
539
		}
5 andreas 540
 
47 andreas 541
		query = "MINIMAL:" + itostring(room) + ":" + doubleToString(val, 1) + ";";
5 andreas 542
		answer(query);
4 andreas 543
	}
544
	else if (room == 0)
545
	{
546
		glb_minimal = val;
49 andreas 547
 
548
		if (HtmlServer != nullptr)
549
			HtmlServer->setGlobals(glb_night, glb_minimal, onoff);
550
 
47 andreas 551
		query = "update glbheat set minimal = " + doubleToString(val, 1);
4 andreas 552
 
5 andreas 553
		if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 554
		{
555
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
556
			sqlite3_free(zErrMsg);
557
			sqlite3_close(db);
558
			return;
559
		}
5 andreas 560
 
47 andreas 561
		query = "CONFIG:GLOBAL MINIMAL:" + doubleToString(val, 1) + ";";
5 andreas 562
		answer(query);
4 andreas 563
	}
564
 
565
	sqlite3_close(db);
566
}
567
 
568
bool heating::switchOnOff(bool what)
569
{
570
HCONF *akt;
571
char *zErrMsg = nullptr;
572
int rc;
573
sqlite3 *db;
574
sqlite3_stmt *res;
575
string query;
576
 
577
	rc = sqlite3_open(Configure.home, &db);
578
 
579
	if (rc)
580
	{
581
		syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
5 andreas 582
		return onoff;
4 andreas 583
	}
584
 
585
	onoff = what;
49 andreas 586
 
587
	if (HtmlServer != nullptr)
588
		HtmlServer->setGlobals(glb_night, glb_minimal, onoff);
589
 
5 andreas 590
	query = "update glbheat set onoff = " + (what)?"1":"0";
4 andreas 591
 
5 andreas 592
	if ((rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg)) != SQLITE_OK)
4 andreas 593
	{
594
		syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
595
		sqlite3_free(zErrMsg);
596
		sqlite3_close(db);
5 andreas 597
		return onoff;
4 andreas 598
	}
599
 
600
	sqlite3_close(db);
49 andreas 601
	query = "POWER:";
602
	query.append((onoff) ? "ON;" : "OFF;");
5 andreas 603
	answer(query);
604
	return onoff;
4 andreas 605
}
606
 
607
double heating::getNight (int room)
608
{
609
HCONF *akt = findRoom(room);
610
 
611
	if (akt && akt->night > 0.0)
612
		return akt->night;
613
 
614
	return glb_night;
615
}
616
 
617
double heating::getMinimal (int room)
618
{
619
HCONF *akt = findRoom(room);
620
 
621
	if (akt && akt->minimal > 0.0)
622
		return akt->minimal;
623
 
624
	return glb_minimal;
625
}
626
 
3 andreas 627
void heating::destroyHConf()
628
{
629
HCONF *p, *next;
630
 
631
	p = HeatConf;
632
 
633
	while (p)
634
	{
635
		next = p->next;
636
		delete p;
637
		p = next;
638
	}
639
 
640
	HeatConf = nullptr;
641
}
642
 
5 andreas 643
void heating::getConfig (int s1)
644
{
645
HCONF *akt = HeatConf;
646
stringstream stream;
647
 
50 andreas 648
	stream << "CONFIG:GLOBAL NIGHT:" << fixed << setprecision(1) << glb_night << ";";
649
	stream << "CONFIG:GLOBAL MINIMAL:" << fixed << setprecision(1) << glb_minimal << ";";
49 andreas 650
	stream << "CONFIG:ONOFF:" << ((onoff) ? "ON;" : "OFF;");
5 andreas 651
 
652
	while (akt)
653
	{
50 andreas 654
		stream << "CONFIG:ROOM:" << itostring(akt->rnum) << ":" << string(akt->rname) << ":" <<
655
			fixed << setprecision(1) << akt->soll << ":" << akt->night << ":" <<
42 andreas 656
			akt->minimal << ":" << timeToStr(akt->start) << ":" << timeToStr(akt->end) << ":" <<
48 andreas 657
			timeToStr(akt->wstart) << ":" << timeToStr(akt->wend) << ";";
5 andreas 658
 
659
		akt = akt->next;
660
	}
49 andreas 661
 
662
	write(s1, stream.str().c_str(), stream.str().length());
5 andreas 663
}
664
 
4 andreas 665
heating::HCONF* heating::findRoom (int room)
666
{
667
HCONF *akt = HeatConf;
668
 
669
	while (akt)
670
	{
671
		if (akt->rnum == room)
672
			return akt;
673
 
674
		akt = akt->next;
675
	}
676
 
677
	return nullptr;
678
}
679
 
3 andreas 680
bool heating::readConf()
681
{
682
ifstream cfile;
683
char line[1024], hv0[256], hv1[128];
684
char *p;
4 andreas 685
CNFSTAT cs;
686
HCONF *akt = HeatConf;
3 andreas 687
 
688
	// If there's no config file, inform the user about it.
689
	cfile.open(Configure.heatconf, ios::in);
690
 
691
	if ((cfile.rdstate() & std::ifstream::failbit) != 0)
692
	{
693
		syslog(LOG_WARNING,"Error opening the config file: %s", strerror(errno));
694
		return false;
695
	}
696
 
697
	syslog(LOG_INFO, "Found config file %s.", Configure.heatconf);
4 andreas 698
	cs = NONE;
3 andreas 699
	// Here we've found a config file. We'll read it and set the
700
	// contents to the structure
701
	while (cfile.getline(&line[0], sizeof(line)))
702
	{
703
		int len;
704
 
705
		trim (&line[0]);
706
 
707
		if (line[0] == '#' || !char_traits<char>::length(line))
708
			continue;
709
 
4 andreas 710
		if ((p = findc(line, char_traits<char>::length(line), ']')) != NULL)
711
		{
712
			char *x;
713
 
714
			*(p+1) = 0;
715
 
716
			if (!compcase(line, "[global]"))
717
			{
718
				cs = GLOBAL;
719
				continue;
720
			}
721
 
722
			if ((x = findc(line, char_traits<char>::length(line), '[')) != NULL)
723
			{
724
				*p = 0;
725
				akt = appendHConf();
726
				strncpy(akt->rname, x+1, sizeof(akt->rname) - 1);
727
				akt->soll = 20.0;
728
				cs = ROOM;
729
				continue;
730
			}
731
			else
732
				cs = NONE;
733
		}
734
 
735
		if ((p = findc(line, char_traits<char>::length(line), '=')) == NULL || cs == NONE)
3 andreas 736
			continue;
737
 
738
		*p = 0;
739
		p++;
740
		len = char_traits<char>::length(line);
741
 
742
		if (len > (int)sizeof(hv0))
743
			len = (int)sizeof(hv0) - 1;
744
 
745
		strncpy(hv0, line, len);
746
		hv0[len] = 0;
747
		trim (hv0);
748
		len = char_traits<char>::length(p);
749
 
750
		if (len > (int)sizeof(hv1))
751
			len = (int)sizeof(hv0) - 1;
752
 
753
		strncpy(hv1, p, len);
754
		hv1[len] = 0;
755
		trim (hv1);
756
 
4 andreas 757
		if (!compcase(hv0, "minimal"))
758
		{
759
			if (cs == GLOBAL)
760
				glb_minimal = atof (hv1);
761
			else
762
				akt->minimal = atof (hv1);
763
		}
3 andreas 764
 
4 andreas 765
		if (!compcase(hv0, "night"))
766
		{
767
			if (cs == GLOBAL)
768
				glb_night = atof (hv1);
769
			else
770
				akt->night = atof (hv1);
771
		}
772
 
773
		if (!compcase(hv0, "OnOff") && cs == GLOBAL)
774
			onoff = ToBool(hv1);
775
 
12 andreas 776
		if (!compcase(hv0, "OutSensor") && cs == GLOBAL)
777
			outSensor = atoi(hv1);
778
 
4 andreas 779
		if (cs == ROOM)
780
		{
781
			if (!compcase(hv0, "soll"))
782
				akt->soll = atoi(hv1);
783
 
784
			if (!compcase(hv0, "Sensor"))
785
				akt->tempsensor = atoi(hv1);
786
 
787
			if (!compcase(hv0, "GPIO"))
788
				akt->gpio = atoi(hv1);
789
 
790
			if (!compcase(hv0, "StartTime"))
13 andreas 791
			{
4 andreas 792
				akt->start = strToTime(hv1);
14 andreas 793
				debug("StartTime: " + itostring(akt->start) + "(" + ToString(hv1) + ")");
13 andreas 794
			}
4 andreas 795
 
796
			if (!compcase(hv0, "EndTime"))
13 andreas 797
			{
4 andreas 798
				akt->end = strToTime(hv1);
14 andreas 799
				debug("EndTime: " + itostring(akt->end) + "(" + ToString(hv1) + ")");
13 andreas 800
			}
42 andreas 801
 
802
			if (!compcase(hv0, "WorkStartTime"))
803
			{
804
				akt->wstart = strToTime(hv1);
805
				debug("WorkStartTime: " + itostring(akt->wstart) + "(" + ToString(hv1) + ")");
806
			}
807
 
808
			if (!compcase(hv0, "WorkEndTime"))
809
			{
810
				akt->wend = strToTime(hv1);
811
				debug("WorkEndTime: " + itostring(akt->wend) + "(" + ToString(hv1) + ")");
812
			}
4 andreas 813
		}
814
	}
815
 
816
	cfile.close ();
817
	return true;
818
}
819
 
820
 
821
bool heating::buildDB()
822
{
823
sqlite3 *db;
824
sqlite3_stmt *res;
825
int rc;
826
char *zErrMsg = nullptr;
827
string query;
828
 
829
	if (access(Configure.home, R_OK | W_OK))		// Try to create a new database if it doesn't exist
830
	{
831
		HCONF *akt;
832
 
18 andreas 833
		debug("Creating SQL3 database.");
4 andreas 834
		rc = sqlite3_open(Configure.home, &db);
835
 
836
		if (rc)
837
		{
838
			syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
839
			return false;
840
		}
841
 
842
		query = "create table \"main\".\"heating\" (";
843
		query.append("\"id\" INTEGER PRIMARY KEY NOT NULL,");
844
		query.append("\"name\" TEXT NOT NULL,");
845
		query.append("\"soll\" REAL,");
846
		query.append("\"night\" REAL,");
847
		query.append("\"minimal\" REAL,");
848
		query.append("\"start\" INTEGER,");
42 andreas 849
		query.append("\"end\" INTEGER,");
850
		query.append("\"wstart\" INTEGER,");
851
		query.append("\"wend\" INTEGER");
4 andreas 852
		query.append(")");
5 andreas 853
		rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);
4 andreas 854
 
855
		if (rc != SQLITE_OK)
856
		{
857
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
858
			sqlite3_free(zErrMsg);
859
			sqlite3_close(db);
860
			return false;
861
		}
862
		else
863
			syslog(LOG_INFO, "Database \"heating\" was successfully created.");
864
 
10 andreas 865
		query = "create table \"main\".\"kitable\" (";
866
		query.append("\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,");
12 andreas 867
		query.append("\"rnum\" INTEGER NOT NULL,");
10 andreas 868
		query.append("\"diffout\" INTEGER,");
869
		query.append("\"diffin\" INTEGER,");
870
		query.append("\"heat\" INTEGER)");
871
		rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);
872
 
873
		if (rc != SQLITE_OK)
874
		{
875
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
876
			sqlite3_free(zErrMsg);
877
			sqlite3_close(db);
878
			return false;
879
		}
880
		else
881
			syslog(LOG_INFO, "Database \"kitable\" was successfully created.");
882
 
883
		query = "create table \"main\".\"glbheat\" (";
884
		query.append("\"night\" REAL,");
885
		query.append("\"minimal\" REAL,");
886
		query.append("\"onoff\" INTEGER");
887
		query.append(")");
888
		rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);
40 andreas 889
 
10 andreas 890
		if (rc != SQLITE_OK)
891
		{
892
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
893
			sqlite3_free(zErrMsg);
894
			sqlite3_close(db);
895
			return false;
896
		}
897
		else
898
			syslog(LOG_INFO, "Database \"glbheat\" was successfully created.");
40 andreas 899
 
4 andreas 900
		// Insert the values from the config file into the table
18 andreas 901
		debug("Inserting configuration into SQL3 database");
4 andreas 902
		akt = HeatConf;
40 andreas 903
 
4 andreas 904
		while (akt)
905
		{
42 andreas 906
			query = "insert into heating (id, name, soll, night, minimal, start, end, wstart, wend) values (";
4 andreas 907
			query.append(itostring(akt->rnum));
10 andreas 908
			query.append(",\""+string(akt->rname)+"\"");
47 andreas 909
			query.append(","+doubleToString(akt->soll, 1));
910
			query.append(","+doubleToString(akt->night, 1));
911
			query.append(","+doubleToString(akt->minimal, 1));
4 andreas 912
			query.append(","+itostring(akt->start));
913
			query.append(","+itostring(akt->end));
42 andreas 914
			query.append(","+itostring(akt->wstart));
915
			query.append(","+itostring(akt->wend));
4 andreas 916
			query.append(")");
5 andreas 917
			rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);
4 andreas 918
 
919
			if (rc != SQLITE_OK)
920
			{
921
				syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
922
				sqlite3_free(zErrMsg);
923
				sqlite3_close(db);
924
				return false;
925
			}
926
 
927
			akt = akt->next;
15 andreas 928
		}
4 andreas 929
 
15 andreas 930
		query = "insert into glbheat (night, minimal, onoff) values (";
47 andreas 931
		query.append(doubleToString(glb_night, 1));
932
		query.append(","+doubleToString(glb_minimal, 1));
15 andreas 933
		query.append(","+itostring(onoff));
934
		query.append(")");
935
		rc = sqlite3_exec(db, query.c_str(), NULL, NULL, &zErrMsg);
936
 
937
		if (rc != SQLITE_OK)
938
		{
939
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), zErrMsg);
940
			sqlite3_free(zErrMsg);
941
			sqlite3_close(db);
942
			return false;
4 andreas 943
		}
944
 
945
		sqlite3_close(db);
946
	}
947
	else
948
	{
949
		HCONF *akt;
15 andreas 950
 
18 andreas 951
		debug("Reading configuration from SQL3 database");
4 andreas 952
		rc = sqlite3_open(Configure.home, &db);
15 andreas 953
 
4 andreas 954
		if (rc)
955
		{
956
			syslog(LOG_DAEMON, "Error opening database %s: %s", Configure.home, sqlite3_errmsg(db));
957
			return false;
958
		}
959
 
960
		akt = HeatConf;
40 andreas 961
 
4 andreas 962
		while (akt)
3 andreas 963
		{
18 andreas 964
			debug("Queriing "+itostring(akt->rnum)+", "+ToString(akt->rname));
4 andreas 965
			query = "select id, name, soll, night, minimal, start, end from heating where id = "+itostring(akt->rnum);
966
 
5 andreas 967
			if (sqlite3_prepare(db, query.c_str(), -1, &res, NULL) != SQLITE_OK)
4 andreas 968
			{
969
				syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), sqlite3_errmsg(db));
970
				sqlite3_close(db);
971
				return false;
972
			}
973
 
974
			if ((rc = sqlite3_step(res)) == SQLITE_ROW)
975
			{
976
				strncpy(akt->rname, (const char *)sqlite3_column_text(res, 1), sizeof(akt->rname) - 1);
977
				akt->soll = sqlite3_column_double(res, 2);
978
				akt->night = sqlite3_column_double(res, 3);
979
				akt->minimal = sqlite3_column_double(res, 4);
980
				akt->start = (time_t)sqlite3_column_int(res, 5);
981
				akt->end = (time_t)sqlite3_column_int(res, 6);
982
			}
983
 
44 andreas 984
			sqlite3_finalize(res);
12 andreas 985
			query = "select rnum, diffout, diffin, heat from kitable where rnum = " + itostring(akt->rnum);
986
 
987
			if (sqlite3_prepare(db, query.c_str(), -1, &res, NULL) != SQLITE_OK)
988
			{
989
				syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), sqlite3_errmsg(db));
990
				sqlite3_close(db);
991
				return false;
992
			}
993
 
994
			while ((rc = sqlite3_step(res)) == SQLITE_ROW)
995
			{
996
				HTABLE *tb = appendHTable(akt);
15 andreas 997
				tb->tempDifferOut = sqlite3_column_double(res, 1);
998
				tb->tempDifferIn = sqlite3_column_double(res, 2);
12 andreas 999
				tb->heat = sqlite3_column_int(res, 3);
18 andreas 1000
				debug("KITABLE: read "+itostring(sqlite3_column_int(res, 0)));
12 andreas 1001
			}
1002
 
44 andreas 1003
			sqlite3_finalize(res);
4 andreas 1004
			akt = akt->next;
3 andreas 1005
		}
4 andreas 1006
 
18 andreas 1007
		debug("Reading global parameters from SQL3 database");
4 andreas 1008
		query = "select night, minimal, onoff from glbheat";
1009
 
5 andreas 1010
		if (sqlite3_prepare(db, query.c_str(), -1, &res, NULL) != SQLITE_OK)
4 andreas 1011
		{
1012
			syslog(LOG_WARNING, "SQL error [%s]: %s", query.c_str(), sqlite3_errmsg(db));
1013
			sqlite3_close(db);
1014
			return false;
1015
		}
1016
 
1017
		if ((rc = sqlite3_step(res)) == SQLITE_ROW)
1018
		{
1019
			glb_night = sqlite3_column_double(res, 0);
1020
			glb_minimal = sqlite3_column_double(res, 1);
1021
			onoff = (time_t)sqlite3_column_int(res, 2);
1022
		}
1023
 
44 andreas 1024
		sqlite3_finalize(res);
4 andreas 1025
		sqlite3_close(db);
3 andreas 1026
	}
4 andreas 1027
 
18 andreas 1028
	debug("All database operations done");
3 andreas 1029
	return true;
1030
}
1031
 
5 andreas 1032
void heating::setHandle (int fd)
1033
{
1034
	// First look into table to make sure the handle isn't already in the table
1035
	for (int i = 0; i < 100; i++)
1036
	{
1037
		if (s1[i] == fd)
1038
			return;
1039
	}
1040
 
1041
	// Because we're here, the handle is not in the table. So insert it now.
1042
	for (int i = 0; i < 100; i++)
1043
	{
1044
		if (s1[i] < 0)
1045
		{
1046
			s1[i] = fd;
1047
			break;
1048
		}
1049
	}
1050
}
1051
 
1052
void heating::removeHandle (int fd)
1053
{
1054
	for (int i = 0; i < 100; i++)
1055
	{
1056
		if (s1[i] == fd)
1057
		{
1058
			s1[i] = -1;
1059
			break;
1060
		}
1061
	}
1062
}
1063
 
1064
void heating::answer (string msg)
1065
{
1066
	for (int i = 0; i < 100; i++)
1067
	{
1068
		if (s1[i] > 0)
1069
			write(s1[i], msg.c_str(), strlen(msg.c_str()));
1070
	}
1071
}
1072
 
4 andreas 1073
time_t heating::strToTime (char* str)
1074
{
14 andreas 1075
int hour, min;
4 andreas 1076
 
7 andreas 1077
	if (strchr(str, ':') != NULL)
1078
	{
14 andreas 1079
		sscanf(str, "%d:%d", &hour, &min);
1080
		return (time_t)hour * 3600 + (time_t)min * 60;
7 andreas 1081
	}
1082
 
1083
	return 0;
4 andreas 1084
}
1085
 
5 andreas 1086
string heating::timeToStr(time_t t)
1087
{
14 andreas 1088
int hour, min;
38 andreas 1089
char hv0[48];
5 andreas 1090
 
14 andreas 1091
	hour = (int)(t / 3600);
39 andreas 1092
	min = (int)((t - (time_t)hour * 3600) / 60);
38 andreas 1093
	sprintf(hv0, "%02d:%02d", hour, min);
1094
	string zeit(hv0);
5 andreas 1095
	return zeit;
1096
}
1097
 
8 andreas 1098
time_t heating::getTime()
1099
{
1100
tm *ltm, ptm;
1101
time_t t = time(NULL);
1102
 
1103
	ltm = localtime(&t);
1104
	memset (&ptm, 0, sizeof(struct tm));
1105
	ptm.tm_hour = ltm->tm_hour;
1106
	ptm.tm_min = ltm->tm_min;
1107
	ptm.tm_isdst = ltm->tm_isdst;
1108
	return mktime(&ptm);
1109
}
1110
 
3 andreas 1111
heating::HCONF* heating::appendHConf()
1112
{
1113
HCONF *akt, *p;
1114
 
1115
	akt = new HCONF;
1116
	memset(akt, 0, sizeof(HCONF));
1117
 
1118
	if (HeatConf == nullptr)
1119
		HeatConf = akt;
1120
	else
1121
	{
1122
		p = HeatConf;
1123
 
1124
		while (p->next)
1125
			p = p->next;
1126
 
1127
		p->next = akt;
1128
	}
1129
 
1130
	rm++;
1131
	akt->rnum = rm;
1132
	return akt;
1133
}
8 andreas 1134
 
1135
heating::HTABLE* heating::appendHTable(HCONF *ht)
1136
{
1137
HTABLE *akt, *p;
1138
 
1139
	akt = new HTABLE;
1140
	memset(akt, 0, sizeof(HTABLE));
1141
 
1142
	if (ht->hTable == nullptr)
1143
		ht->hTable = akt;
1144
	else
1145
	{
1146
		p = ht->hTable;
1147
 
1148
		while (p->next)
1149
			p = p->next;
1150
 
1151
		p->next = akt;
1152
	}
1153
 
1154
	return akt;
1155
}
1156
 
1157
bool heating::evaluateTemp (int room, double temp)
1158
{
1159
HCONF *akt;
9 andreas 1160
double soll, diffOut, diffIn;
52 andreas 1161
time_t loctime;
8 andreas 1162
 
1163
	akt = HeatConf;
1164
 
1165
	while (akt)
1166
	{
1167
		if (akt->rnum == room)
1168
			break;
1169
 
1170
		akt = akt->next;
1171
	}
1172
 
1173
	if (!akt)
1174
		return false;
1175
 
52 andreas 1176
	loctime = getTime();
1177
 
9 andreas 1178
	switch(akt->status)
1179
	{
1180
		case NORMAL:
53 andreas 1181
			if (akt->wstart && akt->wstart < akt->wend && loctime >= akt->wstart && loctime <= akt->wend)
52 andreas 1182
				soll = akt->night;
1183
			else if ((loctime >= akt->start && loctime <= akt->wstart) ||
1184
				(loctime >= akt->wend && loctime <= akt->end)) 
1185
				soll = akt->soll;
1186
 
9 andreas 1187
			diffOut = trunc(soll - getOutside());
1188
			diffIn = trunc(soll - temp);
1189
		break;
1190
 
1191
		case NIGHT:
1192
			if (akt->night)
1193
				soll = akt->night;
1194
			else
1195
				soll = glb_night;
1196
 
1197
			diffOut = trunc(soll - getOutside());
1198
			diffIn = trunc(soll - temp);
1199
		break;
1200
 
1201
		case OFF:
1202
			if (akt->minimal)
1203
				soll = akt->minimal;
1204
			else
1205
				soll = glb_minimal;
1206
 
1207
			diffOut = trunc(soll - getOutside());
1208
			diffIn = trunc(soll - temp);
1209
		break;
1210
	}
1211
 
8 andreas 1212
	if (!akt->hTable)			// If we've no heating table, we've to evaluate the temperature difference
1213
	{
9 andreas 1214
		if (!akt->valve && temp <= (soll - 0.5))
8 andreas 1215
		{
1216
			HTABLE *tb;
1217
			tb = appendHTable(akt);
9 andreas 1218
			tb->tempDifferOut = trunc(soll - getOutside());
1219
			tb->tempDifferIn = trunc(soll - temp);
8 andreas 1220
			tb->start = getTime();
1221
			return true;
1222
		}
1223
 
9 andreas 1224
		if (akt->valve && temp < soll)
8 andreas 1225
			return true;
1226
		else
1227
			return false;
1228
	}
9 andreas 1229
	else if (akt->valve && temp < soll)
8 andreas 1230
		return true;
9 andreas 1231
	else if (akt->valve && temp >= soll)
8 andreas 1232
	{
1233
		HTABLE *tb = akt->hTable;
1234
 
9 andreas 1235
		while (tb)
1236
		{
1237
			if (tb->start)
1238
				break;
8 andreas 1239
 
9 andreas 1240
			if (tb->next)
1241
				tb = tb->next;
1242
			else
1243
				break;
1244
		}
1245
 
1246
		if (tb && tb->start)
8 andreas 1247
		{
53 andreas 1248
			if (loctime > tb->start)
1249
				tb->heat = loctime - tb->start;
8 andreas 1250
			else
53 andreas 1251
				tb->heat = (86400 + loctime) - tb->start;
8 andreas 1252
 
1253
			tb->start = 0;
1254
		}
1255
 
1256
		return false;
1257
	}
9 andreas 1258
	else if (!akt->valve && temp <= (soll - 0.5))		// If temperature is beyond soll, start immediately
53 andreas 1259
	{
1260
		HTABLE *tb = akt->hTable;
1261
		// Find out whether we've already a correct time in the table or not.
1262
		while (tb)
1263
		{
1264
			if (tb->tempDifferOut == diffOut && tb->tempDifferIn == diffIn)
1265
				break;
1266
 
1267
			tb = tb->next;
1268
		}
1269
 
1270
		if (!tb)
1271
		{
1272
			tb = appendHTable(akt);
1273
			tb->tempDifferOut = diffOut;
1274
			tb->tempDifferIn = diffIn;
1275
			tb->start = loctime;
1276
		}
1277
 
9 andreas 1278
		return true;
53 andreas 1279
	}
9 andreas 1280
	else
8 andreas 1281
	{
9 andreas 1282
		HTABLE *tb = akt->hTable;
1283
		time_t t = getTime();
1284
		// Check the current time and look in learned table whether we should
1285
		// start heating or not.
1286
		if (tb)											// Only if we already have a table!
1287
		{												// Yes there is one, so search through it.
1288
			bool strt = false;
1289
 
1290
			if (diffIn <= 0.0)							// Is it warmer inside than it should be?
1291
				return false;							// yes, then turn heating off
1292
 
1293
			if (akt->status == OFF && diffIn >= 0.5)	// Are we in off mode and is the temperature lower than it should be?
1294
				return true;							// yes, then start heating
1295
			else if (akt->status == OFF)				// otherwise ...
1296
				return false;							// no heating
1297
 
1298
			while (tb)									// Search through learned table
1299
			{
1300
				if (!tb->start && tb->tempDifferOut == diffOut && tb->tempDifferIn == diffIn)
1301
					break;
1302
 
1303
				tb = tb->next;
1304
			}
1305
 
1306
			if (tb)										// Have we found a known time to heat?
1307
			{											// Yes, then look if it's time to heat
1308
				if (akt->status == NORMAL)
1309
				{
1310
					if ((t + tb->heat) >= akt->start && (t + tb->heat) <= akt->end)
1311
						return true;
1312
				}
1313
				else if (akt->status == NIGHT)
1314
				{
1315
					if ((t + tb->heat) >= akt->end || t < (akt->end - tb->heat))
1316
						return true;
1317
				}
1318
			}
1319
			else										// None of the saved temperture differences match.
1320
			{											// So test if it's time to add a new one
1321
				if (akt->status == NORMAL)
1322
				{
1323
					if ((t + 600) >= akt->start && (t + 600) <= akt->end)
1324
						strt = true;
1325
				}
1326
				else if (akt->status == NIGHT)
1327
				{
1328
					if ((t + 600) >= akt->end || t < (akt->end - 600))
1329
						strt = true;
1330
				}
1331
 
1332
				if (strt)
1333
				{
1334
					bool add = true;
1335
 
1336
					// Make sure there is no open start
1337
					tb = akt->hTable;
1338
 
1339
					while (tb)
1340
					{
1341
						if (tb->start)
1342
						{
1343
							add = false;
1344
							break;
1345
						}
1346
 
1347
						tb = tb->next;
1348
					}
1349
 
1350
					if (add)
1351
						tb = appendHTable(akt);
1352
 
1353
					tb->tempDifferOut = diffOut;
1354
					tb->tempDifferIn = diffIn;
1355
					tb->start = t;
1356
					return true;
1357
				}
1358
			}
1359
		}
8 andreas 1360
	}
9 andreas 1361
 
1362
	return false;
8 andreas 1363
}
53 andreas 1364
 
1365
time_t heating::calcTime(double soll, double ist)
1366
{
1367
double pwm, len;
1368
double correct= 100;
1369
double parallel = 110;
1370
 
1371
	pwm = (log((soll - ist) + correct * 0.001) + parallel * 0.01) * 100.0;
1372
	len = (10 * 60) * pwm / 100.0;
1373
	return time_t(len * 10.0);
1374
}
1375