RoomController mit Display für Schalterprogramm

Begonnen von ckaytwo, 02 August 2016, 22:19:44

Vorheriges Thema - Nächstes Thema

ckaytwo

Hallo zusammen,

erst einmal meinen herzlichen Dank an alle die, die Ihre Zeit für die Entwicklung und diejenigen, die immer so ausführliche Anleitungen und Hilfestellungen veröffentlichen. Ohne Euch hätte ich mein Projekt wohl nie zu Ende bekommen.
An das Programmieren bin ich nur durch dieses Hobby gekommen. Mein Code ist daher mit Sicherheit noch lange nicht Optimal. Aus diversen Gründen musste ich den Code in Fhem auch auf viel zu viele verschiedene Module ausweiten. Toll wäre natürlich alles in einem Modul, aber das kann ich halt nicht.
Vorschläge sind natürlich willkommen.

Da ich das ganze parallel zum Umbau unseren Hauses gemacht habe, konnte ich natürlich die Passenden Kabel ziehen. Ich bin bei meinen Recherchen aber auch auf einige Projekte gestoßen, die auf Kabel verzichten und die Kommunikation über WLAN abwickeln. (ESP8266 - den ich nicht ans laufen bekommen habe ;-()


Mein Projekt:
Unsichtbare Musik mit komfortabler Steuerung und Einstellung der Raumtemperatur.
- drahtgebunden
- Knöpfe sind ein Muss

Umsetzung:
- Deckenlautsprecher im Esszimmer in Einbauspots
- max2play auf Raspberry
- Oled Display von Adafruit und 6 Knöpfe in Blindabdeckung (Merten), Arduino Nano als Herzstück
- Kommunikation über RS232 und ECMD-Modul

Funktionen:
- Anzeige von Ist- und Solltemperatur
- Einstellen der Solltemperatur
- Ein-/Ausschalten des Radios
- Nächsten/Vorherigen Eintrag aus der Playlist auswählen (bei mir bisher Radiosender)
- Lautstärke beim Einschalten definiert (40%)
- Lautstärke einstellen.

Bedienung:
Power: Schaltet das Radio ein, Begrüßung erscheint und der laufende Titel wird regelmäßig aktualisiert angezeigt.
Menü im Radiomodus: Die Temperatur wird angezeigt und kann eingestellt werden.
Menü im Heizungsmodus: Der Titel wird wieder angezeigt
Up/Down: Je nach Modus Titelwechsel oder Temperatureinstellung. Die titelanzeige erfolgt dabei zeitverzögert. (sonnst wird immer der zuletzt gespielte angezeigt)
Vol+/-: Einstellung und Anzeige der Lautstärke. Wird länger nichts mehr gedrückt wird automatisch wieder zu der Titelanzeige umgeschaltet.

Arduino:
Da auch die Welt des Arduinos für mich neu ist, und ich daran verzweifelt bin mich in vorgefertigte Projekte einzuarbeiten, (firmata hat mich in den Wahnsinn getrieben) habe ich den Sketch von grund auf selber geschriben. War unterm Strich einfacher.
Für das Protokoll habe ich mich an dem ECMD-Protrokoll orientiert.

Der Arduino versteht bisher nur zwei Kommandos:

write xx yy z <ausgabe>
clear all

xx--> 2 Ziffern, in welche Zeile geschrieben werden soll
yy--> 2 Ziffern, wie weit eingerückt werden soll
z-> Schriftgröße


<ausgabe> --> Text der auf dem Display erscheint.


Hier der Scetch. Die Adafruit_GFX und die Adafruit_SSD Bibliothek muss natürlich vorher installiert werden.
Die Buttons senden nur "btn x"

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2
#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
//eingangkomandos
char cmd[125];
char cmd_text[125];
char cmd_clear[25];
// Zähler

int icmd = 0;
int itext = 0;
int iclear = 0;
// Statusvariablen
int write = 0;
int clear = 0;

//Tasten
int taster2 = 8;
int taster6 = 3;
int taster5 = 4;
int taster4 = 5;
int taster3 = 6;
int taster1 = 7;
int t1 = 0;
int t2 = 0;
int t3 = 0;
int t4 = 0;
int t5 = 0;
int t6 = 0;

int buttonTime= 400;

void setup()   {               
  Serial.begin(57600);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);

  display.setTextSize(3);
  display.setTextColor(WHITE); 
  display.setCursor(0,0);
  display.display();
  delay(3);
  display.clearDisplay();
 
  pinMode(taster1, INPUT);
  pinMode(taster2, INPUT);
  pinMode(taster3, INPUT);
  pinMode(taster4, INPUT);
  pinMode(taster5, INPUT);
  pinMode(taster6, INPUT);
 
  //debug
iclear = 0;
clear = 0;
write = 0;
/*Serial.println("Werte am Anfang");
Serial.print("itext: ");
Serial.println(itext);
Serial.print("icmd: ");
Serial.println(icmd);
Serial.print("clear: ");
Serial.println(clear);
Serial.print("cmd: ");
Serial.println(cmd );
Serial.print("cmd_text: ");
Serial.println(cmd_text);
Serial.print("cmd_clear: ");
Serial.println(cmd_clear);*/
  //ende debug
}
/*write line col size text*/

