SQL Upload von "print" Ausgabe Daten

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
horst2020
User
Beiträge: 4
Registriert: Mittwoch 15. Juli 2020, 04:45

Hallo Python-Gemeinde,

ich bräuchte eure Hilfe bei einem nicht von mir erstellten Skript zum Upload von Daten, welche am Ende des Skripts als "print" ausgegeben werden. Hintergrund ist, dass die Fa. Refusol ein Online Portal für den Datenlogger eines PV-Heizstabes ausser Dienst gestellt hat, aber ich die Daten trotzdem gerne speichern möchte. Ich habe in einem anderem Forum ein Skript gefunden, welches die Daten aus dem Logger soweit abruft, übersetzt und lesbar macht. Allerdings möchte ich diese Daten nicht ständig per Hand abrufen, sondern systematisiert in einer SQL Tabelle erfassen lassen.

Ich habe einen anderen Forums-Beitrag gefunden, in welchem der Aufbau des Skriptes als nicht professionel kritisiert wurde, aber es funktioniert wirklich sehr gut. Ich bin abosluter Python-Rookie und bitte euch um eure Hilfe bzw. um Rat und zwar, ob und wie man einen SQL Upload in das Skript einbauen kann. Hier noch der andere Beitrag:
viewtopic.php?f=1&t=48939&p=368941&hili ... ak#p368941

Die mySQL Tabelle liegt auf einem privaten Webspace und wurde mittels phpMyAdmin wie folgt angelegt:
Host: 62.108.xx.xxx:3306
Datenbank: pvheater
Tabellenname: datenlogger
Spalten: Date; Time; DC Power total; Water Temp; Total kWh; Actual Day kWh; Internal Temperature; DC Power 1; DC Power 2; DC Power 3;

Das Skript lautet nach meinem Umbau aktuell wie folgt:

Code: Alles auswählen

#!/usr/bin/env python3
import ftplib
import struct
import time
from datetime import date
import os
import datetime
import socket



