Ring Video Doorbell

Begonnen von blueberry63, 23 August 2017, 11:06:28

Vorheriges Thema - Nächstes Thema

budy

#300
Hmm... na ja, das Problem ist aber dennoch deine Lib, denn die bricht ab und dann ist snapshot in der ring.py nicht mal initialisiert, weil ja die Lib entweder das Bild oder halt false zurückgeben müsste. Da musst du nochmal in die Zeile 416 schauen, wo ja die Änderung gewesen ist:

response = self._ring.query(url, method="POST", json=payload).json()

Ich könnte auch snapshot generell mit false vorbelegen, aber das würde dein Problem nicht lösen.

Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

JF Mennedy

OK dann schau ich mir das mal an...

Gesendet von meinem VOG-L29 mit Tapatalk


budy

Ich habe eben nochmal ein Update im Master gemacht, welches snapshot immer zuerst mit False initialisiert. Dadurch sollte das Skript nicht abbrechen, wenn die ring_doorbell lib nichts zurückliefert.

Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

OliS.

#303
Ich habe übrigens gerade eher zufällig festgestellt, dass mein Echo Show versucht, selbstständig - also ohne, dass ich ihn dazu auffordere - das Bild der Kamera anzuzeigen, sobald die Klingel betätigt wird. Er bricht dann zwar nach ein paar Sekunden erfolglos ab, aber das sieht für mich so aus, als wenn die Funktion nun endlich bald ins Haus stünde.

EDIT: Dass man für den Video-Download ein Abo braucht, weiß ich. Brauche ich das Abo auch für die Snapshots?

LG
Oli
FHEM in Debian VM auf DS720+, HMLAN und HMUARTLGW, RFXTRX, Conbee II, Homebridge, Alexa
Geräte: Homematic, Tradfri, Shelly, IT, ESA2000, VU+, Denon-AVR, Sonos, Fritz!Box, Harmony Hub, IP-Cams, Roborock, Automower

budy

Na ja, wenn man in FHEM einen SIP-Client hätte, den man einklinken könnte, dann würde das auch in FHEM gehen, denn die SIP-URI wird ja immer mitgeliefert - auch wenn man kein Abo hat. Dass ein Amazon-Gerät das versucht, ohne dass man da einen Skill für installiert hat, finde ich zwar schon frech, aber Ring gehört ja Amazon...

Ob man für die Snapshot-Funktion ein Abo braucht, weiß ich gar nicht, wenn es ja, dann sollte da in der Datei am Ende ja eine Fehlermeldung stehen... - auch wenn FHEM die als Bild nicht anzeigen kann.

Gruß,
Budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

OliS.

Zitat von: budy am 29 April 2020, 19:36:04
Na ja, wenn man in FHEM einen SIP-Client hätte, den man einklinken könnte, dann würde das auch in FHEM gehen, denn die SIP-URI wird ja immer mitgeliefert - auch wenn man kein Abo hat. Dass ein Amazon-Gerät das versucht, ohne dass man da einen Skill für installiert hat, finde ich zwar schon frech, aber Ring gehört ja Amazon...
Nee, da hast Du mich falsch verstanden. Ich nutze den Ring Skill auf dem Show. Nur war es bisher immer so, dass der Echo Show zwar gemeldet hat, wenn jemand geklingelt hat, man aber Alexa immer noch auffordern musste, das Bild der Kamera anzuzeigen. Nun ist es so (wie von vielen seit Ewigkeiten gewünscht), dass der Show automatisch das Bild der Kamera anzeigt, sobald jemand klingelt.

Zitat
Ob man für die Snapshot-Funktion ein Abo braucht, weiß ich gar nicht, wenn es ja, dann sollte da in der Datei am Ende ja eine Fehlermeldung stehen... - auch wenn FHEM die als Bild nicht anzeigen kann.

Im Log bekomme ich die Meldung
Snapshot: False

Also gehe ich davon aus, dass man wohl ein Abo braucht für den Snapshot. Schade eigentlich.

LG
Oli
FHEM in Debian VM auf DS720+, HMLAN und HMUARTLGW, RFXTRX, Conbee II, Homebridge, Alexa
Geräte: Homematic, Tradfri, Shelly, IT, ESA2000, VU+, Denon-AVR, Sonos, Fritz!Box, Harmony Hub, IP-Cams, Roborock, Automower

OliS.

Für den Snapshot ist offenbar ein Abo nötig. Ich habe mal testweise eines abgeschlossen und bekomme den Snap jetzt.

