Aus einem Script mit einem Webserver kommunizieren ...

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: der Fehler ist in Zeile 51. Es sollte doch das gleiche aus der Liste entfernt werden wie in Zeile 41 hinzugefügt wird ;-) Das except in Zeile 48 ist nicht so toll, da das Abbrechen des Task auch über Exceptions gelöst ist.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Danke :)
Das hat mir schon einmal weiter geholfen.
Der entsprechende Code-Abschnitt sieht jetzt so aus:

Code: Alles auswählen

@asyncio.coroutine
def temperature_loop(websocket):
    loop = asyncio.get_event_loop()
    logger.debug('websockets ... asyncio.get_event_loop')

    temperature_changed = Queue()
    logger.debug('websockets ... Queue')

    def changed(temp):
        loop.call_soon_threadsafe(temperature_changed.put_nowait, temp)
        logger.debug('websockets ... call_soon_threadsafe')

    try:
        temperature_consumers.append(changed)
        logger.debug('websockets ... temperature_consumers.append')
        while True:
            temp = yield from temperature_changed.get()
            logger.debug('websockets ... yield from temperature_changed.get')
            yield from websocket.send('Temperature: %f' % temp)
            logger.debug('websockets ... yield from websocket.send')
    finally:
        temperature_consumers.remove(changed)
        logger.debug('websockets ... temperature_consumers.remove')
Allerdings bekomme ich immer noch einen Fehler in den Logs:

Code: Alles auswählen

2014-12-13 19:06:21 : System startet
2014-12-13 19:06:21 : Tinkerforge ... Bricklets installiert
2014-12-13 19:06:21 : Tinkerforge ... Verbindung hergestellt
2014-12-13 19:06:21 : Tinkerforge ... Callbacks konfiguriert
2014-12-13 19:06:21 : Tinkerforge ... Callbacks gestartet
2014-12-13 19:06:21 : Tinkerforge ... System online
2014-12-13 19:06:21 : websockets ... set up websockets-server
2014-12-13 19:06:21 : websockets ... System online
2014-12-13 19:06:25 : websockets ... asyncio.async
2014-12-13 19:06:25 : websockets ... asyncio.get_event_loop
2014-12-13 19:06:25 : websockets ... Queue
2014-12-13 19:06:25 : websockets ... temperature_consumers.append
2014-12-13 19:06:29 : Tinkerforge ... Temperaturänderung -> 22.180000
2014-12-13 19:06:29 : websockets ... call_soon_threadsafe
2014-12-13 19:06:29 : websockets ... Temperaturänderung -> Warteschlange
2014-12-13 19:06:29 : websockets ... yield from temperature_changed.get
2014-12-13 19:06:29 : websockets ... yield from websocket.send
Fatal write error on socket transport
protocol: <websockets.server.WebSocketServerProtocol object at 0x02C77AF0>
transport: <asyncio.selector_events._SelectorSocketTransport object at 0x022A6350>
Traceback (most recent call last):
  File "C:\Python34\lib\asyncio\selector_events.py", line 502, in write
    n = self._sock.send(data)
ConnectionAbortedError: [WinError 10053] Eine bestehende Verbindung wurde softwaregesteuert
durch den Hostcomputer abgebrochen
2014-12-13 19:06:40 : websockets ... task.cancel
2014-12-13 19:06:40 : websockets ... temperature_consumers.remove
2014-12-13 19:06:48 : Tinkerforge ... Temperaturänderung -> 22.250000
2014-12-13 19:06:58 : Tinkerforge ... Temperaturänderung -> 22.310000
2014-12-13 19:07:03 : Tinkerforge ... Temperaturänderung -> 22.250000
Diesen Fehler würde ich gerne noch wegbekommen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: der Fehler ist wahrscheinlich ein Protokoll-Fehler. Wenn eine Verbindung geschlossen wird, wird ein Close-Frame Frame(fin=True, opcode=8, data=b'\x03\xe9') vom Browser zum Server geschickt, der Antwortet auch mit einem Close-Frame Frame(fin=True, opcode=8, data=b'\x03\xe9'). Schließt der Browser den Socket aber zu schnell, kann dieser Close-Frame nicht mehr geschrieben werden. Mit Chrome unter Mac tritt dieser Fehler nicht auf, aber wer weiß welchen Browser Du da unter Windows verwendest.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

aber wer weiß welchen Browser Du da unter Windows verwendest.
Chrome :wink:
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Also ich würde vermutlich das anders angehen: zwei unabhängige Dienste.
Ein periodisch aufgefufenes Skript welches die aktuelle Temperatur mit Zeitstempel in eine Datenbank schreibt. Ein webserver-app die auf die selbe datenenbank zugreift.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@jens: und eine webserver-app, die periodisch die Datenbank abfragt? Wenn alte Temperaturen nicht mehr gebraucht werden, ist doch eine Datenbank völlig unnötig.

