HomeBrewWired - Das Tutorial

Begonnen von Thorsten Pferdekaemper, 01 Dezember 2016, 22:00:12

Vorheriges Thema - Nächstes Thema

Thorsten Pferdekaemper

Folge 13 - Die verflixte Dreizehn

Eigentlich (blödes Wort, ich weiß...) wollte ich in dieser Folge zeigen, wie man einen RGB-Kanal zusammenbastelt, der drei Werte hat (rot, grün, blau). Im Prinzip funktioniert das auch, aber leider bin ich dabei auf ein paar Fehlerchen in der FHEM-Integration gestoßen. Das bedeutet, dass es eben nicht richtig funktioniert.
Die Fehler sind bisher nicht aufgefallen, da es bisher kein (Standard-)Gerät gibt, das so etwas macht. Möglicherweise funktioniert das in der CCU auch nicht. Also lassen wir das erst einmal. Das, was ich zeigen wollte, geht auch mit einem Wert pro Kanal. Allerdings muss ich dazu wieder alles umbauen.

Bis zum nächsten Mal,
   Thorsten
FUIP

Thorsten Pferdekaemper

Folge 14 - Dimmer und eigene Kanal-Implementierungen

Zwar sind Schalter und Taster wahrscheinlich das Wichtigste bei der Automatisierung, aber das gibt's ja einfach so zu kaufen. Jetzt zeige ich, wie man einem Kanal eigene Funktionalität beibringen kann.
Das folgende Gerät hat wie schon vorher einen Schaltausgang und zwei Taster-Eingänge. Dazwischen sind allerdings noch drei Dimmer.
Hier ist erst einmal die Gerätebeschreibungsdatei:


<?xml version="1.0"?>
<device eep_size="1024" version="01">
<supported_types>
<type priority="2" id="HBW-TUTORIAL" name="Das Geraet zum HBWired-Tutorial">
<parameter const_value="0xAB" size="1" index="0"/>
<parameter const_value="0" size="1" index="1"/>
</type>
</supported_types>

<paramset id="HBW-TUTORIAL_dev_master" type="MASTER">
<parameter id="LOGGING_TIME">
<logical type="float" unit="s" default="5.0" max="25.5" min="0.1"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="0x0001"/>
</physical>
<conversion type="float_integer_scale" offset="0.0" factor="10"/>
</parameter>
<parameter id="CENTRAL_ADDRESS" hidden="true">
<logical type="integer"/>
<physical size="4" type="integer" interface="eeprom">
<address index="0x0002"/>
</physical>
</parameter>
<enforce id="CENTRAL_ADDRESS" value="1"/>
</paramset>

<frames>
<frame id="LEVEL_SET" type="#x" channel_field="10" direction="to_device">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="LEVEL_GET" type="#S" channel_field="10" direction="to_device"/>
<frame id="INFO_LEVEL" type="#i" channel_field="10" direction="from_device" event="true">
<parameter size="1.0" index="11.0" type="integer" param="LEVEL"/>
</frame>
<frame id="KEY_EVENT_SHORT" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="0" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
<frame id="KEY_EVENT_LONG" type="#K" channel_field="10" direction="from_device" event="true">
<parameter const_value="1" size="0.1" index="12.0" type="integer"/>
<parameter size="0.6" index="12.2" type="integer" param="COUNTER"/>
</frame>
</frames>

<channels>
<channel index="0" type="MAINTENANCE" count="1" class="maintenance" ui_flags="internal">
</channel>
<channel index="1" type="SWITCH" count="1" physical_index_offset="-1">
      <paramset id="hmw_switch_ch_master" type="MASTER" address_step="2" address_start="0x06">
<parameter id="LOGGING">
<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>
<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>
</parameter>
</paramset>
<paramset id="hmw_switch_ch_values" type="VALUES">
<parameter id="STATE" operations="read,write,event" control="SWITCH.STATE">
<logical type="boolean" default="false"/>
<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>
<conversion type="boolean_integer" true="200" false="0" threshold="1"/>
</parameter>
</paramset>
</channel>

<channel index="2" type="DIMMER" count="3" physical_index_offset="-1">
      <paramset id="hmw_dim_ch_master" type="MASTER" address_step="2" address_start="0x08">