void loop() {

  t1 = digitalRead(taster1);
  t2 = digitalRead(taster2);
  t3 = digitalRead(taster3);
  t4 = digitalRead(taster4);
  t5 = digitalRead(taster5);
  t6 = digitalRead(taster6);
  if(t1==HIGH){Serial.println("01");delay(buttonTime);}
  if(t2==HIGH){Serial.println("02");delay(buttonTime);}
  if(t3==HIGH){Serial.println("03");delay(buttonTime);}
  if(t4==HIGH){Serial.println("04");delay(buttonTime);}
  if(t5==HIGH){Serial.println("05");delay(buttonTime);}
  if(t6==HIGH){Serial.println("06");delay(buttonTime);}
 
if (Serial.available()>0) {
char charIn=Serial.read();

if (charIn=='\n'&&icmd<125){    /* hier muss eine fehlerbehandlung für jeden befehl rein. ggf auch erst in unterprogramme*/
//debug
/*Serial.println("Werte bei Befehlsabschluss");
Serial.print("itext: ");
Serial.println(itext);
Serial.print("icmd: ");
Serial.println(icmd);
Serial.print("clear: ");
Serial.println(clear);
Serial.print("cmd: ");
Serial.println(cmd );
Serial.print("cmd_text: ");
Serial.println(cmd_text);
Serial.print("cmd_clear: ");
Serial.println(cmd_clear);
Serial.print("akteueller Befehltsbuffer: ");
Serial.println(cmd);*/
//ende debug
if (write == 1){
for (int i=5;i<=6;i++){ /* wenn stelle 6 oder 7 */
if (cmd[i]<48 && cmd[i]>57){ /* wenn keine Zahl */
//Serial.println("fehleingabe");
}
}
//Serial.println("write empfangen");

int row = ((cmd[5]-48)*10)+(cmd[6]-48);
int col = ((cmd[7]-48)*10)+(cmd[8]-48);
int size = cmd[9]-48;
display.setTextSize(size);
display.setCursor(col,row);
display.println(cmd_text);
display.display();

Serial.println("ok");

}
if (clear == 1){
if (cmd[5]>=48||cmd[5]<=57){
/*clear befehl für Zeile ausführen*/
}
if (strncmp(cmd_clear,"all",3)==0){
//Serial.println("clear_all empfangen");
/* clear_all befehl ausfürhen und alles zurückgeben */
display.clearDisplay();
display.display();
Serial.println("ok");
}
}


for (int i = 0; i < 25; i++){
cmd_clear[i]=0;
}
for (int i = 0; i < 125; i++){
cmd[i]=0;
cmd_text[i]=0;
}


itext = 0;
icmd = 0;
iclear = 0;
clear = 0;
write = 0;
//debug
/*Serial.println("Werte bei Ende");
Serial.print("itext: ");
Serial.println(itext);
Serial.print("icmd: ");
Serial.println(icmd);
Serial.print("clear: ");
Serial.println(clear);
Serial.print("cmd: ");
Serial.println(cmd );
Serial.print("cmd_text: ");
Serial.println(cmd_text);
Serial.print("cmd_clear: ");
Serial.println(cmd_clear);*/
//ende debug
}
if (charIn>=32&&icmd<125){   /* hier muss von einem korrekten befehl ausgegangen werden */

if(strncmp(cmd,"write",5)==0){          /* wenn befehl write */
/*Serial.print("Zeichennummer itext: ");
Serial.println(itext);*/
if(icmd>=10){
cmd_text[itext]=charIn;
itext++;
}
write = 1;

}
if (strncmp(cmd,"clear",5)==0&&charIn!='\ '){
/*Serial.print("Zeichennummer iclear: ");
Serial.println(iclear);*/
cmd_clear[iclear]=charIn;
clear = 1;
iclear++;
}
if (charIn !='\ '){cmd[icmd]=charIn; //komplette Zeichenkette ohne leerzeichen speichern
icmd++; //muss am ende bleiben
}
}
if(icmd==125){
display.clearDisplay();
display.display();

display.setTextSize(1);
display.setCursor(1,1);
display.println("Die Eingabe war zu lang.");
display.display();
for (int i = 0; i < 25; i++){
cmd_clear[i]=0;
}
for (int i = 0; i < 125; i++){
cmd[i]=0;
cmd_text[i]=0;
}


itext = 0;
icmd = 0;
iclear = 0;
clear = 0;
write = 0;
}
}
}