@der_Mausbiber: dann scheinen sich die Windows-Sockets irgendwo zu verschlucken. Im Grunde kann der Fehler ignoriert werden, dazu müßtest Du die entsprechende Stelle im websockets-Modul ändern.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

@jens
Die Daten speichere ich schon zu statistischen Zwecken zusätzlich in einer Datenbank.
Allerdings möchte ich den Netzwerkverkehr aufs nötigste beschränken und so wird nur dann ein Wert übertragen wenn sich dieser auch geändert hat.
Außerdem möchte ich noch andere Sensoren darüber ansteuern, bei welchem die Datenabfrage über eine Datenbank nicht mehr so perfekt passen würde.


@sirius3
Danke, dann werde ich diesen Fehler nicht weiter beachten.
Ich versuche das Skript jetzt zu verstehen und frage dann hier nochmal kurz nach ob ich auch alle srichtig verstanden habe wenn es in Ordnung ist.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

der_Mausbiber hat geschrieben:Die Daten speichere ich schon zu statistischen Zwecken zusätzlich in einer Datenbank.
Das dachte ich mir. So ein Temperatur-Verlauf ist ja naheliegend... Also brauchst du so oder so alle Daten in einer Datenbank...
der_Mausbiber hat geschrieben:Allerdings möchte ich den Netzwerkverkehr aufs nötigste beschränken und so wird nur dann ein Wert übertragen wenn sich dieser auch geändert hat.
Client sendet seinen Zeitstempel oder den letzten Temperatur-Wert und dann weiß der Server ob sich die Temperatur geändert hat oder nicht...

Warum eigentlich "Netzwerkverkehr aufs nötigste beschränken" ? Was ist das Ziel? Handy? Dann wäre eine App das passendere mit Push-Benachrichtigung...
der_Mausbiber hat geschrieben:Außerdem möchte ich noch andere Sensoren darüber ansteuern, bei welchem die Datenabfrage über eine Datenbank nicht mehr so perfekt passen würde.
Warum nicht?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

So, hat ein wenig länger gedauert da ich anderweitig beschäftigt war.
Das dachte ich mir. So ein Temperatur-Verlauf ist ja naheliegend... Also brauchst du so oder so alle Daten in einer Datenbank...
Ja und nein, für statische Zwecke nehme ich alle 3 Minuten die Temperatur und speicher sie in einer Datenbank, somit kann ich dann leicht Durchschnitt, Höchstwerte, etc... ausgeben.
Die Anzeige in der WebApp (will es mal so nennen) soll mir aber die aktuelle Temperatur anzeigen, also auch wenn sich diese innerhalb von 3 Minuten mehrmals ändert.
Deswegen schicke ich diese Daten über websockets.

Warum websockets? Da ich von der WebApp auch Schaltvorgänge bedienen will brauche ich sowieso ein Verbindung zum Script.
Z.Bsp. habe ich auf der Website auch Schalter zum schalten von Steckdosen.
Diesen Befehl empfängt das Script und leitet dann den Schaltvorgang ein.

Abgesehen davon verstehe ich nicht wie ich das gleiche Verhalten mit einer Datenbank nachbauen soll.
Klar, sowie die Temperatur sich ändert kann ich diese mit einem Timestamp in der Datenbank speichern. Aber woher weiß der Client wenn sich der Wert geändert hat? Dazu müsste die Datenbank eine Art Callback auslösen und ich weiß nicht wie das gehen soll.
Was ist das Ziel? Handy? Dann wäre eine App das passendere mit Push-Benachrichtigung...
Ziel ist alles weitestgehend zu automatisieren und dazu eine ansprechende Steuerung über Handy, Tablet und Desktop-PC zu realisieren.
Die Hardware-Seite besteht dabei aus mehreren raspberry pis mit diversen Sensoren, einem Linux-Server der die Seite hostet und von außerhalb ist die Steuerung nur über VPN zu erreichen.
Warum nicht?
Wie oben geschrieben sende ich Schaltbefehl vom Client zum Script, dafür brauche ich eine direkte Verbindung. Bzw. würde ich eine Lösung über die Datenbank sehr umständlich finden.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

so, jetzt habe ich das Script fast verstanden, nur die Funktion

Code: Alles auswählen

def temperature_loop(websocket):
verstehe ich noch nicht komplett.
Trotzdem konnte ich das Ganze jetzt so umbauen das die Schalterzustände an andere Clients weiter und die Daten per json übergeben werden.
Ich habe das Gefühl das das Script ziemlich genau macht was ich will und will mich nochmal herzlich bei euch für die Hilfe bedanken. :D :D :D

Ansonsten wünsche ich dann ein frohes neues Jahr ....

Anbei nochmal das Script wie es jetzt aussieht, samt mysql-Anbindung.

Code: Alles auswählen

#!/usr/bin/env python3

import asyncio
from asyncio.queues import Queue
import logging
import threading
import time
import json

