home     Inhaltsverzeichnis
erste Version am 30.09.2017
letzte Änderung am 02.10.2017

Zirkulationspumpen-Steuerung - Seite 5


Firmware V1 - Fortsetzung

RTC-Modul

Was war das für ein fürchterlicher Zeitaufwand, diese blöden Funktionen für das RTC-Modul auf time_t umzustellen. Um time_t setzen zu können, braucht es tmElements_t. Und diese Datenstruktur dreht völlig frei, wenn der -1970 bei der Jahreszahl fehlt. Da wird dann nicht etwa nur ein falsches Jahr geliefert, sondern sämtliche Werte haben nix mehr mit den Eingangswerten zu tun. Wenn man dann den Fehler zuerst in der setRTC() statt in getRTC() sucht und aus GPIO14 als zusätzliches Problem nicht schnell genug Saft rauskommt, um damit das RTC-Modul mit +3.3V zu versorgen, dann geht praktisch nix und die grauen Haare sprießen nur so...
Das hier funktioniert jetzt (nach ca. vier Stunden Fehlersuche):
#include "rtc_eeprom.h"
#include "globals.h"


/* -------------------------------------------------------------------
* Initialisiert den ESP8266-12E zum Betrieb des RTC/EEPROM-Moduls.
*/
void RTC_EEPROM_init(void) {
pinMode(RTC_EEPROM_POWER_PIN, OUTPUT);
Wire.begin(RTC_EEPROM_SDA_PIN, RTC_EEPROM_SCL_PIN);
digitalWrite(RTC_EEPROM_POWER_PIN, LOW); // RTC aus
}

/* -------------------------------------------------------------------
* Kleine Helferchen für RTC-Funktionen.
*/
byte dec2bcd(byte val) { return((val/10*16)+(val%10)); }
byte bcd2dec(byte val) { return((val/16*10)+(val%16)); }

/* -------------------------------------------------------------------
* Liefert Datum und Uhrzeit von der RTC als time_t.
*/
time_t getRTC(void) {
tmElements_t tm;
time_t t;

digitalWrite(RTC_EEPROM_POWER_PIN, HIGH); delay(1);
Wire.beginTransmission(DS3231_ADR);
Wire.write(0); // Register(0) (Seconds)
Wire.endTransmission();
Wire.requestFrom(DS3231_ADR, 7); // 7 Byte holen
tm.Second=bcd2dec(Wire.read())&0x3F; // Seconds
tm.Minute=bcd2dec(Wire.read())&0x3F; // Minutes
tm.Hour =bcd2dec(Wire.read())&0x1F; // Hours
Wire.read(); // Day weglesen
tm.Day =bcd2dec(Wire.read())&0x1F; // Date
tm.Month =bcd2dec(Wire.read())&0x0F; // Month
tm.Year =bcd2dec(Wire.read())&0x3F; // Year
tm.Year +=2000-1970;
digitalWrite(RTC_EEPROM_POWER_PIN, LOW);
t=makeTime(tm); // time_t setzen
return(t);
}

/* -------------------------------------------------------------------
* Debug-Print der Zeit "t".
*/
void printRTC(time_t t) {
Serial.print(year(t)); Serial.print("."); Serial.print(month(t)); Serial.print("."); Serial.print(day(t)); Serial.print(" ");
Serial.print(hour(t)); Serial.print(":"); Serial.print(minute(t)); Serial.print(":"); Serial.print(second(t));
}

/* -------------------------------------------------------------------
* Stellt die RTC gemäß Parameter ein.
*/
void setRTC(byte YY, byte MM, byte DD, byte hh, byte mm, byte ss) {
digitalWrite(RTC_EEPROM_POWER_PIN, HIGH); delay(1);
Wire.beginTransmission(DS3231_ADR);
Wire.write(0);
Wire.write(dec2bcd(ss));
Wire.write(dec2bcd(mm));
Wire.write(dec2bcd(hh));
Wire.write(0);
Wire.write(dec2bcd(DD));
Wire.write(dec2bcd(MM));
Wire.write(dec2bcd(YY));
Wire.endTransmission();
digitalWrite(RTC_EEPROM_POWER_PIN, LOW);
}

/* -------------------------------------------------------------------
* Schreibt einen time_t in die RTC.
*/
void setRTC(time_t tm) {
setRTC(year(tm)-2000, month(tm), day(tm), hour(tm), minute(tm), second(tm));
}