def main():
	hostname = "192.168.10.124"
	ftp_user = "refu"
	ftp_password = "EE0129"
	dataset_length = 10
	additional_header_length = 18
	amount_of_datasets = 18
	size_of_dataset_header = 6
	size_of_dataset_info = 4

	#determine actual date

	actual_date = date.today()
	
	s_year = str(actual_date.year)
	#print(s_year)
	
	if actual_date.month<10 :
		s_month = "0" + str(actual_date.month)
	else: s_month = str(actual_date.month)
	
	if actual_date.day<10 :
		s_day = "0" + str(actual_date.day)
	else: s_day = str(actual_date.day)
	
	# creating all directories for the complete year
	
	akt_path=os.getcwd()
	try:
		os.mkdir(s_year)
	except OSError:
		print ("Creation of the directory %s failed maybe already existing" % s_year)
	else:
		print ("Successfully created the directory")
	
	os.chdir(s_year)
	for i in range (1,13):
		if i < 10:
			dir_name = "0"+ str(i)
			try:
				os.mkdir(dir_name)
			except OSError:
				print ("Creation of the directory failed: %s maybe already existing" % dir_name)
			else:
				print ("Successfully created the directory: %s" % dir_name)
			
		else:
			dir_name = str(i)
			try:
				os.mkdir(dir_name)
			except OSError:
				print ("Creation of the directory failed: %s maybe already existing" % dir_name)
			else:
				print ("Successfully created the directory: %s" % dir_name)
			
		
	
	os.chdir(s_month)
	
	#open local temporary file
	print("Open temp file for writing")
	temp_file_name = "temp.log"
	temp_file = open(temp_file_name, 'wb')
	
	#doing the FTP stuff
	print("Open connection to FTP Server")
	try: 
		ftp = ftplib.FTP(hostname, ftp_user, ftp_password, timeout=10)
	except ftplib.all_errors:
		print("FTP Server not reachable aborting connection")
		
		print("Close temp file")
		temp_file.close()
		print("Deleting temporary file")
		if os.path.exists(temp_file_name):
			os.remove(temp_file_name)
			print("Successful")
		else:
			print("Failed")
   
	else:
		# higher debug level not needed anymore
		#ftp.set_debuglevel(2)
		ftp.set_pasv(False)
		remote_path = "/data/logger/"+s_year+"/"+s_month
		ftp.cwd(remote_path)
		retr_command = "RETR "+s_day+".log"
		print("Get file from FTP")
		ftp.retrbinary(retr_command, temp_file.write)
		print("Closing FTP connection")
		ftp.close()
		
		# Determine the file length of the received file
		print("Move to the end of the received file to get length")
		temp_file.seek(0,2)
		temp_file_size = temp_file.tell()
		print("Length of temporary file %d" % temp_file_size)
		print("Close temp file...")
		temp_file.close()
		print("Open temp file for reading")
		temp_file = open("temp.log", 'rb')	
		
		#open local logfile
		local_filename = s_day +".log"
		print("Filename for the local file: " + local_filename)
		print("Trying to open local logfile")
		
		try:
			local_file = open(local_filename, 'rb')
		except IOError:
			print("Error - no logfile existing")
			print("Creating an new file")
			local_file = open(local_filename, 'wb')
			print("closing the local file")
			local_filesize=0;
		else:
			# get bytecount of local logfile
			# move to the end of the file
			print("Move to end to get filesize")
			local_file.seek(0,2)
			# get the position in bytes
			local_filesize=local_file.tell()
			print("Length of old, stored local file %d" % local_filesize)
			print("Closing local file...")	
		
		local_file.close()
		
		print("Open local file for writing or creating new one")
		local_file = open(local_filename, 'wb')
		
		
		
		
		#storage for 18 values, maybe easier to allocate
		myValue = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
		
		# offset consisting of 18 datasets with 10 byte each + 12 byte 0xFF + 4 byte Unix time + 2 bytes length information
		offset = amount_of_datasets*dataset_length + additional_header_length
		
		if temp_file_size > local_filesize:
			# newer data was on ftp server - copy to logal logfile
			print("More data in received file than in local file")
			print("Transfer data from received file to local file")
			print("Transfer data from received file to old logfile")
			local_file.write(temp_file.read())
			print("Closing local file...")
			local_file.close()
			print("Open local file for reading...")
			local_file = open(local_filename, 'rb')
			# move to the start of the last entry
			local_file.seek(-offset,2)
			
			unixtime=local_file.read(4)
			trennzeichen=local_file.read(14)
			
			timeint=struct.unpack('>I', unixtime)
			print(timeint)

			print(datetime.datetime.fromtimestamp(int(timeint[0])).strftime('%Y-%m-%d %H:%M:%S'))

			for i in range (0,amount_of_datasets):
				
				headerData = local_file.read(size_of_dataset_header)
				tagData = local_file.read(size_of_dataset_info)
				# Endian format change with >
				newval = struct.unpack('>f', tagData)
				#newval is a tuple with float as first element
				myValue[i]=newval[0]
				# value printing moved to point after renaming it
				print("%.2f" % newval)
			
			enable_transmission=1;
		else:
			print("No new data available")
			enable_transmission=0;
			
				
		
		print("Reading of datablocks from local file complete")
		print("Closing both local and temporary file")
		# closeing all files
		temp_file.close()
		local_file.close()
		
		# Deleting the temporary file from filesystem

		print("Deleting temporary file")
		if os.path.exists(temp_file_name):
			os.remove(temp_file_name)
			print("Successful")
		else:
			print("Failed")
		
		# giving new names
		dc_power1 = round(myValue[0],2)
		dc_power2 = round(myValue[1],2)
		dc_power3 = round(myValue[2],2)
		ww_temp = round(myValue[4],2)
		int_temp = round(myValue[5],2)
		dc_voltage1 = round(myValue[9],2)
		dc_voltage2 = round(myValue[10],2)
		dc_voltage3 = round(myValue[11],2)
		total_kWh = round((myValue[12]/10),2)
		kWh_gen1 = round((myValue[13]/10),2)
		kWh_gen2 = round((myValue[14]/10),2)
		kWh_gen3 = round((myValue[15]/10),2)
		#kWh_day	 = round((myValue[16]/10),2)
		kWh_day = round((kWh_gen1+kWh_gen2+kWh_gen3),2)
		akt_power = round((dc_power1+dc_power2+dc_power3),2)
		
		print("DC Power total: %f" % akt_power)
		print("Water Temp %f" % ww_temp)
		print("Total kWh %f" % total_kWh)
		print("Actual Day kWh: %f" % kWh_day)
		print("Internal Temperature: %f" % int_temp)
		print("DC Power 1: %f" % dc_power1)
		print("DC Power 2: %f" % dc_power2)
		print("DC Power 3: %f" % dc_power3)	
		
		
		
	print("Python script complete")
				


if __name__ == "__main__":
    main()
Kann man die als "print" ausgegebenen Werte irgendwie in die mySQL Tabelle bringen? Das Skript läuft mittels Phyton3.

Besten Dank für eure Hilfe!
Viele Grüße
Horst
Benutzeravatar
sparrow
User
Beiträge: 4221
Registriert: Freitag 17. April 2009, 10:28

Wo läuft das Script? Auf deinem PC daheim?
Und die Datenbank liegt auf einem privaten Webspace?

Wenn der Webspace vernünftig konfiguriert ist, kann das nicht funktionieren, weil der hoffentlich keine Datenbankzugriff außer von localhost zulässt.
Das müsstest du also vorab erst einmal prüfen.