import websockets
from tinkerforge.ip_connection import IPConnection
from tinkerforge.bricklet_remote_switch import RemoteSwitch
import pymysql
from tinkerforge.bricklet_temperature import Temperature

from constants import *


consumers = []  # queue for sending commands
switches = []  # list of all switches and corresponding settings


@asyncio.coroutine
def send_data_loop(websocket):
    # create sending-queue
    loop = asyncio.get_event_loop()
    send_data_changed = Queue()
    logger.debug('websockets .... Queue startet')

    def changed(tmp):
        loop.call_soon_threadsafe(send_data_changed.put_nowait, tmp)
        logger.debug('websockets .... call_soon_threadsafe')

    try:
        consumers.append(changed)
        logger.debug('websockets .... consumers.append')

        # create json-string & send FIRST temperature to all clients if some new client is connecting
        tmp_json = json.dumps([raspberry_device[1], "sensor", "temperature", round(main_temperature, 1)])
        yield from websocket.send(tmp_json)
        logger.debug('websockets .... Erste Daten -> Client ')

        while True:
            temp = yield from send_data_changed.get()
            yield from websocket.send(temp)
            logger.debug('websockets .... yield from websocket.send : %s' % temp)

    finally:
        consumers.remove(changed)
        logger.debug('websockets .... consumers.remove')


@asyncio.coroutine
def socket_handler(websocket, path):
    # set up sending-queue
    task = asyncio.async(send_data_loop(websocket))
    logger.debug('websockets .... asyncio.async')

    while True:

        # get message from client
        message_rec = yield from websocket.recv()

        # leave if client is disconnect
        if message_rec is None:
            break

        # switch to different tasks
        logger.debug('websockets .... yield from websocket.recv -> %s' % message_rec)
        search_command(message_rec)

    # close sending-queue if client discconect
    task.cancel()
    logger.debug('websockets .... task.cancel')


def set_logging(logging_daemon):
    # Logging auf Console
    if LOG_CONSOLE:
        console_handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
        console_handler.setFormatter(formatter)
        logging_daemon.addHandler(console_handler)

    # Logging in Datei
    if LOG_FILE:
        file_handler = logging.FileHandler(LOG_FILE_PATH, mode='w', encoding=None, delay=False)
        formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
        file_handler.setFormatter(formatter)
        logging_daemon.addHandler(file_handler)


def search_command(tmp):
    # decode JSON String
    tmp_json = json.loads(tmp)

    # extract variables from json
    send_location = tmp_json[0]
    send_type = tmp_json[1]
    send_string = tmp_json[2]
    send_command = tmp_json[3]
    logger.info('Tinkerforge ... Schalteränderung -> %s = %s' % (send_string, send_command))

    # if a switch is sending
    if send_type == "switch":

        # search all switches to find the right one
        for item in switches:

            # extract Variables
            switch_id = item[0]
            switch_string = item[6]
            switch_typ = item[7]
            switch_send_a = item[8]
            switch_send_b = item[9]

            # switch found
            if switch_string == send_string:

                # which kind of switch
                if switch_typ == "C":

                    # switch
                    bricklet_remote.switch_socket_c(switch_send_a, int(switch_send_b), int(send_command))

                    # get Time & Date and update Database
                    uhrzeit = time.strftime('%Y-%m-%d %H:%M:%S')
                    mysql_cursor.execute("UPDATE switches SET timestamp = %s, state = %s WHERE  id = %s",
                                         (uhrzeit, send_command, switch_id))

                    # put switch change into all clients-queues
                    for consumer in consumers:
                        consumer(tmp)
                        logger.debug('websockets .... Schalteränderung -> Warteschlange')
                    break


def read_temperature():
    return bricklet_temperatur.get_temperature() / 100.0


def callback_temperature(te):
    global main_temperature

    # get new temperature
    te = (te / 100.0)

    # only if difference is higher or equial to 0.1
    if abs(main_temperature - te) >= 0.1:

        # old temperature set to new temperature
        main_temperature = te
        logger.info('Tinkerforge ... Temperaturänderung -> %f' % main_temperature)

        # create json-string
        tmp_json = json.dumps([raspberry_device[1], "sensor", "temperature", round(main_temperature, 1)])

        # send new temperature to all clients
        for consumer in consumers:
            consumer(tmp_json)
            logger.debug('websockets .... Temperaturänderung -> Warteschlange ')


def db_timer_tasks():
    global __timer

    # get Time & Date
    uhrzeit = time.strftime('%Y-%m-%d %H:%M:%S')

    # Task 1 - take a temperature sample
    mysql_cursor.execute("INSERT INTO sensor_data_temperature (timestamp , data , sensors_id) VALUES (%s , %s , %s)",
                         (uhrzeit, main_temperature, raspberry_device[0]))
    logger.debug('Tinkerforge ... Temperaturwert %f in Datenbank aufgenommen' % main_temperature)

    # start Timer again
    if __timer_is_running:
        __timer = threading.Timer(__timer_intervall, db_timer_tasks).start()