In Fhem muss das ECMD-Modul ans laufen gebracht werden.
define uart ECMD serial /dev/ttyUSB0@57600
attr uart classdefs radio=/opt/fhem/radio.classdef:oled=/opt/fhem/oled.classdef
attr uart logTraffic 5
attr uart requestSeparator �
attr uart room radio
attr uart timeout 1
attr uart verbose 5

Ich brauche zwei Devices.
Für die Anzeige auf dem Display
define radio.oled ECMDDevice oled
attr radio.oled IODev uart
attr radio.oled class oled
attr radio.oled room radio

und für die Knöpfe
define radio.btn ECMDDevice radio
attr radio.btn IODev uart
attr radio.btn class radio
attr radio.btn room radio


Die dazugehörige radio.classdef

reading btn match ".*"

Und die für die Anzeige

set write params line col size text
set write cmd {"write %line %col %size %text\n"}
set write expect {"ok.*"}
set clear params what
set clear cmd {"clear %what\n"}
set clear expect {"ok.*"}
set owntext params text
set owntext cmd{"text\r\n"}
set write expect {".*"}



Die Squeezebox von Max2play muss auch noch eingebunden werden.
define SqueezeBoxServer SB_SERVER 192.168.178.27
attr SqueezeBoxServer alivetimer 120
attr SqueezeBoxServer doalivecheck true
attr SqueezeBoxServer httpport 9000
attr SqueezeBoxServer maxcmdstack 200
attr SqueezeBoxServer maxfavorites 30
attr SqueezeBoxServer room radio
define sb.player1 SB_PLAYER b8:27:eb:8c:31:b6
attr sb.player1 IODev SqueezeBoxServer
attr sb.player1 amplifier play
attr sb.player1 coverartheight 50
attr sb.player1 coverartwidth 50
attr sb.player1 donotnotify true
attr sb.player1 event-on-update-reading power,volume
attr sb.player1 fadeinsecs 10
attr sb.player1 idismac true
attr sb.player1 room SB_PLAYER,radio
attr sb.player1 serverautoon true
attr sb.player1 ttslanguage de
attr sb.player1 ttslink http://translate.google.com/translate_tts?ie=UTF-8&tl=<LANG>&q=<TEXT>&client=tw-ob
attr sb.player1 volumeLimit 100
attr sb.player1 volumeStep 5
attr sb.player1 webCmd pause:next:prev:volumeDown:volumeUp


Ich brauche eine Menge an Dummys, Doifs und notifys...
attr ntf.radio.btn.new room radio
define dum.radio.menu dummy
attr dum.radio.menu room radio
attr dum.radio.menu setList radio heizung volume
attr dum.radio.menu webCmd radio:heizung:volume
define dum.heizung.soll dummy
attr dum.heizung.soll room radio
define ntf.radio.player1 notify sb.player1.* {\
if (($EVTPART0 eq "on") or ($EVTPART0 eq "off") ){playerState()}\
}
attr ntf.radio.player1 room radio
define ntf.radio.menu notify dum.radio.menu.* {radioMenu()}
attr ntf.radio.menu room radio
define OG.kir.RO.O CUL_HM 35F217
attr OG.kir.RO.O IODev hmLan
attr OG.kir.RO.O autoReadReg 4_reqStatus
attr OG.kir.RO.O expert 2_raw
attr OG.kir.RO.O firmware 2.8
attr OG.kir.RO.O model HM-LC-BL1-FM
attr OG.kir.RO.O peerIDs 00000000,
attr OG.kir.RO.O room CUL_HM,Kind2
attr OG.kir.RO.O serialNr MEQ0652616
attr OG.kir.RO.O subType blindActuator
attr OG.kir.RO.O webCmd statusRequest:toggleDir:on:off:up:down:stop
define FileLog_OG.kir.RO.O FileLog ./log/OG.kir.RO.O-%Y.log OG.kir.RO.O
attr FileLog_OG.kir.RO.O logtype text
attr FileLog_OG.kir.RO.O room CUL_HM
define di.radio.display.volume DOIF ([+1])({radioDisplayVolume()},set dum.radio.menu radio)
attr di.radio.display.volume do resetwait
attr di.radio.display.volume room radio
attr di.radio.display.volume wait 1,3
define di.radio.display.temp DOIF ([+15])({radioDisplayTemp()})
attr di.radio.display.temp do always
attr di.radio.display.temp room radio
define di.radio.display.play DOIF ([+10])({radioDisplayPlay()})
attr di.radio.display.play do always
attr di.radio.display.play room radio
attr di.radio.display.play wait 1