Kann es sei, dass dieser Snapshot aber nicht eine Aufnahme des jeweiligen Ereignisses (motion, ring) ist, sondern eher die Aufnahmen, die die Kamera periodisch zwischen den Ereignissen macht?

LG
Oli
FHEM in Debian VM auf DS720+, HMLAN und HMUARTLGW, RFXTRX, Conbee II, Homebridge, Alexa
Geräte: Homematic, Tradfri, Shelly, IT, ESA2000, VU+, Denon-AVR, Sonos, Fritz!Box, Harmony Hub, IP-Cams, Roborock, Automower

budy

#307
Die Motion-Videos werden ja sowieso immer runtergeladen, wenn sie verfügbar sind. Einen Snapshot kann man jederzeit von der API anfordern. Ich habe das nur erst mal so umgesetzt, dass das Skript das tut, wenn es die FHEM-Readings aktualisiert. Es läuft also unabhängig von den Motion- oder Ring-Ereignissen.

Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

OliS.

Zitat von: budy am 30 April 2020, 20:31:39Einen Snapshot kann man jederzeit von der API anfordern.

Das hört sich auf jeden Fall interessant an. Da die Ring-App ja unverständlicherweise keinen Snapshot mitschickt, sobald geklingelt wird oder eine Bewegung erkannt wird, wäre es doch schön, wenn man das mit FHEM umsetzen könnte. Pushover erlaubt ja mittlerweile den Versand von Anhängen.

LG
Oli
FHEM in Debian VM auf DS720+, HMLAN und HMUARTLGW, RFXTRX, Conbee II, Homebridge, Alexa
Geräte: Homematic, Tradfri, Shelly, IT, ESA2000, VU+, Denon-AVR, Sonos, Fritz!Box, Harmony Hub, IP-Cams, Roborock, Automower

budy

Na ja... den Snapshot von der API zu bekommen dauert auch ein paar Sekunden. Selbst wenn du den dann per Pushover sendest, ist der Mensch der geklingelt hat, ggf. schon wieder weg. Ich kann aber mal schauen, ob ich nicht auch noch Snaps für Ding- und Motion-Events einbaue, die könnte man dann "weiterverwenden".

Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

onkel-tobi

Zitat von: budy am 02 Mai 2020, 17:45:28
Ich kann aber mal schauen, ob ich nicht auch noch Snaps für Ding- und Motion-Events einbaue, die könnte man dann "weiterverwenden".

Das wäre cool.

Gruß & einen schönen Sonntag,
Tobi

JF Mennedy

#311
Snapshots gehen bei mir noch gar nicht... Im log steht no connection to ring api, continueing... Hatte die doorbot.py nochmal neu installiert und das Patch gemacht, aber irgendwo hakt es noch... Ich habe ein ring Abo, falls die Frage kommt... Schönes Restwochenende noch, Gruß Jan

Gesendet von meinem VOG-L29 mit Tapatalk

budy

Zitat von: onkel-tobi am 03 Mai 2020, 08:36:46
Das wäre cool.

Gruß & einen schönen Sonntag,
Tobi

Ja, wäre... ist es aber leider nicht. Wenn ein Motion- oder Ding-Event ausgelöst wurde kann ich über die Lib/API keine Snapshots holen. Schade... aber ich habe dafür mal ein Issue in Githup Repo der Lib aufgemacht. Mal sehen, ob das wirklich nicht geht, oder ob das ein Implementierungsfehler ist. Immerhin sender die API ja den SIP-Feed, den man sich holen könnte, es gibt also nicht unbedingt einen Grund einen Snapshot zu holen - zumindest könnte Ring so argumentieren...

Wenn sich da was tut, gibt's ein Update dazu.

Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

budy

Zitat von: JF Mennedy am 03 Mai 2020, 09:47:53
Snapshots gehen bei mir noch gar nicht... Im log steht no connection to ring api, continueing... Hatte die doorbot.py nochmal neu installiert und das Patch gemacht, aber irgendwo hakt es noch... Ich habe ein ring Abo, falls die Frage kommt... Schönes Restwochenende noch, Gruß Jan

Du müsstest noch mal die Ausgabe des Skripts posten... so kann ich nicht sagen, wo es haken könnte. Und poste auch mal den Code aus der doorbot.py, speziell die Funktion get_snapshot. Bei mir sieht die aktuell so aus:

    def get_snapshot(self, retries=3, delay=1):
        """Take a snapshot and download it"""
        url = SNAPSHOT_TIMESTAMP_ENDPOINT
        payload = {"doorbot_ids": [self._attrs.get("id")]}
        self._ring.query(url, method="POST", json=payload)
        request_time = time.time()
        for _ in range(retries):
            time.sleep(delay)
            response = self._ring.query(url, method="POST", json=payload).json()
            if response["timestamps"][0]["timestamp"] / 1000 > request_time:
                return self._ring.query(
                    SNAPSHOT_ENDPOINT.format(self._attrs.get("id"), raw=True)
                ).content
        return False