/* -------------------------------------------------------------------
* Schreibt einen String mit Datum+Zeit in die RTC.
* Der String muss folgendes Format haben: YYYY.MM.DD hh:mm:ss
*/
void setRTC(char *datetime_str) {
// Einzelstrings aus Timestamp bauen
// 2015.10.20 19:48:08
// 01234567890123456789
// X X X X X
*(datetime_str+4)='\0';
*(datetime_str+7)='\0';
*(datetime_str+10)='\0';
*(datetime_str+13)='\0';
*(datetime_str+16)='\0';
setRTC(atoi(datetime_str)-2000, atoi(datetime_str+5), atoi(datetime_str+8),
atoi(datetime_str+11), atoi(datetime_str+14), atoi(datetime_str+17));
}

Die blau markierten Stellen waren es, die mich so aus dem Zeitplan gebracht haben.....damit sehe ich jetzt schwarz für meine Idee, die Schaltung heute noch im Heizungsraum installieren zu können.
So viel Ärger für etwas, das nur nach einem Stromausfall gebraucht wird. Und dann auch nur, wenn zusätzlich keine WLAN-Verbindung aufgebaut werden kann.

Immerhin ist die Hardware nun endgültig fertig. Das RTC-Modul sitzt auf der CPU-Platine (Vcc=GPIO14, SDA=GPIO4, SCL=GPIO5), die zwei LEDs stecken in der Gehäuse-Vorderseite, die Gehäuse-Hinterseite hat zwei Löcher zwecks Wand-Befestigung und sogar die Basis-Platine habe ich bereits am Gehäuse-Boden festgeschraubt. Es fehlen somit nur noch zwei Dübel samt Schrauben in der Wand im Heizungsraum, an denen das Gehäuse letztendlich hängen soll.

TODO: ich muss noch daran denken, bei Gelegenheit diese unsägliche Akku-Ladeschaltung vom RTC-Modul lahmzulegen.

Schalt-Zeiten-Bitmap

Heute fange ich mal mit der Zeiten-Bitmap an. Ein Bit alle fünf Minuten, über sieben Tage.
Somit 12 Bit pro Stunde und 24 Bit oder 3 Byte alle zwei Stunden. Die Bit-Verteilung in diesen zwei Stunden sieht so aus:
a0
a1
a2
a3
a4
a5
a6
a7
b0
b1
b2
b3
00:00
00:05
00:10
00:1500:2000:2500:3000:3500:4000:4500:5000:55
b4
b5
b6
b7
c0
c1
c2
c3
c4
c5
c6
c7
01:0001:0501:1001:1501:2001:2501:3001:3501:4001:4501:5001:55

Ein ganzer Tag belegt folglich 288 Bit oder 36 Byte. So:
Stunde
Byte
00:XX
00 + 01
01:XX
01 + 02
02:XX
03 + 04
03:XX
04 + 05
....

20:XX
30 + 31
21:XX
31 + 32
22:XX
33 + 34
23:XX
34 + 35

Um von einer Zeit d samt Wochentag auf das zu adressierende Byte zu kommen, berechnet sich zunächst die Adresse der Wochentags-Bitmap als:

Innerhalb der so identifizierten Tages-Bitmap ist nun die Adresse des Bytes für die Stunde zu ermitteln:
und
Das ergibt sowas hier:
hour
byte_adrbit_offset
00
0
0
01
1
4
02
3
0
03
4
4
....


20
30
0
21
31
4
22
33
0
23
34
4

Jetzt braucht es noch das Bit entsprechend der Minute.
Die Minute muss im 5'er Raster vorliegen. Das lässt sich erzwingen mit:
Dann muss sie nur noch durch 5 geteilt werden, um das Bit zu erhalten.

Die komplette Rechnung wäre folglich:
und
Allerdings liefert das Bit-Nummern jenseits der Byte-Grenze. Irgendwo fehlt da ja auch noch der bit_offset.
Wenn bit_num>7 ist, muss byte_adr um eins inkrementiert werden und von bit_num sind acht abzuziehen.
Das wird unschön!
Andersrum aufgezogen könnte man auch die Zeit erstmal nach Bit wandeln:
Daraus das Byte zu berechnen, ist einfach:
und der Rest ist dann das Bit im Byte:
Schnell mal testen:
for h in range(24):
  for m in range(0, 59, 5):
    bit_full_day=h*12+(m-m%5)/5
    byte_adr=bit_full_day/8
    bit_num=bit_full_day-byte_adr*8
    print "%02d:%02d - %03d, %02d.%d"%(h, m, bit_full_day, byte_adr, bit_num)