Nur damit das Display schön angesteuert werden kann. Das geschieht dann aber über die myUtils

sub playerState(){Log 1, "player State";
my $playerState = ReadingsVal("sb.player1","power","off");
if($playerState eq "on"){
Log 1, "sub playerState --> dum.radio:heizung";
fhem "set dum.radio.menu heizung"
};
if($playerState eq "off"){
Log 1, "sub playerState --> dum.radio:radio";
fhem "set dum.radio.menu radio"
};
};
#
sub radioMenu(){Log 1, "sub radioMenu";
my $dumState = ReadingsVal("dum.radio.menu","state","heizung");

if($dumState eq "radio"){
Log 1, "sub radioMenu hat Status:Radio erkannt";
fhem "set di.radio.display.play initialize;
set di.radio.display.volume disable;
set di.radio.display.temp disable";
};
if($dumState eq "heizung"){
Log 1, "sub radioMenu hat Status:heizung erkannt";
fhem "set di.radio.display.play disable;
set di.radio.display.volume disable;
set di.radio.display.temp initialize";
};
if($dumState eq "volume"){
Log 1, "sub radioMenu hat Status:volume erkannt";
fhem "set di.radio.display.play disable;
set di.radio.display.volume initialize;
set di.radio.display.temp disable";
};
};
#
sub radioDisplayPlay(){
my $sender = ReadingsVal("sb.player1","currentArtist"," ");
$sender =~ tr#\x20#_#;
Log 1, $sender;
fhem "set radio.oled clear all;set radio.oled write 01 11 1 $sender";
};
#
sub radioDisplayTemp(){
my $tempSoll = ReadingsVal("dum.heizung.soll","state","18");
my $tempIst = ReadingsVal("EG.fl.TF","temperature","18");
Log 1, "Heizung sollwert $tempSoll";
fhem "set radio.oled clear all;
set radio.oled write 01 01 1 Temp:\\x20Ist\\x20\\x20Soll;
set radio.oled write 15 20 2 $tempIst\\x20$tempSoll";
};
#
sub radioDisplayVolume(){
my $volume = ReadingsVal("sb.player1","volume","40");
my $dumRadio = ReadingsVal("dum.radio.menu","state","heizung");
fhem "set radio.oled clear all;set radio.oled write 01 01 2 Volume\\x20$volume";
};
#
sub radioBtn01(){Log 1, "button 1 wurde gedrückt";
my $playerState = ReadingsVal("sb.player1","power","off");
if($playerState eq "off"){fhem "set sb.player1 on;set sb.player1 volume 40";radioClear();fhem"set radio.oled write 01 01 1 Hallo"};
if($playerState eq "on"){fhem "set sb.player1 off";radioClear();fhem"set radio.oled write 01 01 1 Good\\x20n8"};
};

sub radioBtn02(){Log 1, "button 2 gedrückt";
my $dumRadio = ReadingsVal("dum.radio.menu","state","heizung");
my $tempSoll = ReadingsVal("dum.heizung.soll","state","18");
my $tempIst = ReadingsVal("EG.fl.TF","temperature","17");
if($dumRadio eq "radio"){fhem "set sb.player1 next"};
if($dumRadio eq "heizung"){$tempSoll ++;fhem"set dum.heizung.soll $tempSoll"};
};
sub radioBtn03(){Log 1, "button 3 gedrückt";
fhem "set dum.radio.menu volume;set sb.player1 volumeUp";
};
sub radioBtn04(){Log 1, "button 4 gedrückt";
my $dumRadio = ReadingsVal("dum.radio.menu","state","heizung");
if ($dumRadio eq "heizung"){fhem "set dum.radio.menu radio";radioDisplayPlay()};
if ($dumRadio eq "radio"){fhem "set dum.radio.menu heizung";radioDisplayTemp()};
};
sub radioBtn05(){Log 1, "button 5 gedrückt";
my $dumRadio = ReadingsVal("dum.radio.menu","state","heizung");
my $tempSoll = ReadingsVal("dum.heizung.soll","state","18");
my $tempIst = ReadingsVal("EG.fl.TF","temperature","17");
if($dumRadio eq "radio"){fhem "set sb.player1 prev"};
if($dumRadio eq "heizung"){$tempSoll --;fhem"set dum.heizung.soll $tempSoll"};
};
sub radioBtn06(){Log 1, "button 6 gedrückt";
fhem "set dum.radio.menu volume;set sb.player1 volumeDown";
};
sub radioClear(){fhem "set radio.oled clear all;"}



Falls sich wirklich jemand die Mühe macht das mal auszuprobieren würde ich natürlich gerne wissen obs funktioniert... ;-)

Gruß

Christian