<parameter id="LOGGING">
<logical type="option">
<option id="OFF"/>
<option id="ON" default="true"/>
</logical>
<physical size="0.1" type="integer" interface="eeprom">
<address index="+0"/>
</physical>
</parameter>
</paramset>
<paramset id="hmw_dim_ch_values" type="VALUES">
<parameter id="LEVEL" operations="read,write,event" control="DIMMER.LEVEL">
<logical type="float" unit="100%" default="0.0" max="1.0" min="0.0"/>
<physical type="integer" interface="command" value_id="LEVEL">
<set request="LEVEL_SET"/>
<get request="LEVEL_GET" response="INFO_LEVEL"/>
<event frame="INFO_LEVEL"/>
</physical>
<conversion type="float_integer_scale" factor="200"/>
</parameter>
</paramset>
</channel>

<channel index="5" type="KEY" count="2" physical_index_offset="-1">
<paramset id="hmw_input_ch_master" type="MASTER" address_step="2" address_start="0x0E">
<parameter id="LONG_PRESS_TIME">
<logical type="float" unit="s" default="1.0" max="5.0" min="0.4"/>
<physical size="1.0" type="integer" interface="eeprom">
<address index="+1"/>
</physical>
<conversion type="float_integer_scale" factor="10"/>
<conversion type="integer_integer_map">
<value_map to_device="false" from_device="true" parameter_value="10" device_value="0xff"/>
</conversion>
</parameter>
</paramset>

<paramset id="hmw_input_ch_values" type="VALUES">
<parameter id="PRESS_SHORT" operations="event,write" loopback="true" control="BUTTON.SHORT">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_SHORT"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
<parameter id="PRESS_LONG" operations="event,write" loopback="true" control="BUTTON.LONG">
<logical type="action"/>
<physical type="integer" interface="command" value_id="COUNTER">
<event frame="KEY_EVENT_LONG"/>
</physical>
<conversion type="action_key_counter" counter_size="6" sim_counter="SIM_COUNTER"/>
</parameter>
</paramset>
</channel>

</channels>

</device>

Neu daran sind nur die drei Kanäle mit type="DIMMER". Möglicherweise fällt hier auf, dass der Wert von 0 bis 1 geht und in Prozent angegeben wird. Außerdem ist die Konversion nach "physical" eine Multiplikation mit 200. Das ist vielleicht etwas unschön, da man im Arduino lieber 0 bis 255 hätte und nicht 0 bis 200. Das ganze liegt daran, dass die Original-HMW-Dimmer so arbeiten und da noch ein paar Sachen in FHEM hart codiert sind. Außerdem habe ich keine Ahnung, ob es die CCU auch anders könnte.

Der zugehörige Arduino-Code sieht so aus:


#define HMW_DEVICETYPE 0xAB

#define HARDWARE_VERSION 0x01
#define FIRMWARE_VERSION 0x0100

#include "HBWSoftwareSerial.h"
#include "HBWired.h"
#include "HBWSwitch.h" 
#include "HBWKey.h"

#define RS485_RXD 4   // Receive-Pin
#define RS485_TXD 2   // Transmit-Pin
#define RS485_TXEN 3  // Transmit-Enable


// Definition of Dimmer channel

class DimmerChannel : public HBWChannel {
  public:
    DimmerChannel(uint8_t _pin, hbw_config_switch* _config);
    virtual uint8_t get(uint8_t* data);   
    virtual void loop(HBWDevice*, uint8_t channel);   
    virtual void set(HBWDevice*, uint8_t length, uint8_t const * const data);
  private:
    uint8_t pin;  // output pin, should be a PWM pin
    uint8_t current; // current value
    hbw_config_switch* config; // logging
    uint32_t lastFeedbackTime;  // when did we send the last feedback?
    uint16_t nextFeedbackDelay; // 0 -> no feedback pending
};


DimmerChannel::DimmerChannel(uint8_t _pin, hbw_config_switch* _config) {
    pin = _pin;
    config = _config;
    nextFeedbackDelay = 0;
    lastFeedbackTime = 0;
    // Pin auf OUTPUT
    // ...und auf LOW (also 0) setzen
    digitalWrite(pin,LOW);
    pinMode(pin,OUTPUT);
    current = 0;
};