def get_db_settings():
    global switches

    # get ID
    mysql_cursor.execute("SELECT * FROM raspi_devices WHERE ip = %s LIMIT 1", WS_IP)
    __tmp_data = mysql_cursor.fetchone()
    raspberry_pi_id = __tmp_data[0]
    raspberry_pi_name = __tmp_data[2]

    # get switches for this ID
    mysql_cursor.execute("SELECT * FROM switches WHERE raspi_devices_id = %s", raspberry_pi_id)
    switches = mysql_cursor.fetchall()
    logger.info('mySQL ......... Einstellungen eingelesen, id = %s', raspberry_pi_id)

    # return ID for use in other functions
    return raspberry_pi_id, raspberry_pi_name


if __name__ == "__main__":
    #
    # set up Logging Deamon
    #
    logger = logging.getLogger('raspi_server')
    # set up Logging-Level
    logger.setLevel(logging.DEBUG)
    # start Logging
    set_logging(logger)
    logger.info('System startet')

    #
    # set up MySQL Connection
    #
    mysql_connection = pymysql.connect(host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, passwd=MYSQL_PW,
                                       db=MYSQL_DB, autocommit=True)
    mysql_cursor = mysql_connection.cursor()
    # get some settings from db
    raspberry_device = get_db_settings()
    logger.info('mySQL ......... Verbindung online')

    #
    # set up Tinkerforge
    #
    tinkerforge_connection = IPConnection()
    # set up Bricklets
    bricklet_temperatur = Temperature(TF_TEMP_UID, tinkerforge_connection)
    bricklet_remote = RemoteSwitch(TF_REMOTE_UID, tinkerforge_connection)
    # connect to Masterbrick
    tinkerforge_connection.connect(TF_HOST, TF_PORT)
    # set up Callbacks
    bricklet_temperatur.set_temperature_callback_period(5000)
    bricklet_temperatur.register_callback(bricklet_temperatur.CALLBACK_TEMPERATURE, callback_temperature)
    # first data
    main_temperature = read_temperature()
    # finish
    logger.info('Tinkerforge ... System online')

    #
    # set up Database Timer
    #
    if DATABASE_TIMER > 0:
        __timer_is_running = True
        __timer_intervall = DATABASE_TIMER
        __timer = threading.Timer(DATABASE_TIMER, db_timer_tasks).start()

    #
    # set up Websocket-Server
    #
    start_server = websockets.serve(socket_handler, WS_IP, WS_PORT)
    logger.debug('websockets .... set up websockets-server')
    # run Websocket-Server
    asyncio.get_event_loop().run_until_complete(start_server)
    logger.info('websockets .... System online')
    asyncio.get_event_loop().run_forever()

    #
    #
    #
    tinkerforge_connection.disconnect()
    mysql_cursor.close()
    mysql_connection.close()
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

einen Fehler habe ich selbst gefunden.
Und zwar sendet das Script Änderungen des Schalterzustands zweimal, anstatt nur einmal.

Siehe hier

Code: Alles auswählen