Gruß,
budy
Debian stretch, FHEM 5.9.
HM-CC-RT-DN, HM-ES-PMSw1-Pl, HM-LC-Dim1TPBU-FM, HMUARTLGW, HMLAN, HM-SEC-KEY, HM-SEC-RHS, HM-SEC-SC-2, HM-SEC-SCo, HM-SEC-SD-2, HM-OU-CFM-TW, div. HUEs, Wifilight, Ring Video Pro

JF Mennedy

Die Ausgabe der ring.py ist folgendes:

2020-05-11 17:33:39,161 - fhem_ring - INFO - Found 1 devices.
2020-05-11 17:33:39,341 - fhem_ring - INFO - Polling for events
2020-05-11 17:33:39,702 - fhem_ring - INFO - Updating device data for device 'Haustuer' in FHEM...
2020-05-11 17:33:40,966 - fhem_ring - INFO - Haustuer has no connection to ring API, continueing...
2020-05-11 17:33:40,966 - fhem_ring - INFO - Snapshot: True


Die Doorbot.py sieht flgenermassen aus:

# coding: utf-8
# vim:sw=4:ts=4:et:
"""Python Ring Doorbell wrapper."""
import logging
from datetime import datetime
import os
import time
import pytz


from ring_doorbell.generic import RingGeneric

from ring_doorbell.const import (
    DOORBELLS_ENDPOINT,
    DOORBELL_VOL_MIN,
    DOORBELL_VOL_MAX,
    DOORBELL_EXISTING_TYPE,
    DINGS_ENDPOINT,
    DOORBELL_KINDS,
    DOORBELL_2_KINDS,
    DOORBELL_PRO_KINDS,
    DOORBELL_ELITE_KINDS,
    FILE_EXISTS,
    LIVE_STREAMING_ENDPOINT,
    MSG_BOOLEAN_REQUIRED,
    MSG_EXISTING_TYPE,
    MSG_VOL_OUTBOUND,
    PEEPHOLE_CAM_KINDS,
    SNAPSHOT_ENDPOINT,
    SNAPSHOT_TIMESTAMP_ENDPOINT,
    URL_DOORBELL_HISTORY,
    URL_RECORDING,
    DEFAULT_VIDEO_DOWNLOAD_TIMEOUT,
    HEALTH_DOORBELL_ENDPOINT,
)

_LOGGER = logging.getLogger(__name__)