Sollte das der Fall sein, kannst du nicht von außerhalb auf die Datenbank zugreifen. Du könntest auf dem Server eine Webanwendung oder ähnliches laufen lassen, dass die Daten empfängt und dann in die Datenbank schreibt.
Sirius3
User
Beiträge: 17782
Registriert: Sonntag 21. Oktober 2012, 17:20

@horst2020: dieses Skript kommt mir wieder von irgendwoher so bekannt vor. Wenn schon kopieren, dann von der Version, die hier im Forum schon verbessert wurde.
Eingerückt wird mit 4 Leerzeichen pro Ebene, keine Tabs.
socket wird importiert aber nicht genutzt.
Kryptische Kürzel vor Variablennamen vermeiden, z.B. das s_ vor s_year. Für führende Nullen gibt es Stringformatierung.
os.chdir hat in einem ordentlichen Programm nichts verloren, man benutzt absolute oder relative Pfade vom Arbeitsverzeichnis aus.
Statt der low-Level-Funktionen in os benutzt man pathlib.Path.
`actual_date` ist sicher nicht das, was Du denkst, was es ist, das müsste `current_date` heißen.
Kosntanten schreibt man gross und an den Anfang der Datei.

Wenn man unbedingt ftplib verwenden möchte, dann benutzt man retrbinary oder ähnliches, um Daten zu lesen, und benutzt keine temporären Dateien (die man dann aber mit tempfile erzeugt). Ach tust Du ja, schreibst aber trotzdem in eine Datei.
Die Fehlerbehandung verschluckt den eigentlichen Fehler und kann weg.
Strings stückelt man nicht mit + zusammen sondern benutzt Stringformatierung.

Dann initialisiert man nicht Listen mit Dummy-Werten vor, sondern erzeugt sie gleich mit den richtigen Werten. Das my-Präfix ist wieder total überflüssig und kann weg.
Gerundet wird erst bei der Ausgabe, und zwar mit Stringformatierung, nicht schon vorher mit round.
Ein paar mehr Funktionen wären auch gut:

Code: Alles auswählen

from datetime import date
from pathlib import Path

HOSTNAME = "192.168.10.124"
FTP_USER = "refu"
FTP_PASSWORD = "EE0129"
ADDITIONAL_HEADER_LENGTH = 18
AMOUNT_OF_DATASETS = 18
SIZE_OF_DATASET_HEADER = 6
SIZE_OF_DATASET_INFO = 4
DATASET_LENGTH = SIZE_OF_DATASET_HEADER + SIZE_OF_DATASET_INFO
# offset consisting of 18 datasets with 10 byte each + 12 byte 0xFF + 4 byte Unix time + 2 bytes length information
TOTAL_SIZE = AMOUNT_OF_DATASETS * DATASET_LENGTH + ADDITIONAL_HEADER_LENGTH

def ftp_read_data(current_date):
    ftp = ftplib.FTP(HOSTNAME, FTP_USER, FTP_PASSWORD, timeout=10)
    ftp.set_pasv(False)
    remote_path = f"/data/logger/{current_date:%Y}/{current_date:%m}"
    ftp.cwd(remote_path)
    retr_command = f"RETR {current_date:%d}.log"
    print("Get file from FTP")
    data = []
    ftp.retrbinary(retr_command, data.append)
    data = b''.join(data)
    print("Closing FTP connection")
    ftp.close()
    return data

def parse_data(data):
    data = data[-TOTAL_SIZE:]
    unixtime = struct.unpack_from(">I", data, 0)
    print(datetime.datetime.fromtimestamp(int(timeint[0])).strftime('%Y-%m-%d %H:%M:%S'))
    return [struct.unpack_form(">f", data, offset)
        for offset in range(ADDITIONAL_HEADER_LENGTH, size, DATASET_LENGTH)]

def print_values(values):
    (dc_power1, dc_power2, dc_power3, _,
    ww_temp, int_temp, _, _, _,
    dc_voltage1, dc_voltage2, dc_voltage3,
    total_kWh, kWh_gen1, kWh_gen2, kWh_gen3, _, _) = values
    total_kWh /= 10
    kWh_day = (kWh_gen1+kWh_gen2+kWh_gen3) / 10
    akt_power = dc_power1+dc_power2+dc_power3	
    print(f"DC Power total: {akt_power:.2f}")
    print(f"Water Temp {ww_temp:.2f}")
    print(f"Total kWh {total_kWh:.2f}")
    print(f"Actual Day kWh: {kWh_day:.2f}")
    print(f"Internal Temperature: {int_temp:.2f}")
    print(f"DC Power 1: {dc_power1:.2f}")
    print(f"DC Power 2: {dc_power2:.2f}")
    print(f"DC Power 3: {dc_power3:.2f}")