Liefert das gewünschte:
00:00 - 000, 00.0
00:05 - 001, 00.1
00:10 - 002, 00.2
00:15 - 003, 00.3
00:20 - 004, 00.4
00:25 - 005, 00.5
00:30 - 006, 00.6
00:35 - 007, 00.7
00:40 - 008, 01.0
00:45 - 009, 01.1
00:50 - 010, 01.2
00:55 - 011, 01.3
01:00 - 012, 01.4
01:05 - 013, 01.5
[.....]
22:50 - 274, 34.2
22:55 - 275, 34.3
23:00 - 276, 34.4
23:05 - 277, 34.5
23:10 - 278, 34.6
23:15 - 279, 34.7
23:20 - 280, 35.0
23:25 - 281, 35.1
23:30 - 282, 35.2
23:35 - 283, 35.3
23:40 - 284, 35.4
23:45 - 285, 35.5
23:50 - 286, 35.6
23:55 - 287, 35.7


Zur byte_adr muss final noch die bitmap_adr addiert werden.
Oder ich deklariere das Array einfach als:
Die Abfrage der Bitmap sähe in C etwa so aus:
int getPowerOnStateForTime(time_t t) {
  int bit_full_day, byte_adr, bit_num;

  bit_full_day=hour(t)*12+(minute(t)-minute(t)%5)/5;
  byte_adr=bit_full_day/8;
  bit_num=bit_full_day-byte_adr*8;
  return((power_on_times_g[weekday(t)-1][byte_adr]>>bit_num)&1);
}

Auf der Python-Seite braucht es primär die entsprechende Set-Funktion.
Oder gleich ein Script, das ich leicht in meinen SocketServer einbinden kann und das die Bitmap für die derzeitigen Schaltzeiten des Fritz!Powerline546E-Adapters liefert:
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import datetime
import sys

class PowerOnBitmap():
def __init__(self):
# self.power_on_times[7][36] anlegen und mit 0/AUS füllen
self.power_on_times=[[0 for x in range(36)] for y in range(7)]

# ###########################################################
# Python-Weekday kompatibel zum C-Weekday machen.
#
# datetime.weekday(): Monday is 0 and Sunday is 6
# Time.h : Day of the week (1-7), Sunday is day 1
# Py C
# Mo 0 -> 2
# Di 1 -> 3
# Mi 2 -> 4
# Do 3 -> 5
# Fr 4 -> 6
# Sa 5 -> 7
# So 6 -> 1
def __weekday_c(self, dt):
if dt.weekday()==6:
return(1)
return(dt.weekday()+2)

# ###########################################################
# bit_nr [0, ..., 7]
def __getBit(self, byte, bit_nr):
return((byte>>bit_nr)&1)

# ###########################################################
#
def getPowerOnStateForTime(self, dt):
bit_full_day=dt.hour*12+(dt.minute-dt.minute%5)/5
byte_adr=bit_full_day/8
bit_num=bit_full_day-byte_adr*8
return(self.__getBit(self.power_on_times[self.__weekday_c(dt)-1][byte_adr], bit_num))

# ###########################################################
#
def setPowerOnStateForDatetime(self, dt, state):
bit_full_day=dt.hour*12+(dt.minute-dt.minute%5)/5
byte_adr=bit_full_day/8
bit_num=bit_full_day-byte_adr*8
if state==0:
self.power_on_times[self.__weekday_c(dt)-1][byte_adr]&=((1<<bit_num)^0xff)
elif state==1:
self.power_on_times[self.__weekday_c(dt)-1][byte_adr]|=(1<<bit_num)

# ###########################################################
#
def setPowerOnStateForTime(self, weekday, hour, minute, state):
bit_full_day=hour*12+(minute-minute%5)/5
byte_adr=bit_full_day/8
bit_num=bit_full_day-byte_adr*8
if state==0:
self.power_on_times[weekday-1][byte_adr]&=((1<<bit_num)^0xff)
elif state==1:
self.power_on_times[weekday-1][byte_adr]|=(1<<bit_num)

# ###########################################################
#
def printBitmap(self):
print " ",
for i in range(0, 23, 2):
print "%02d:00 "%(i,),
print
for i in range(len(self.power_on_times)):
print "day=%d :"%(i,),
for j in range(len(self.power_on_times[i])):
print "%02x"%(self.power_on_times[i][j],),
print