class RingDoorBell(RingGeneric):
    """Implementation for Ring Doorbell."""

    def __init__(self, ring, device_id, shared=False):
        super(RingDoorBell, self).__init__(ring, device_id)
        self.shared = shared

    @property
    def family(self):
        """Return Ring device family type."""
        return "authorized_doorbots" if self.shared else "doorbots"

    def update_health_data(self):
        """Update health attrs."""
        self._health_attrs = (
            self._ring.query(HEALTH_DOORBELL_ENDPOINT.format(self.id))
            .json()
            .get("device_health", {})
        )

    @property
    def model(self):
        """Return Ring device model name."""
        if self.kind in DOORBELL_KINDS:
            return "Doorbell"
        if self.kind in DOORBELL_2_KINDS:
            return "Doorbell 2"
        if self.kind in DOORBELL_PRO_KINDS:
            return "Doorbell Pro"
        if self.kind in DOORBELL_ELITE_KINDS:
            return "Doorbell Elite"
        if self.kind in PEEPHOLE_CAM_KINDS:
            return "Peephole Cam"
        return None

    def has_capability(self, capability):
        """Return if device has specific capability."""
        if capability == "battery":
            return self.kind in (DOORBELL_KINDS + DOORBELL_2_KINDS + PEEPHOLE_CAM_KINDS)
        if capability == "knock":
            return self.kind in PEEPHOLE_CAM_KINDS
        if capability == "volume":
            return True
        return False

    @property
    def battery_life(self):
        """Return battery life."""
        value = 0
        if "battery_life_2" in self._attrs:
            # Camera has two battery bays
            if self._attrs.get("battery_life") is not None:
                # Bay 1
                value += int(self._attrs.get("battery_life"))
            if self._attrs.get("battery_life_2") is not None:
                # Bay 2
                value += int(self._attrs.get("battery_life_2"))
            return value
        # Camera has a single battery bay
        # Latest stickup cam can be externally powered
        if self._attrs.get("battery_life") is not None:
            value = int(self._attrs.get("battery_life"))
            if value and value > 100:
                value = 100
        return value

    @property
    def existing_doorbell_type(self):
        """
        Return existing doorbell type.

        0: Mechanical
        1: Digital
        2: Not Present
        """
        try:
            return DOORBELL_EXISTING_TYPE[
                self._attrs.get("settings").get("chime_settings").get("type")
            ]
        except AttributeError:
            return None

    @existing_doorbell_type.setter
    def existing_doorbell_type(self, value):
        """
        Return existing doorbell type.

        0: Mechanical
        1: Digital
        2: Not Present
        """
        if value not in DOORBELL_EXISTING_TYPE.keys():
            _LOGGER.error("%s", MSG_EXISTING_TYPE)
            return False
        params = {
            "doorbot[description]": self.name,
            "doorbot[settings][chime_settings][type]": value,
        }
        if self.existing_doorbell_type:
            url = DOORBELLS_ENDPOINT.format(self.id)
            self._ring.query(url, extra_params=params, method="PUT")
            self._ring.update_devices()
            return True
        return None

    @property
    def existing_doorbell_type_enabled(self):
        """Return if existing doorbell type is enabled."""
        if self.existing_doorbell_type:
            if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[2]:
                return None
            return self._attrs.get("settings").get("chime_settings").get("enable")
        return False

    @existing_doorbell_type_enabled.setter
    def existing_doorbell_type_enabled(self, value):
        """Enable/disable the existing doorbell if Digital/Mechanical."""
        if self.existing_doorbell_type:

            if not isinstance(value, bool):
                _LOGGER.error("%s", MSG_BOOLEAN_REQUIRED)
                return None

            if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[2]:
                return None

            params = {
                "doorbot[description]": self.name,
                "doorbot[settings][chime_settings][enable]": value,
            }
            url = DOORBELLS_ENDPOINT.format(self.id)
            self._ring.query(url, extra_params=params, method="PUT")
            self._ring.update_devices()
            return True
        return False

    @property
    def existing_doorbell_type_duration(self):
        """Return duration for Digital chime."""
        if self.existing_doorbell_type:
            if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[1]:
                return self._attrs.get("settings").get("chime_settings").get("duration")
        return None

    @existing_doorbell_type_duration.setter
    def existing_doorbell_type_duration(self, value):
        """Set duration for Digital chime."""
        if self.existing_doorbell_type:

            if not (
                (isinstance(value, int))
                and (DOORBELL_VOL_MIN <= value <= DOORBELL_VOL_MAX)
            ):
                _LOGGER.error(
                    "%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN, DOORBELL_VOL_MAX)
                )
                return False

            if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[1]:
                params = {
                    "doorbot[description]": self.name,
                    "doorbot[settings][chime_settings][duration]": value,
                }
                url = DOORBELLS_ENDPOINT.format(self.id)
                self._ring.query(url, extra_params=params, method="PUT")
                self._ring.update_devices()
                return True
        return None

    def history(
        self,
        limit=30,
        timezone=None,
        kind=None,
        enforce_limit=False,
        older_than=None,
        retry=8,
    ):
        """
        Return history with datetime objects.

        :param limit: specify number of objects to be returned
        :param timezone: determine which timezone to convert data objects
        :param kind: filter by kind (ding, motion, on_demand)
        :param enforce_limit: when True, this will enforce the limit and kind
        :param older_than: return older objects than the passed event_id
        :param retry: determine the max number of attempts to archive the limit
        """
        queries = 0
        original_limit = limit

        # set cap for max queries
        if retry > 10:
            retry = 10

        while True:
            params = {"limit": str(limit)}
            if older_than:
                params["older_than"] = older_than

            url = URL_DOORBELL_HISTORY.format(self.id)
            response = self._ring.query(url, extra_params=params).json()

            # cherrypick only the selected kind events
            if kind:
                response = list(filter(lambda array: array["kind"] == kind, response))

            # convert for specific timezone
            utc = pytz.utc
            if timezone:
                mytz = pytz.timezone(timezone)

            for entry in response:
                dt_at = datetime.strptime(entry["created_at"], "%Y-%m-%dT%H:%M:%S.000Z")
                utc_dt = datetime(
                    dt_at.year,
                    dt_at.month,
                    dt_at.day,
                    dt_at.hour,
                    dt_at.minute,
                    dt_at.second,
                    tzinfo=utc,
                )
                if timezone:
                    tz_dt = utc_dt.astimezone(mytz)
                    entry["created_at"] = tz_dt
                else:
                    entry["created_at"] = utc_dt

            if enforce_limit:
                # return because already matched the number
                # of events by kind
                if len(response) >= original_limit:
                    return response[:original_limit]

                # ensure the loop will exit after max queries
                queries += 1
                if queries == retry:
                    _LOGGER.debug(
                        "Could not find total of %s of kind %s", original_limit, kind
                    )
                    break

                # ensure the kind objects returned to match limit
                limit = limit * 2

            else:
                break

        return response

    @property
    def last_recording_id(self):
        """Return the last recording ID."""
        try:
            return self.history(limit=1)[0]["id"]
        except (IndexError, TypeError):
            return None

    @property
    def live_streaming_json(self):
        """Return JSON for live streaming."""
        url = LIVE_STREAMING_ENDPOINT.format(self.id)
        req = self._ring.query(url, method="POST")
        if req and req.status_code == 204:
            url = DINGS_ENDPOINT
            try:
                return self._ring.query(url).json()[0]
            except (IndexError, TypeError):
                pass
        return None

    def recording_download(
        self,
        recording_id,
        filename=None,
        override=False,
        timeout=DEFAULT_VIDEO_DOWNLOAD_TIMEOUT,
    ):
        """Save a recording in MP4 format to a file or return raw."""
        if not self.has_subscription:
            msg = "Your Ring account does not have an active subscription."
            _LOGGER.warning(msg)
            return False

        url = URL_RECORDING.format(recording_id)
        try:
            # Video download needs a longer timeout to get the large video file
            req = self._ring.query(url, timeout=timeout)
            if req and req.status_code == 200:

                if filename:
                    if os.path.isfile(filename) and not override:
                        _LOGGER.error("%s", FILE_EXISTS.format(filename))
                        return False

                    with open(filename, "wb") as recording:
                        recording.write(req.content)
                        return True
                else:
                    return req.content
        except IOError as error:
            _LOGGER.error("%s", error)
            raise
        return False

    def recording_url(self, recording_id):
        """Return HTTPS recording URL."""
        if not self.has_subscription:
            msg = "Your Ring account does not have an active subscription."
            _LOGGER.warning(msg)
            return False

        url = URL_RECORDING.format(recording_id)
        req = self._ring.query(url)
        if req and req.status_code == 200:
            return req.url
        return False

    @property
    def subscribed(self):
        """Return if is online."""
        result = self._attrs.get("subscribed")
        if result is None:
            return False
        return True

    @property
    def subscribed_motion(self):
        """Return if is subscribed_motion."""
        result = self._attrs.get("subscribed_motions")
        if result is None:
            return False
        return True

    @property
    def has_subscription(self):
        """Return boolean if the account has subscription."""
        return self._attrs.get("features").get("show_recordings")

    @property
    def volume(self):
        """Return volume."""
        return self._attrs.get("settings").get("doorbell_volume")

    @volume.setter
    def volume(self, value):
        if not (
            (isinstance(value, int)) and (DOORBELL_VOL_MIN <= value <= DOORBELL_VOL_MAX)
        ):
            _LOGGER.error(
                "%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN, DOORBELL_VOL_MAX)
            )
            return False

        params = {
            "doorbot[description]": self.name,
            "doorbot[settings][doorbell_volume]": str(value),
        }
        url = DOORBELLS_ENDPOINT.format(self.id)
        self._ring.query(url, extra_params=params, method="PUT")
        self._ring.update_devices()
        return True

    @property
    def connection_status(self):
        """Return connection status."""
        return self._attrs.get("alerts").get("connection")

    def get_snapshot(self, retries=3, delay=1):
        """Take a snapshot and download it"""
        url = SNAPSHOT_TIMESTAMP_ENDPOINT
        payload = {"doorbot_ids": [self._attrs.get("id")]}
        self._ring.query(url, method="POST", json=payload)
        request_time = time.time()
        for _ in range(retries):
            time.sleep(delay)
            response = self._ring.query(url, method="POST", json=payload).json()
            if response["timestamps"][0]["timestamp"] / 1000 > request_time:
                return self._ring.query(
                    SNAPSHOT_ENDPOINT.format(self._attrs.get("id"), raw=True)
                ).content
        return False



Irgendwie sieht es so aus, dass sich die API nicht mit meiner Klingel verbinden kann...

Gruss Jan