2015-01-03 01:46:54 : websockets .... yield from websocket.recv -> ["Wohnzimmer","switch","living_room_lights_plants",1]
2015-01-03 01:46:54 : Tinkerforge ... Schalteränderung -> living_room_lights_plants = 1
2015-01-03 01:46:54 : websockets .... call_soon_threadsafe
2015-01-03 01:46:54 : websockets .... Schalteränderung -> Warteschlange
2015-01-03 01:46:54 : websockets .... call_soon_threadsafe
2015-01-03 01:46:54 : websockets .... Schalteränderung -> Warteschlange
2015-01-03 01:46:54 : websockets .... yield from websocket.send : ["Wohnzimmer","switch","living_room_lights_plants",1]
2015-01-03 01:46:54 : websockets .... yield from websocket.send : ["Wohnzimmer","switch","living_room_lights_plants",1]
Nur warum das so ist verstehe ich nicht.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: Wieviele Clients sind denn gerade verbunden?
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Peinlich, peinlich .... manchmal sieht man den Wald vor lauter Bäumen nicht. :(

Natürlich stimmt alles, den wie von dir vermutet waren zu dem Zeitpunkt 2 x Clients verbunden.
Also ist es ja nur logisch das er auch 2 x versendet.

Tut mir ehrlich leid, aber irgendwie bin ich auf das offensichtlichste nicht gekommen.
Danke :D
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Guten Abend,

mittlerweile bin ich ein Stück weiter und habe mich für ein Client-Server Modell entschieden, sprich die einzelnen RaspberryPis sprechen nur mit einem Script auf dem Server.
Der Server spricht dann wieder mit den UI's auf Desktop, Tablet & Co.
Außerdem holt sich das Client-Script seine Einstellungen beim Server ab und auch die angeschlossenen Sensoren werden dynamisch erzeugt. Zumindest das habe ich schon einmal alles geschafft.
Sehr wahrscheinlich habe ich es nicht gut gelöst, aber immerhin.

Erstmal der Code:

Code: Alles auswählen

#!/usr/bin/env python3

import asyncio
from asyncio.queues import Queue
import logging

import websockets
from tinkerforge.ip_connection import IPConnection

from my_tinkerforge import *


# logging
LOG_CONSOLE = True
LOG_FILE = False
LOG_FILE_PATH = "/var/log/raspi.log"
DEVICE_IP = "192.168.127.30"
WS_SERVER = "192.168.127.30:5500"

consumers = []  # queue for sending commands
sensors = []
bricklets = {}
settings = {"id": 0, "tf_callback": 0}


@asyncio.coroutine
def send_data_loop(websocket):
    # create sending-queue
    loop = asyncio.get_event_loop()
    sending_queue = Queue()
    logger.debug('websockets .... Queue startet')

    def changed(tmp):
        loop.call_soon_threadsafe(sending_queue.put_nowait, tmp)
        logger.debug('websockets .... call_soon_threadsafe')

    try:
        consumers.append(changed)
        logger.debug('websockets .... consumers.append')

        # step 1 get settings
        yield from websocket.send(json.dumps(["get_settings", DEVICE_IP, "", "", ""]))

        while True:
            tmp_data = yield from sending_queue.get()
            yield from websocket.send(tmp_data)
            logger.debug('websockets .... yield from websocket.send : %s' % tmp_data)

    finally:
        consumers.remove(changed)
        logger.debug('websockets .... consumers.remove')


@asyncio.coroutine
def client_handler():
    # connect to server
    websocket = yield from websockets.connect('ws://' + WS_SERVER + '/')

    # set up sending-queue
    task = asyncio.async(send_data_loop(websocket))
    logger.debug('websockets .... asyncio.async')

    while True:

        # get message from client
        message_rec = yield from websocket.recv()

        # leave if client is disconnect
        if message_rec is None:
            break

        # switch to different tasks
        logger.debug('websockets .... yield from websocket.recv -> %s' % message_rec)
        message_handler(message_rec)

    # close sending-queue if client discconect
    task.cancel()
    logger.debug('websockets .... task.cancel')


def message_handler(tmp):
    global settings
    global sensors
    # decode JSON String
    tmp_json = json.loads(tmp)

    if tmp_json[0] == "send_settings":
        settings = tmp_json[4]
        logger.info('websockets .... get Settings -> id = %s , title = %s , tf_callback = %s' % (
            settings["id"], settings["title"], settings["tf_callback"]))
        for consumer in consumers:
            consumer(json.dumps(["get_sensors", settings["id"], "", "", ""]))
    elif tmp_json[0] == "send_sensors":
        sensors = tmp_json[4]
        for x in sensors:
            set_tinkerforge_bricklet(x[0], x[1], x[2], tinkerforge_connection)
            logger.info('websockets .... get Sensors -> id = %s , title = %s , type = %s' % (
                x[0], x[1], x[2]))
        tinkerforge_connection.connect("localhost", 4223)
        for x in sensors:
            bricklets[x[1]].set_callback(settings["tf_callback"])
        logger.info('Tinkerforge ... System online')


def set_tinkerforge_bricklet(uid, title, tf_type, tf_connection):
    if tf_type == "temperature":
        bricklets[title] = BrickletTemperature(uid, tf_connection, logger, consumers)
    # connect to Masterbrick
    logger.info('Tinkerforge ... Bricklet %s online' % title)


def set_logging():
    # Logging auf Console
    if LOG_CONSOLE:
        console_handler = logging.StreamHandler()
        formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
        console_handler.setFormatter(formatter)
        logger.addHandler(console_handler)

    # Logging in Datei
    if LOG_FILE:
        file_handler = logging.FileHandler(LOG_FILE_PATH, mode='w', encoding=None, delay=False)
        formatter = logging.Formatter('%(asctime)s : %(message)s', '%Y-%m-%d %H:%M:%S')
        file_handler.setFormatter(formatter)
        logger.addHandler(file_handler)


if __name__ == "__main__":
    #
    # set up Logging Deamon
    #
    logger = logging.getLogger('raspi_server')
    # set up Logging-Level
    logger.setLevel(logging.DEBUG)
    # start Logging
    set_logging()
    logger.info('System startet')

    #
    # set up Tinkerforge
    #
    tinkerforge_connection = IPConnection()

    #
    # set up Websocket-Server
    #
    asyncio.get_event_loop().run_until_complete(client_handler())

Code: Alles auswählen

import json
from tinkerforge.bricklet_temperature import Temperature
from tinkerforge.bricklet_barometer import Barometer
from tinkerforge.bricklet_ambient_light import AmbientLight
from tinkerforge.bricklet_humidity import Humidity


class BrickletTemperature:
    value = 0.0
    trigger_difference = 0.1
    value_digits = 1

    def __init__(self, uid, tmp_connection, tmp_logging, tmp_queue):
        self.bricklet = Temperature(uid, tmp_connection)
        self.__uid = uid
        self.__logging = tmp_logging
        self.__queue = tmp_queue
        self.__logging.debug('Tinkerforge ... Temperatur-Bricklet "%s" initialisiert' % uid)

    def set_callback(self, timeframe=5000):
        self.bricklet.set_temperature_callback_period(timeframe)
        self.bricklet.register_callback(self.bricklet.CALLBACK_TEMPERATURE, self.__changed)
        self.__logging.debug('Tinkerforge ... Temperatur-Bricklet "%s" Callback gesetzt' % self.__uid)

    def read(self):
        return self.bricklet.get_temperature() / 100.0

    def __changed(self, tmp_value):
        # get new temperature
        tmp_value = (tmp_value / 100.0)

        # only if difference is higher or equial to 0.1
        if abs(self.value - tmp_value) >= self.trigger_difference:

            # old temperature set to new temperature
            self.value = tmp_value
            self.__logging.debug('Tinkerforge ... Temperaturänderung bei "%s" -> %f' % (self.__uid, self.value))

            # create json-string ( sensor-id <> sensor-type <> sensor-name <> sensor-value )
            tmp_json = json.dumps(["send_data", self.__uid, "sensor", "temperature", round(self.value, self.value_digits)])

            # send new temperature to all clients
            for consumer in self.__queue:
                consumer(tmp_json)
                self.__logging.debug('websockets .... Temperaturänderung bei "%s" -> Warteschlange ' % self.__uid)


Ich versuche jetzt das Script das hier erarbeitet wurde entsprechend anzupassen.
Im Prinzip funktioniert das auch ohne Fehler.

Nur, wenn ich das richtig sehe ist es doch unnötig eine Liste der consumers anzulegen, da das Client-Skript ausschließlich mit dem Server Skript spricht, sprich es gibt immer nur einen consumer. Hab ich das soweit richtig verstanden?

Ich versuche nun schon eine ganze Weile zu verstehen wie ich die Liste entfernen kann und trotzdem meine Callback-Änderungen durchgegeben bekomme - leider bekomme ich es nicht hin.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@der_Mausbiber: Wenn Du keine Liste willst, dann übergibt BrickletTemperature einfach nur eine Callback-Funktion, anstatt der Liste. An der Klasse ist übrigens noch einiges unschön:
- auch Klasse-Konstanten sollte man in Großbuchstaben schreiben (TRIGGER_DIFFERENCE)
- value ist aber keine Konstante und muß deshalb in __init__ initialisiert werden.
- die Doppelunterstriche sind alle unschön, stören und sollten durch einfache ersetzt werden.
- was sollen die tmp_-Päfixe an allen Argumenten von __init__?
- floats sollte man erst bei und durch die Ausgabe runden
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

Nabend
Ich war die letzten Wochen beruflich und privat eingespannt und kam nicht dazu hier weiter zu arbeiten.
Jetzt möchte ich mal weiter machen.
Die Antwort hat mir schon einmal zum Teil weitergeholfen.
Mal schauen ob ich dich richtig verstanden habe:
value ist aber keine Konstante und muß deshalb in __init__ initialisiert werden.
Das heißt alle Klasseneigenschaften, nichts anderes soll "value" sein, müssen innerhalb von __init__ initialisiert werden?
Ich kannte das aus anderen Sprachen anders, aber wenn es so besser ist - gerne.
Auch Klasse-Konstanten sollte man in Großbuchstaben schreiben (TRIGGER_DIFFERENCE)
Das wusste ich noch nicht.
In dem Fall soll aber "trigger_difference" keine Konstante, sondern eine Klasseneigenschaft mit einem Vorgabewert sein.
Müsste also nach __init__ verschoben werden?
die Doppelunterstriche sind alle unschön, stören und sollten durch einfache ersetzt werden.
Ich wollte diese Variablen auf "private"-Status setzen und hatte hier -> http://www.hdm-stuttgart.de/~maucher/Py ... assen.html unter "6.1.3. Sichtbarkeit" gelesen das man dann einen "__" nutzen soll.
Aber in meinem Fall ist dann "protected" -> "_" richtig?
floats sollte man erst bei und durch die Ausgabe runden
Werde ich ändern und dann durch die javascript die Ausgabe runden.
was sollen die tmp_-Päfixe an allen Argumenten von __init__?
Ich dachte so wäre der Code leichter lesbar, da diese Variablen ja nur temporär genutzt werden.
So kann man sich täuschen :wink:
Wenn Du keine Liste willst, dann übergibt BrickletTemperature einfach nur eine Callback-Funktion, anstatt der Liste
Hier hänge ich noch ein fest, hoffe das ich die Tage einen Durchbruch habe.
BlackJack

Die verlinkte Klassenbeschreibung ist ja gruselig. Da versucht jemand die Ideen von einer anderen Programmiersprache 1:1 auf Python zu übertragen.

Klassenattribute werden dort als „statische Attribute” bezeichnet — an denen ist aber nichts statisch, die sind genau so dynamisch wie alle anderen Attribute auch. Sie sind halt nur auf dem Klassenobjekt statt auf Exemplaren die aus der Klasse erzeugt werden.

Am Anfang wird auch zwischen „Klassen” und „Objekten” unterschieden. Nur sind Klassen in Python auch Objekte, „Klasse” ist also zu „Objekt” gar keine scharfe Abgrenzung.

Attribute können nicht privat im Sinne von von aussen nicht zugänglich sein, wie da fälschlicherweise behauptet wird. Und *deklarieren* kann man das schon gar nicht. Python hat nur sehr wenige Deklarationen. Wenn ich jetzt nichts vergessen habe nur ``global`` und bestimmte ``__future__``-Importe, und in Python 3 ``nonlocal``. Alles andere findet nach dem Compilieren zur Laufzeit statt.

Der Satz „Methoden sind Funktionen, die innerhalb einer Klasse definiert werden. Sie sind innerhalb der Klasse sichtbar und benutzbar. Von aussen kann auf Methoden nur über eine Referenz auf ein Objekt dieser Klasse zugegriffen werden […]” macht auch keinen Sinn denn was bedeutet denn hier „innerhalb der Klasse sichtbar und benutzbar”? Auch *in* den Methoden kann man auf andere Attribute oder Methoden nur über eine Referenz zugreifen. Das Objekt ist da ja an den Namen `self` gebunden. Ohne geht es nicht. Das steht dann ja auch gleich danach im selben Absatz.

„Häufig werden Klassenattribute verwendet um die aktuell instanzierten Objekte einer Klasse zu zählen.” — *WTF*, das wird *nie* gemacht. Echt nicht. Nur in Tutorials die nichts taugen. In realem Code nicht.

Es gibt nur eine Sichtbarkeit und die ist ”public”, wobei das natürlich wenig Sinn macht eine Bezeichnung zu haben für etwas was gegen nichts anderes abgegrenzt werden muss. Das zwanghafte fantasieren von ``public``, ``private``, und ``proteced`` als Unterscheidung ist irgendwie so eine Macke von Java-Programmierern glaube ich. Doppelte führende Unterstriche sind nicht ``privat`` — man kann auf solche Attribute von aussen problemlos zugreifen, die heissen real bloss anders weil der Klassenname da noch eingebastelt wird („name mangling”). Das wird nicht gemacht damit man nicht darauf zugreifen kann, sondern um Namenskollisionen bei Mehrfachvererbung zu verhindern. Da aber keiner ernsthaft Mehrfachvererbung verwendet braucht man das so gut wie *nie*. Ein führender Unterstrich ist eine Konvention um Attribute zu kennzeichnen die nicht zur öffentlichen API eines Objekts gehören. Das würde ich aber nicht als ``protected`` bezeichnen.

Das was der Text als Konstruktor bezeichnet ist keiner. Die `__init__()`-Methode *initialisiert* ein bereits bestehendes Objekt. Im Gegensatz zu einigen anderen Sprachen (z.B. Java) die das als Konstruktor bezeichnen ist die Unterscheidung in Python wichtig weil Python tatsächlich auch eine Methode kennt in der man in den Konstruktionsprozess eingreifen kann, also einen echten Konstruktor und das ist die `__new__()`-Methode.

Beim Text zum „Destruktor” wird es abenteuerlich. Was da steht ist schlicht *falsch*. Weder kann man mit ``del(objref)`` ein Objekt explizit löschen noch löst das zwangsläufig den Aufruf der `__del__()`-Methode aus. Das ist gefährlicher Unsinn. Das vorhandensein von `__del__()` kann sogar dazu führen das ein Objekt unter bestimmten Umständen niemals gelöscht wird und damit auch der Code in der Methode nicht ausgeführt wird. Man sollte sich mit der Speicherverwaltung von Python schon genau auskennen bevor man daran denkt diese Methode zu definieren. Das ist ein ziemliches Minenfeld. Anfängern würde ich diese Methode gar nicht erst zeigen, oder nur mit der Warnung: Nicht implementieren, sehr gefährlich.

Beim „Erzeugen von Objekten” werden die runden Klammern im Text als „geschweifte Klammern” bezeichnet und: „Enthält der Konstruktor als Parameter nur self, dann können die geschweiften Klammern nach dem Klassennamen ganz weggelassen werden.” — Äh, was bitte? Das ist nicht Python was da beschrieben wird.

Das Beispiel mit der Kontoklasse ist dann auch so ein typisch kaputter Code von jemandem der offensichtlich Java oder so kann und nach kurzer Zeit meint er hätte Python verstanden. Mit ”privaten” Attributen und einem Examplarzähler der in der `__init__()`- hoch und der `__del__()`-Methode runtergezählt wird. Mal davon abgesehen das so ein globaler Zähler ein schlechter Entwurf ist, funktioniert das so nicht zuverlässig. Da wird von Garantien ausgegangen die von der Sprache nicht gegeben werden.

In der `einzahlen()`-Methode werden Typen geprüft — wer das so macht soll gefälligst keine dynamisch typisierte Programmiersprache verwenden. Wobei die Art — `type()`-Ergebnisse mit ``==`` und Typen zu vergleichen — auch noch mal schlecht ist. Wenn man so etwas schon machen muss nimmt man dafür `isinstance()`.
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

wow ... da habe ich wohl genau die richtige Seite gefunden :o
der_Mausbiber
User
Beiträge: 72
Registriert: Donnerstag 2. Oktober 2014, 09:51

so, habe die Klasse jetzt soweit umgebaut

Code: Alles auswählen

class BrickletTemperature:

    def __init__(self, uid, connection, logging, queue, value=0.0, trigger_difference=0.1):
        self._bricklet = Temperature(uid, connection)                                               # private
        self.value = value                                                                          # public
        self.trigger_difference = trigger_difference                                                # public
        self._uid = uid                                                                             # private
        self._logging = logging                                                                     # private
        self._queue = queue                                                                         # private
        self._logging.debug('Tinkerforge ... Temperatur-Bricklet "%s" initialisiert' % uid)

    def set_callback(self, timeframe=5000):
        self._bricklet.set_temperature_callback_period(timeframe)
        self._bricklet.register_callback(self._bricklet.CALLBACK_TEMPERATURE, self.__changed)
        self._logging.debug('Tinkerforge ... Temperatur-Bricklet "%s" Callback gesetzt' % self._uid)

    def read(self):
        return self._bricklet.get_temperature() / 100.0

    def __changed(self, tmp_value):
        # get new temperature
        tmp_value = (tmp_value / 100.0)

        # only if difference is higher or equial to 0.1
        if abs(self.value - tmp_value) >= self.trigger_difference:

            # old temperature set to new temperature
            self.value = tmp_value
            self._logging.debug('Tinkerforge ... Temperaturänderung bei "%s" -> %f' % (self._uid, self.value))

            # create json-string ( sensor-id <> sensor-type <> sensor-name <> sensor-value )
            tmp_json = json.dumps(
                ["send_data", self._uid, "sensor", "temperature", self.value])

            # send new temperature to all clients
            for consumer in self._queue:
                consumer(tmp_json)
                self._logging.debug('websockets .... Temperaturänderung bei "%s" -> Warteschlange ' % self._uid)
Ist das jetzt soweit in Ordnung oder sind noch grobe Schnitzer drin?
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

So ein paar Dinge fallen da auf den ersten Blick noch auf. Zeilen sollten in Python nicht länger als 80 Zeichen sein, dann solltest du die Zeile umbrechen oder dich fragen, ob wirklich so viel in eine Zeile gehört. Dann wird das ganze etwas leserlicher. Auch solltest du keine Kommentare hinter Code schreiben, so wie in den Zeilen 4 bis 9. Das ist sehr unübersichtlich. Schreibe Kommentare am besten vor die betreffende Zeile.

Die Kommentare aus Zeilen 4 bis 9 solltest du am besten ganz weglassen, die haben keinen Zusätzlichen nutzen. Durch den führenden Unterstrich, oder eben das Fehlen desselben, ist vollkommen offensichtlich, ob diese privat sind oder nicht. Das gilt auch für deine anderen Kommentare. Die beschreiben viel zu offensichtliche Dinge. Kommentare sollten einen Mehrwert bieten und nicht den Code noch einmal in Worten beschreiben. Kommentare sind dazu gedacht, dass der Ablauf und die Zusammenhänge beschrieben werden und nicht das Programm noch einmal 1:1 in Umgangssprache zu beschreiben.

Für "logging" solltest du dir noch einen besseren Namen überlegen, den Namen gibt es schon als Modul in der Standardbibliothek. Die selben Namen sollten besser nicht verwendet werden, das führt nur zur Verwirrung. "logger" wäre zum Beispiel eine Alternative.

"__changed" solltest du noch anpassen, die doppelten führenden Unterstriche gehören hier nicht. Die sind dazu gedacht, um Namenskonflikte bei der Vererbung zu vermeiden. Es findet aber gar keine Vererbung statt.

Die 100.0 aus read und aus Zeile 22 solltest du noch zu eine Konstante zusammenfassen. Zumindest nehme ich mal an, dass diese zusammengehören. Vielleicht machst du gleich eine Funktion draus, die die Temperaturen umwandelt. Aus der read-Funktion könntest du auch ein Property machen, dann hat man von außen einen schöneren Zugriff.
Das Leben ist wie ein Tennisball.
Antworten