void DimmerChannel::set(HBWDevice* device, uint8_t length, uint8_t const * const data) {
  if(length < 1) return;
  current = data[0];
  analogWrite(pin, (uint8_t)((uint16_t)(current) * 255 / 100));
  // Logging
  if(!nextFeedbackDelay && config->logging) {
      lastFeedbackTime = millis();
      nextFeedbackDelay = device->getLoggingTime() * 100;
  }
};


uint8_t DimmerChannel::get(uint8_t* data) {
  data[0] = current;
  return 1;
};


void DimmerChannel::loop(HBWDevice* device, uint8_t channel) {
  // feedback trigger set?
    if(!nextFeedbackDelay) return;
    unsigned long now = millis();
    if(now - lastFeedbackTime < nextFeedbackDelay) return;
    lastFeedbackTime = now;  // at least last time of trying
    // sendInfoMessage returns 0 on success, 1 if bus busy, 2 if failed
  // we know that the level has only 1 byte here
    uint8_t data[1];
    get(data); 
    uint8_t errcode = device->sendInfoMessage(channel, 1, data);   
    if(errcode == 1) {  // bus busy
    // try again later, but insert a small delay
      nextFeedbackDelay = 250;
    }else{
      nextFeedbackDelay = 0;
    }
}

// END Dimmer channel


struct hbw_config {
  uint8_t  logging_time;     // 0x0001
  uint32_t central_address;  // 0x0002 - 0x0005
  // NEU: Mehrere "switches"
  hbw_config_switch switchcfg[4]; 
  hbw_config_key keycfg[2];       
} config;


HBWSoftwareSerial rs485(RS485_RXD, RS485_TXD);
HBWDevice* device = NULL;

HBWChannel* channels[6];

void setup()
{
  Serial.begin(19200);
  rs485.begin();

  // NEU: Definition mehrerer Kanaele pro Typ
  //      (das geht natuerlich auch eleganter)
  channels[0] = new HBWSwitch(13,&(config.switchcfg[0]));
  channels[1] = new DimmerChannel(11, &(config.switchcfg[1]));
  channels[2] = new DimmerChannel(10, &(config.switchcfg[2]));
  channels[3] = new DimmerChannel(9, &(config.switchcfg[3]));
  channels[4] = new HBWKey(7,&(config.keycfg[0]));
  channels[5] = new HBWKey(8,&(config.keycfg[1]));

  device = new HBWDevice(HMW_DEVICETYPE, HARDWARE_VERSION, FIRMWARE_VERSION,
                         &rs485,RS485_TXEN,sizeof(config),&config,
                         6,channels,
                         &Serial,
                         NULL, NULL);
  hbwdebug(F("B: 2A\n"));
}


void loop()
{
  // call the HBW loop
  device->loop();
};

Das wesentliche Neue ist hier die Definition der Klasse DimmerChannel. Diese ist abgeleitet von HBWChannel, die Basisklasse von allen Homebrew-Kanälen sein muss. HBWChannel ist nicht abstrakt, man muss also nicht alle drei Methoden (set, get, loop) implementieren. Meist ist es jedoch sinnvoll.
Wie man in der Funktion setup sieht, schaltet Kanal 1 (also 0 im Sketch) wie üblich die interne LED am Pin 13. Die drei Dimmer-Kanäle hängen an 11, 10 und 9, die per PWM angesteuert werden. Die Taster hängen immer noch an 7 und 8.

Wie man den Sketch auf den Arduino bekommt und was mit der XML-Datei zu tun ist sollte inzwischen klar sein. Wenn alles richtig läuft, dann sollte das ganze ungefähr so aussehen wie auf den angehängten Screenshots und man sollte ggf. an Pins 9, 10 und 11 angeschlossene LEDs dimmen können.

So, das ist jetzt erstmal die letzte Folge des Tutorials. Die Library kann zwar auch noch direkte Verknüpfungen (Peerings), aber ich will erst einmal abwarten, bis jemand überhaupt so weit kommt und dann noch Interesse hat. Sagt mir einfach Bescheid.

Gruß,
   Thorsten
FUIP