# ###########################################################
#
def initDefault(self):
for wd in [2, 3, 4, 5, 6]: # Montag - Freitag
self.setPowerOnStateForTime(wd, 5, 45, 1)
self.setPowerOnStateForTime(wd, 5, 50, 1)
self.setPowerOnStateForTime(wd, 5, 55, 1)
self.setPowerOnStateForTime(wd, 6, 0, 1)
self.setPowerOnStateForTime(wd, 7, 0, 1)
self.setPowerOnStateForTime(wd, 8, 0, 1)
self.setPowerOnStateForTime(wd, 10, 0, 1)
self.setPowerOnStateForTime(wd, 13, 0, 1)
self.setPowerOnStateForTime(wd, 14, 0, 1)
self.setPowerOnStateForTime(wd, 18, 0, 1)
self.setPowerOnStateForTime(wd, 20, 0, 1)
self.setPowerOnStateForTime(wd, 21, 0, 1)
self.setPowerOnStateForTime(wd, 22, 0, 1)
self.setPowerOnStateForTime(wd, 22, 30, 1)
for wd in [1, 7]: # Sonntag, Samstag
self.setPowerOnStateForTime(wd, 6, 0, 1)
self.setPowerOnStateForTime(wd, 7, 0, 1)
self.setPowerOnStateForTime(wd, 8, 0, 1)
self.setPowerOnStateForTime(wd, 10, 0, 1)
self.setPowerOnStateForTime(wd, 13, 0, 1)
self.setPowerOnStateForTime(wd, 14, 0, 1)
self.setPowerOnStateForTime(wd, 18, 0, 1)
self.setPowerOnStateForTime(wd, 20, 0, 1)
self.setPowerOnStateForTime(wd, 21, 0, 1)
self.setPowerOnStateForTime(wd, 22, 0, 1)
self.setPowerOnStateForTime(wd, 22, 30, 1)

# ###########################################################
#
def getAsString(self):
ret_str=""
for wd in range(7):
for bp in range(36):
ret_str+=chr(self.power_on_times[wd][bp])
return(ret_str)


# ###########################################################
# Gibt einen Hexdump von "buf" in der Länge "lng" aus.
def hexdump(buf, lng):
p=0
while p<lng:
sys.stdout.write("%04X "%(p))
for i in range(min(16, (lng-p))):
sys.stdout.write("%02x "%ord(buf[p+i]))
sys.stdout.write(" ")
if (lng-p)<16:
sys.stdout.write(" "*(3*(16-(lng-p))))
for i in range(min(16, (lng-p))):
c=ord(buf[p+i])
if c>31 and c<128:
sys.stdout.write(chr(c))
else:
sys.stdout.write('.')
p+=16
print



if __name__=='__main__':
pob=PowerOnBitmap()
pob.initDefault()

pob.printBitmap()

print pob.getPowerOnStateForTime(datetime.datetime(2017, 10, 2, 6, 0))
print pob.getPowerOnStateForTime(datetime.datetime(2017, 10, 2, 12, 0))

print "---"
#for m in range(40, 60):
# print "%02d, %d"%(m, pob.getPowerOnStateForTime(datetime.datetime(2017, 10, 2, 5, m)))

bm=pob.getAsString()
hexdump(bm, len(bm))

Dessen Ausgabe ist das hier:
        00:00    02:00    04:00    06:00    08:00    10:00    12:00    14:00    16:00    18:00    20:00    22:00  
day=0 : 00 00 00 00 00 00 00 00 00 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=1 : 00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=2 : 00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=3 : 00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=4 : 00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=5 : 00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
day=6 : 00 00 00 00 00 00 00 00 00 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00
1
0
---
0000  00 00 00 00 00 00 00 00 00 01 10 00 01 00 00 01  ................
0010  00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10  ................
0020  00 41 00 00 00 00 00 00 00 00 00 00 e0 01 10 00  .A..............
0030  01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01  ................
0040  00 00 01 10 00 41 00 00 00 00 00 00 00 00 00 00  .....A..........
0050  e0 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00  ................
0060  00 00 00 01 00 00 01 10 00 41 00 00 00 00 00 00  .........A......
0070  00 00 00 00 e0 01 10 00 01 00 00 01 00 00 00 10  ................
0080  00 01 00 00 00 00 00 01 00 00 01 10 00 41 00 00  .............A..
0090  00 00 00 00 00 00 00 00 e0 01 10 00 01 00 00 01  ................
00A0  00 00 00 10 00 01 00 00 00 00 00 01 00 00 01 10  ................
00B0  00 41 00 00 00 00 00 00 00 00 00 00 e0 01 10 00  .A..............
00C0  01 00 00 01 00 00 00 10 00 01 00 00 00 00 00 01  ................
00D0  00 00 01 10 00 41 00 00 00 00 00 00 00 00 00 00  .....A..........
00E0  00 01 10 00 01 00 00 01 00 00 00 10 00 01 00 00  ................
00F0  00 00 00 01 00 00 01 10 00 41 00 00              .........A..


Damit hätte ich dann etwa die gleichen Einschaltzeiten wie jetzt. Die Ausschaltzeiten hingen zukünftig von der Temperatur am Rohr Z ab.
Die automatische Korrektur der Einschaltzeiten anhand der Messwert-Historie baue ich irgendwann später.

Das war wieder genug Text für eine Seite. Hier gehts zur Folgeseite.