def main():
    current_date = date.today()
    current_date_path = Path(f"{current_date:%Y}") / f"{current_date:%m}" / f"{current_date:%d}.log"
    current_date_path.parent.mkdir(exist_ok=True, parents=True)
    data = ftp_read_data(current_date)

    # Determine the file length of the received file
    print(f"Length of data {len(data}")
    try:
        old_data = current_date_path.read_bytes()
    except IOError:
        print("Error - no logfile existing")
        old_data = b""
    print(f"Length of old data {len(old_data}")
    if len(data) > len(old_data):
        current_date_path.write_bytes(data)
        values = parse_data(data)
        print_values(values)

if __name__ == "__main__":
    main()
horst2020
User
Beiträge: 4
Registriert: Mittwoch 15. Juli 2020, 04:45

@sparrow:

Danke erstmal für deine Antwort. Das Skript läuft auf einem Raspberry Pi Zero und funktioniert mit Python3 bisher tadellos. Die MySQL Tabelle liegt auf einem Webspace von Wint.Global. Welche Anwendung braucht man für den Upload in die SQL?
Alternativ wäre auch eine SQL Tabelle auf dem Raspberry für mich in Ordnung.

@sirius3:

Sorry aber als Anfänger kann ich mit deinen Ausführungen nicht viel anfangen. Das bereits korrigierte Skript funktioniert nicht und deshalb habe ich die Ursprungsversion verwendet. Trotz der vielen Anmerkungen und Verbesserungsvorschläge funktioniert dies bisher tadellos. Ich kann als Laie diese Punkte leider nicht implementieren.
Benutzeravatar
sparrow
User
Beiträge: 4221
Registriert: Freitag 17. April 2009, 10:28

@horst2020: Wenn kein externer Zugriff auf die Datenbank möglich ist (was wahrscheinlich ist) braucht es halt irgend ein Programm, das auf dem Server Daten empfängt und in die Datenbank schreibt. Die muss man natürlich entsprechend absichern - sonst könnte man die Datenbank ja direkt für das Internet öffnen. Das möchte man nicht, weshalb die Webspace-Anbieter die Datenbank auch nur intern öffnen. Sprich: Du müsstest eine Webapplikation in einer entsprechenden Sprache schreiben.

Mit einer lokalen Datenbank hast du zumindest das Verbindugsproblem nicht.
Warum soll es denn mysql sein? Wenn die Daten einfach nur in eine Datenbank sollen (die Installation müsstest du ja pflegen) könntest du auch sqlite benutzen. Python hat ein entsprechendes Modul in das du dich einarbeiten müsstest.
horst2020
User
Beiträge: 4
Registriert: Mittwoch 15. Juli 2020, 04:45

@sparrow

Danke für deine schnelle Antwort. Bedeutet selbst mit den Zugangsdaten zu der Datenbank bekomme ich die Daten nicht in die Tabelle? Ok, sqlite ist natürlich eine Alternative.

Mache mich da mal schlau. Danke
Benutzeravatar
sparrow
User
Beiträge: 4221
Registriert: Freitag 17. April 2009, 10:28

Da du bisher noch nicht mitgeteilt hast, ob die Datenbank extern geöffnet ist, kann dir die Frage niemand beantworten.
horst2020
User
Beiträge: 4
Registriert: Mittwoch 15. Juli 2020, 04:45

@sparrow

Ja das würde ich einstellen, also dann von extern Daten eingespielt werden könnten
Benutzeravatar
sparrow
User
Beiträge: 4221
Registriert: Freitag 17. April 2009, 10:28

Dann such dir eines der zahlreichen Beispiele im Internet, wie man sich zu einer mySQL-Datenbank verbindet und probiere ob die Verbindung funktioniert.
Sirius3
User
Beiträge: 17782
Registriert: Sonntag 21. Oktober 2012, 17:20

horst2020 hat geschrieben: Mittwoch 15. Juli 2020, 10:26Sorry aber als Anfänger kann ich mit deinen Ausführungen nicht viel anfangen.
Dann solltest Du Dich in das, was ich geschrieben habe, einarbeiten. Da steht nichts, was besonders kompliziert wäre. Und wenn etwas nicht funktioniert, dann wäre die Fehlermeldung ganz hilfreich.
Benutzeravatar
__blackjack__
User
Beiträge: 13168
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Wenn es SSH-Zugriff auf den Rechner mit der Datenbank gibt, oder zumindest auf einen Rechner der eine Verbindung zum Rechner mit der Datenbank aufbauen kann, könnte man eventuell auch die DB beziehungsweise deren Port per SSH tunneln. Ist einfacher und sicherer als eine Webanwendung zu basteln und die abzusichern.
“There will always be things we wish to say in our programs that in all known languages can only be said poorly.” — Alan J. Perlis
Antworten