3. August 2023
HomeAssistant - after aprrox. 2 years usage
In an older post I found that I started home automation with OpenHAB.
A few days later I switched to HomeAssistant and I'm still using it.
Getting it up and running on a Raspi 4 was quite easy.
However, the complexity of integrating devices differs wildly.
My setup is now like this:
- Automatic backup to Google Drive
- Using the Mosquitto MQTT addon.
- Integrated several WiFi plugs, for some of them I flashed Tasmota myself (OTA), but the newer ones I bought pre-flashed in the UK. These include power sensors.
- Integrated ~ 20 ZigBee light bulbs (using a ConBee 2 USB stick), most of them bought from IKEA
- Integrated some ZigBee switches from IKEA.
- Integrated my SunGrow Solar system using modbus.
- Integrated my Vaillant heating and hot-water system using a tiny device which is connected to it via the proprietary "ebus" wire protocol and is connect to MQTT via WiFi. This device is magic!
- Using scenes for light settings
- Using very few automations (for switching on and off lights, switching on Spotify on the TV in the morning)
1. November 2021
Design flaws in Oracle PL/SQL and ideas how to fix them
Thinking about Oracle PL/SQL and SQL flaws and how they could be fixed
Background
First of all, let me say that I like Oracle PL/SQL.
I've been working with it since 1994, and it is remarkable that a programming language lasts such a long time.
Jist think of other languages that have come and gone in these decades.
In our company, there have been some constants more or less since I started in that year, and these were:
- The Oracle database
- SQL
- PL/SQL
- Java
- Python
- C#
A main reason why our company does not pick up new trends frequently is the kind of software that we are developing (a LIMS) and the kind of our customers (pharmaceutics, industry). The software has an impact to the way our customers are working comparable to e.g. SAP. Thus it intended to run for at least a decade when installed. Of course this doesn't mean there are no updates, but it means that we need a very reliable framework.
So most of the business logic was and still is implemented in PL/SQL, while the technologies for UI and reporting changed over time.
Reasons why I like PL/SQL
It is remarkable that AFAIK there have never been severe (e.g. security) problems with PL/SQL in all this time. This may be related to the fact that PL/SQL is somewhat "old-fashioned".
It's tight integration with SQL makes developing business logic for (Oracle) database applications very easy.
It supports NULL so in this regard it matches SQL exactly.
It is easy to learn and easy to read.
It is very stable and solid - I mean the running programs as well as its development over the decades.
What is not so good (but cannot be changed)
PL/SQL is quite verbose.
String processing is often cumbersome.
It counts 1-based and not 0-based (this matches SQL and saying this not so good is just my personal opinion).
The design flaws
From my experience, the following are serious design flaws which often cause hard-to-find errors.
Implicit type conversion
All string types can be assigned despite the maximum length
Maximum length and nullability of parameters and return type cannot be declared or accessed
Calling procedures/functions with a lot of parameters without =>
Lack of a type registry
Exception handling
Other issues
CAST does not allow %TYPE
Sometimes you want to use %TYPE more or less just as a SUBSTR to truncate a string to the maximum allowed length for a given column. Can't remember an example now, but I had this problem several times.
A similar problem was when declaring types (at schema level, not inside a package).
The prefix FX for date conversion is not the default
Labels: Oracle, PL/SQL, Programming, SQL
16. Oktober 2020
OpenHAB2 - Mühsam ernährt sich das Eichhörnchen
Einige Notizen (keine vollständige Anleitung: bin selbst Anfänger) zu OpenHAB2, inkl. Backup
- Die Software bringt ihre eigene Nomenklatur mit: Bindings, Services, Things, Items, Channels. Wozu das alles gut ist, habe ich bisher noch nicht verstanden.
- MQTT ist nicht ab Werk dabei und muss nachinstalliert werden
- Die Konfiguration ist völlig wir. Konfigurationsdateien? Gibt es an zwei verschiedenen Orten. Außerdem noch Paper UI für die Konfiguration. Wo speichert das die Konfiguration?
- Der Systemstart dauert extrem lange. 10 Minuten lang rödelt der Raspi auf 90% CPU, und das wo ich bisher nur eine Steckdose eingebunden habe...
Ich habe bisher manuell folgende Konfigurationsdateien angelegt:
Datei /etc/openhab2/items/kronocken.items
Datei /etc/openhab2/sitemaps/kronocken.sitemap
Diverse andere Dateien...
Backup und Restore
Windows-Seite
Skript backup.cmd
Skript restore.cmd
Linux-Seite
Datei /home/pi/.ssh/authorized_keys
Datei /home/pi/openhab-backup
Sinn und Zweck der Dateien
Labels: Backup, OpenHAB 2, scp, Smart Home, ssh
Echt hart - OpenHAB, Tuya-Convert, Raspi und WLAN-Steckdosen Luminea NX-4491-675
Mühsame Erfahrungen bei der Einrichtung von WLAN-Steckdosen und OpenHAB
Um was geht's?
Erste Erfahrungen mit Heimautomation - und das möglichst ohne Cloud.
Hardware:
Vorhanden waren bereits:
- Ein Raspberry Pi 2
- WLAN mit 2.4 GHz, in meinem Fall ein Mesh mit Firtzbox (auch 5 GHz, aber nur 2.4 GHz funktioniert für die SmartHome-Gadgets)
- Ein WLAN-USB-Stick von CSL (Mod. 27395) Dieser wird nur für die Installation und Konfiguration benötigt.
- LAN (WLAN sowieso)
- Handelsübliches Notebook/PC mit Windows
- Smartphone (in meinem Fall ein Huawei P10 lite mit Android)
- Ein 3er-Set WLAN-Steckdosen von Pearl (ca. 35€ inkl. Versand).
Bei Pearl werden diese als WLAN-Steckdose SF-450.avs verkauft.
Dahinter steckt ein Luminea NX-4491-675. Das sind die einfacheren (und kompakteren) Modelle ohne Strommesser. Inzwischen bietet Pearl unter derselben Bezeichnung anders aussehende Steckdosen an - ich nehme aber an, dass sie im Prinzip genauso funktionieren.
- Die Steckdosen sind mit Tuya-Convert geflasht (also von der China-Cloud befreit)
- und in OpenHAB mehr oder weniger eingerichtet
- Ein Backup- und Restore-Skript für OpenHAB
- Steckdosen können per Handy - nur von zu Hause aus - geschaltet werden.
Warum war das so mühsam?
- Steckdosen flashen mit Tuya-Convert (OTA : over the air - ohne das Gehäuse zu öffnen). Dazu wird ein Linux-System mit WLAN benötigt.
- Steckdosen konfigurieren
- OpenHAB einrichten
- MQTT bei OpenHAB einrichten (MQTT ist das Protokoll, mit dem u.a. diese Steckdosen gesteuert werden)
- Steckdosen bei OpenHAB konfigurieren
- Zum Flashen habe ich versucht, den Raspi mit WLAN-Stick zu verwenden. Das hat beim ersten Mal auch relativ problemlos funktioniert, danach aber nie wieder! Es traten dann wahlweise folgende Fehler auf (die zu absolut verwirrenden Fehlermeldungen führten):
- Der WLAN-Stick wurde nicht erkannt
- Das WLAN vtrust-wlan war gar nicht sichtbar auf dem Smartphone
- Das WLAN vtrust-wlan war sichtbar, man konnte sich aber mit dem Smartphone nicht damit verbinden
- Das Skript sagte immer wieder "Retrying"
- Da der Start von OpenHAB den Raspi für gut 10 Minuten extrem belastet, ist ein Reboot (um etwas anderes auszuprobieren) eine zeitraubende Angelegenheit.
- OpenHAB
- OpenHAB ist für Einsteiger alles andere als einfach.
- Die Konfiguration ist m.E. völlig wirr - Textdateien und/oder per GUI (wobei diese Einstellungen dann nicht in den Textdateien landen)
- Außerdem ist die Dokumentation, die man im Internet so findet, häufig veraltet und funktioniert so nicht mehr.
- Doofe Kleinigkeit: Ich hatte ein Leerzeichen in der SSID! Damit kommt die Tasmota-Software einfach nicht klar! Es gibt auch keinen Workaround. Daher habe ich die SSID in der Fritzbox umbenannt und einen kurzen Namen ohne Leerzeichen oder Sonderzeichen gewählt. Das zog natürlich diverse Nacharbeiten bei den dutzenden vorhandenen Geräten (Smartphones, Tablets, ...) nach sich.
- Den Raspi nur für OpenHAB verwenden, nicht für das Flashen.
- Zum Flashen besser ein Linux im VMWare Player verwenden.
So, jetzt noch einmal alles hintereinander in Kurzform
Vorüberlegungen
Benötigt werden zwei Dinge: Ein MQTT-Broker und die eigentliche SmartHome-Steuerungssoftware (in meinem Fall OpenHAB2).
Raspi installieren
Den Raspi konfigurieren
Dem Raspi eine feste IP zuordnen
MQTT Broker installieren
OpenHAB2 installieren
Steckdosen flashen
- Ein aktuelles Ubuntu Linux ISO-Image herunterladen
- Den VMWare Player herunterladen und installieren
- Mit dem VMWare Player das Linux als virtuelle Maschine installieren
- Das Ubuntu halbwegs brauchbar einrichten (z.B. deutsche Tastatur) und auf den aktuellen Stand bringen
- Im Ubuntu der Anleitung von https://github.com/ct-Open-Source/tuya-convert folgen - aber nur die Installation, nicht Flashen! Beim install-prereq.sh stattdessen mit sudo ./install-prereq.sh arbeiten
- Den WLAN USB-Stick einstöpseln und -nur- mit der Ubuntu VM verbinden (nicht mit dem Host)
- Im Ubuntu mit iw dev herausfinden, wie der WLAN-Stick im Linux heißt, und das in der Datei config.txt entsprechend anpassen (wlan0 durch den passenden Namen ersetzen).
- Jetzt erst sudo ./start_flash.sh ausführen. Am Anfang einmal yes [Enter], die Fragen ob dies und das gestoppt werden soll, mit y beantworten.
- Nun auf dem Smartphone mit dem WLAN vtrust-wlan verbinden. Falls das nicht funktioniert, abbrechen. Am besten den WLAN-Stick wieder herausziehen, die Ubuntu-VM neu booten und ab Schritt 6 nochmal von vorne versuchen.
- Jetzt die Steckose einstecken und durch langes Drücken der Taste in den Pairing-Modus versetzen (die LED blinkt dann)
- Dann erst Enter drücken.
- Bei der Frage welches ROM installiert werden soll, "tasmota.bin" auswählen und das Ganze nochmal bestätigen.
Steckdose konfigurieren
- Es erscheint eine Konfigurationsseite im Browser. Falls das nicht funktioniert, manuell zu http://192.168.4.1 wechseln
- Es erscheint die Startseite der Steckdose:
- Hier nun als allererstes die WLAN-Zugangsdaten bei "AP1 SSID" und "AP1 Password" eingeben und vor dem Drücken auf Save sehr sorgfältig kontrollieren. Nicht den Haken bei der Checkbox "AP1 Password" vergessen.
- Beim PC wieder mit dem normalen WLAN verbinden.
- Die Steckdose startet neu und ist nun im WLAN irgendwo sichtbar als "tasmota-irgendwas" oder aber über den im Konfigurationsdialog festgelegten Hostnamen. In meinem Fall kann man das über die FritzBox leicht herausfinden (es gibt sicher auch andere Möglichkeiten). Die verwendete IP-Nummer merken (z.B. 192.168.178.44).
- Diese IP im Browser aufrufen: http://192.168.178.44. Herzlichen Glückwunsch, die Steckdose ist im WLAN!
- Es erscheint wieder eine Konfigurationsseite.
Diese sieht "oben rum" zunächst noch nicht ganz so aus wie in diesem Screenshot: - "Configure WiFi" aufrufen und den Hostnamen ergänzen.
Der Hostname muss natürlich im lokalen Netzwerk eindeutig sein und (wenn ich die Überschrift bei dem Eingabefeld richtig deute) sollte er aus einem Text, Bindestricht und dann vier Ziffern bestehen. Keine Ahnung, ob das wichtig ist. Natürlich sollte der Hostname keine Sonderzeichen (außer Minus und Unterstrich) oder Leerzeichen enthalten. Auf "Save" drücken. - Wieder auf "Configuration" gehen und dort auf "Configure Template". Als Name für das Template etwas passendes auswählen. Ich habe "Luminea NX-4491-675" eingegeben, das wurde aber beim Speichern offensichtlich gekürzt. Als Basis für das Template Generic (18) auswählen. Falls man großes Glück hat, dann gibt es vielleicht schon ein passendes Template in der Werteliste (je nach Hardware). In dem Fall kann natürlich zunächst dieses Template ausprobieren (und den weiteren Verlauf abkürzen).
Was wir jetzt zu tun haben, ist, die logischen Ein-Ausgabe-Pins des Chips mit den richtigen Stellen zu verbinden - ohne diese Konfiguration bliebe die Steckdose "tot". - Dank eines YouTube-Videos ist es gar nicht so schwierig, das herauszufinden. In meinem Fall ist es eine einfache Steckdose, sie hat 1 LED und einen physikalischen Button. Richtigerweise brauchen wir also Zuordnungen für Button1, Led1 und Relay1 (das Relay ist der eigentliche Schalter). Zuerst setzen wir bei allen GPIOs die Zuordnung auf None (0). Dann schließen wir eine einfache Lampe an die Steckdose an (und stellen vorher an einer normalen Steckdose sicher, dass die Lampe leuchten sollte!). Nun setzen wir bei 4 GPIOs (z.B. 12, 13, 14, 15) die Zuordnung auf Relay1 .. Relay4 und drücken "Save".
Die Steckdose startet neu und zeigt wieder die Startseite an. Auf der Startseite gibt es jetzt 4x nebeneinander den Text "OFF" und darunter eine Schaltfläche "Toggle" (so ähnlich wie im Screenshot oben, aber eben 4x nebeneinander). Nun testet man, was mit der Steckdose und der Lampe passiert, wenn man auf die "Toggle" Schaltflächen klickt. Wenn bei einer der Schaltflächen (z.B. in meinem Fall bei der vierten) die Lampe an- bzw. ausgeht, dann wissen wir, dass die GPIO-Nummer eigentlich Relay 1 ist und nicht Relay 4. Mit etwas Glück (so wie in meinem Beispiel) stellen wir außerdem fest, dass - nur wenn die Lampe nicht leuchtet - eine der Schaltflächen die Kontroll-LED umschaltet.
Wir gehen wieder zum "Configure Template" Bildschirm und legen für die 4 getesteten GPIOs nun die richtige Zuordnung fest -wobei man statt Led1 auch LEd1i ("i" wie invertiert) verwenden kann, je nachdem wie man es lieber hat). Die übrigen GPIOs setzt man wieder auf None:
Danach klickt man auf "Save" und die Steckdose startet neu. Im Hauptmenü ist jetzt nur noch ein ON/OFF-Status und eine "Toggle"-Schaltfläche zu sehen. Diese kann man nun testen - sie sollte die Lampe ein- und ausschalten.
Auf ähnliche Weise (Button1 .. Button4 testweise unbenutzten GPIOs zuordnen) findet man heraus, welche GPIO-Nummer tatsächlich zu Button1 gehört. Dazu wechselt man nach dem Konfigurieren und Neustart zur "Console" und beobachtet, welche Nachricht (außer den Power-Nachrichten) ankommt, wenn man auf den Button klickt (wenn ich mich recht erinnere, im Youtube-Video ist das jedenfalls gut erklärt). - Es kann sein, dass man nach dem ersten Erstellen des Templates (Schritt 12) bei "Configure Module" zunächst einmal das neu angelegte Template (in meinem Fall "Luminea NX-449") auswählen muss.
- Unter "Configure Other" wählt man nun einen passenden Namen aus und achtet darauf, dass "MQTT Enable" angehakt ist:
- Es kann sein, das man nach dem ersten Erstellen des Templates (Schritt 12) bei "Configure Module" zunächst einmal das neu angelegte Template (in meinem Fall "Luminea NX-449") auswählen muss.
- Unter "Configure MQTT" wählt man jetzt (das ist wichtig, damit die Steckdose nicht nur über dieses Web-UI, sondern auch über MQTT angesprochen werden kann) die IP-Nummer des Raspi bzw. genauer gesagt die IP-Nummer des Rechners auf dem der MQTT-Broker läuft. Außerdem habe ich die Einträge Topic und Client beide auf einen einfachen eindeutigen Namen "steckdose1" geändert:
LED-Statusanzeige anpassen
- Die LED leuchtet, wenn der Strom aus ist (sozusagen als Mini-Nachtlicht) und die LED ist aus, wenn der Strom an ist.
- Die LED blinkt, wenn MQTT-Nachrichten empfangen werden.
MQTT testen
Mit einem Tool wie dem Google-Browser-Addon MQTTLens kann man nun beobachten, dass die Steckdose alle 5 Minuten ihren Status meldet. Dazu zuerst die Connection zum Raspi hinzufügen und dann das Topic "#" subscriben.Außerdem kann man mit "Publish" und dem Topic steckdose1/power und der Message on oder off die Steckdose ein- und ausschalten. Die richtigen Topics lassen sich herausfinden über die Menüs "Information" und "Console" der Steckdosen-Seite.
Meilenstein erreicht: MQTT funktioniert
Bis hierhin war es relativ unabhängig von der Heimautomations-Software OpenHAB 2. Der Rest der Konfiguration folgt in einem weiteren Artikel.Labels: MQTT, Smart Home, Tasmota
12. Februar 2011
Rusty's blog: Not doing much
(somebody has done that before, search for Alien Breed Wii on the web).
On ubuntu, when I run cross_compile_python.sh, somere during the build the script throws a lot of error messages and then hangs.
./configure.lineno: line 429: expr: command not found (many times), followed by ./configure.lineno: fork: Cannot allocate memory
Someone else had a similar problem in 2009, but didn't publish a solution: See
Rusty's blog: Not doing much from Sunday, 31 May 2009.
9. Dezember 2007
Panda3D - Spiegelungen
Schließlich sind die Gegner derzeit Pinguine, und da wäre eine spiegelglatte Eis-Oberfläche doch irgendwie passender als der Rasen.
Aber so ganz hab ich es noch nicht hingekriegt.
Es gibt 2 Probleme (das 2. ist sicherlich einfach zu lösen)
1. Problem:
Die Spiegelung funktioniert zwar im Prinzip, ist aber verzerrt und entspricht nicht den Erwartungen.
Die beigefügten Screenshots verdeutlichen das Problem. Die Spielfigur ist immer im Zentrum und wird optisch korrekt gespiegelt. Aber alles, was außerhalb des Zentrums ist, wird nicht richtig gespiegelt - man achte auf die Pinguine.
Außerdem ist die Pixelauflösung der Spiegelung zu gering (wenn ich es mit 1024x1024 statt 512x512 versuche, stürzt Panda3D ab - vermutlich unterstützt die Laptop-Grafikkarte das nicht. Was gibt es hier für Alternativen?
2. Problem:
Der gespiegelte Himmel ist zu dunkel, obwohl er weiß ist. Das liegt aber sicher daran, dass das AmbientLight zu dunkel ist und das DirectionalLight eben von oben nach unten scheint. Ich müsste also vermutlich eine Lichtquelle einbauen, die sich nur auf den Himmel auswirkt.
Der nächste Schritt wäre dann vielleicht noch eine Textur für den Himmel.
22. Oktober 2007
PDF 2-Up Script mit pyPdf
#!/bin/env/python
# -*- coding: utf-8 -*-
# Basiert auf der pyPdf-Library von Mathieu Fenniak
# Siehe http://pybrary.net/pyPdf
#
# (C) Henning von Bargen 2007
#
import os
import sys
import unittest
from cStringIO import StringIO
from pyPdf import PdfFileWriter, PdfFileReader
from reportlab.lib.units import cm, mm, inch
from reportlab.lib import pagesizes
from reportlab.pdfgen import canvas
from pyPdf.pdf import *
from pyPdf.generic import *
PAGESIZE = pagesizes.landscape(pagesizes.A4)
def framePage(canvas, title):
canvas.saveState()
canvas.setFont('Times-BoldItalic',12)
canvas.drawCentredString(PAGESIZE[0] * 1/2, 200*mm, title)
canvas.setFont('Times-Roman',10)
canvas.drawCentredString(PAGESIZE[0] * 1/4, 10*mm,
'Page %d' % (canvas.getPageNumber() * 2 - 1))
canvas.drawCentredString(PAGESIZE[0] * 3/4, 10*mm,
'Page %d' % (canvas.getPageNumber() * 2))
canvas.restoreState()
#canvas.doForm("frame") # Hier wäre Platz für ein Logo
def makeEmptyPagesPdf(nPages):
buffer = StringIO()
c = canvas.Canvas(None)
c.setPageSize(PAGESIZE)
#c.setPageCompression(0)
#c.setPageCallBack(pageCallBack)
#framePageForm(c) # define the frame form
c.showOutline()
for page in range(nPages):
print page
framePage(c, "This is a title")
c.showPage()
buffer.write(c.getpdfdata())
buffer.seek(0)
return buffer
def _pushTransformPopGS(contents, offset, scale, pdf):
# adds a graphics state "push" and "pop" to the beginning and end
# of a content stream. This isolates it from changes such as
# transformation matricies.
print "offset:",offset
print "scale:",scale
stream = ContentStream(contents, pdf)
stream.operations.insert(0, [[], "q"])
arr = [scale[0],0,0,scale[1],offset[0],offset[1]]
arr = [FloatObject(str(x)) for x in arr]
stream.operations.insert(1, [arr, "cm"])
stream.operations.append([[], "Q"])
return stream
def mergePage(page1, page2, offset, scale):
newResources = DictionaryObject()
rename = {}
originalResources = page1["/Resources"].getObject()
page2Resources = page2["/Resources"].getObject()
for res in "/ExtGState", "/Font", "/XObject", "/ColorSpace", "/Pattern", "/Shading":
new, newrename = PageObject._mergeResources(originalResources, page2Resources, res)
if new:
newResources[NameObject(res)] = new
rename.update(newrename)
# Combine /ProcSet sets.
newResources[NameObject("/ProcSet")] = ArrayObject(
ImmutableSet(originalResources.get("/ProcSet", ArrayObject()).getObject()).union(
ImmutableSet(page2Resources.get("/ProcSet", ArrayObject()).getObject())
)
)
newContentArray = ArrayObject()
originalContent = page1["/Contents"].getObject()
newContentArray.append(PageObject._pushPopGS(originalContent, page1.pdf))
page2Content = page2['/Contents'].getObject()
page2Content = PageObject._contentStreamRename(page2Content, rename, page1.pdf)
page2Content = _pushTransformPopGS(page2Content, offset, scale, page1.pdf)
newContentArray.append(page2Content)
page1[NameObject('/Contents')] = ContentStream(newContentArray, page1.pdf)
page1[NameObject('/Resources')] = newResources
def write2Up(infname, outfname):
output = PdfFileWriter()
input = PdfFileReader(open(infname, "rb"))
nPages = input.getNumPages()
print "nPages=",nPages
nNupPages = int((nPages+1)/2.0)
print "nNupPages=",nNupPages
emptyPages = PdfFileReader(makeEmptyPagesPdf(nNupPages))
page = None
nupindx = 0
for indx in range(nPages):
print "processing page", indx
if indx % 2 == 0:
if page is not None:
output.addPage(page)
print "nupindx=",nupindx
page = emptyPages.getPage(nupindx)
nupindx += 1
mergePage(page, input.getPage(indx),
(PAGESIZE[0]/2 * (indx%2), 0), # offset x,y
(0.7, 0.7) # scaling x,y
)
if page is not None:
output.addPage(page)
outputStream = open(outfname, "wb")
output.write(outputStream)
if __name__ == "__main__":
infname = sys.argv[1]
if len(sys.argv) > 2:
outfname = sys.argv[2]
else:
outfname = os.path.splitext(infname)[0] + "-new.pdf"
write2Up(infname, outfname)
10. September 2007
Einen habich noch: Python 2-Zeiler
Es gab aber keine Möglichkeit, Dateien zu kopieren.
Nur die Zwischenablage ging, und das auch nur für Texte.
Aber Python ist dein Freund:
- Dateien zusammenzippen zu datei.zip,
- in Python: import uu; uu.encode(open("datei.zip","rb"), open("datei.txt","wt"))
- Die Datei datei.txt dann per Editor und Zwischenablage blockweise (ging nur bis 64K) auf den Zielrechner kopieren.
- Auf dem Zielrechner in Python: import uu; uu.decode(open("datei.txt","rt"), open("datei.zip","wb"))
- Und dann datei.zip wieder entpacken.
Das Zippen habe ich mit WinZip gemacht, aber auch das wäre leicht mit Python gegangen. Ich musste mir nur leider in weniger als 10 Minuten eine Lösung einfallen lassen, und da geht Geschwindigkeit eben vor Eleganz.
Da hat Python mir den Tag gerettet...
Dies und Das
Mein Favorit derzeit ist MoinMoin (http://www.moinmoin.wikiwiweb.de/).
Erstens, weil's in Pure Python programmiert ist.
Zweitens, weil es leicht erweiterbar ist.
Drittens schon wegen des freundlichen Namens, bei dem ich gleich an Urlaub denken muss...
Und viertens, es kommt ohne Datenbank aus.
Zur Musik: Hab gerade im Radio "I've been Everywhere" von Jackie Leven gehört (vom Album "Oh What A Blow That Phantom Dealt Me!" - das scheint auch so eine Art Tribut an Johnny Cash zu sein. Klasse!
Rund um Python:
Thread-Vererbung will ich in Python 2.6 einbauen, wenn ich denn mal Zeit dafür habe...
Panda3D: (http://www.panda3d.org/) hat ne neue Version rausgebracht und mein PC hat jetzt ne Karte mit Bump Mapping. Mein 3D-Pacman-Klon mit dem guten deutschen Arbeitstitel "Arbeitstitel-Mann" ist gut spielbar, aber auch da fehlt mir die Zeit. Zuletzt habe ich es immerhin geschafft, eine Highscore-Liste einzubauen. Außerdem "Einbahnstraßen-Extras". Nächste Schritte sind Bump-Mapping, noch mehr Extras und dann natürlich intelligentere Gegner. Ein echter Netzwerkmodus wär auch schön, steht aber auf der Liste weit hinten (zu zweit an einem Rechner macht es doch am meisten Spaß). Werde es wohl mal veröffentlichen, wenn ich dazu komme - dazu fehlt vor allem noch eine Installationsanleitung.
Die Silbentrennung (http://deco-cow.sourceforge.net/) könnte auch mal wieder Pflege vertragen, wenn mal wieder Zeit ist:
* sauberere Vererbung von RL-Standardklassen
* Integration des anscheinend ziemlich guten hunspell (http://hunspell.sourceforge.net/).
14. Juni 2007
Früher...
Google ist dein Freund - tatsächlich, das Lied war im Original von "House of Love", einer Band, die damals vor langer Zeit (ca. 1988) als eine von den Manchester-Bands bezeichnet wurde.
Ach war das schön damals... Ich hab sie noch im Dortmunder Live Station gesehen - ein Konzert in fast familiärer Athmosphäre war das.
Und YouTube ist auch dein Freund - dort findet man tatsächlich ein Video von House of Love - Shine On. Sehr schön auch "Man To Child".
Und in der Bücherei hab ich nochmal zwei alte Romane von der Scheibenwelt ausgegraben, die aus heutiger Sicht noch irgendwie unfertig wirken:
- "Das Licht der Phantasie" mit einem frühen Auftritt von Rincewind (die Hauptfigur, mit der ich am wenigsten anfangen kann) und Zweiblum.
- und "Das Erbe des Zauberers", das im Original mit "Equal Rites" viel witziger tituliert ist, mit einer Oma Wetterwachs, die ohne Nanny Ogg nur halb so gut ist.
Aber ich konnte mich halt an die Bücher nur noch sehr schwach erinnern...
Programmieren mit Panda
Im Prinzip war es ein Refactoring.
Zunächst wollte im zweiten Level die Steuerung nicht funktionieren, und es waren irgendwelche Phantom-Objekte vorhanden.
Beides lag daran, dass ich die Objekte im Baum "render" und die Tasks im globalen TaskManager "taskMgr" beim Ende des ersten Levels bzw. beim Start des nächsten Levels nicht richtig gelöscht bzw. neu angelegt hatte.
Die einfachen Befehl
render.ls()
print taskMgr
haben aber sehr dabei geholfen, dass rauszufinden - obwohl mir immer noch nicht klar ist, warum Tasks, die ich soeben entfernt habe, von print taskMgr immer noch aufgelistet werden.
Weil Python so viel Spass macht und ich das händische Kopieren leid war, habe ich mir auch noch gleich ein kleines Backupskript backup.py angelegt:
#!/bin/env python
# -*- coding: iso-8859-1 -*-
import os
import os.path
import glob
import time
import shutil
dateStr = time.strftime("%Y%m%d-%H%M%S")
thisDir = "."
backupDir = os.path.join (thisDir, "backup", dateStr)
try: os.mkdir(backupDir)
dateien = os.listdir(thisDir)
numFiles = 0
for datei in dateien:
fullname = os.path.join (thisDir, datei)
if os.path.isfile(fullname):
base, ext = os.path.splitext(datei)
if ext not in [".log", ".py", ".obj"]:
print "Datei gesichert:", datei
shutil.copy2 (os.path.join (thisDir, datei), backupDir)
numFiles += 1
else:
print "Unterverzeichnis ignoriert:", datei
_ = raw_input("Backup erledigt (%d Dateien gesichert)" % numFiles)
except:
import traceback
traceback.print_exc()
_ = raw_input ("Backup konnte nicht komplett durchgeführt werden!")
12. Juni 2007
Spieleprogrammierung mit Panda3D
Als erste Übung will ich jetzt mal eine Art 3D-PacMan bauen, um mal in das Ganze so reinzukommen.
Aller Anfang ist schwer, aber jetzt habe ich den Punkt erreicht, wo ein Programm grundsätzlich funktioniert und es scheinbar immer einfacher wird, neue Features einzubauen.
Immerhin findet mein Sohn (7) das Spiel schon so schön, dass er ständig quengelt weil er spielen möchte.
Am meisten Zeit ist bisher für die Collision Detection draufgegangen, weil die zwar grundsätzlich funktioniert, dann aber scheinbar etwas falsche Werte zurückliefert, so dass z.B. der Avatar leicht in die Wände hinein rennt und dann zu weit zurückprallt und sich die Gegner ständig in den Ecken "verheddert" haben.
Für die Gegner habe ich es inzwischen durch gute alte handgestrickte einfachste Geometrie-Routinen in den Griff gekriegt, und beim Avatar stört es nicht ganz so sehr.
Über den weiteren Fortschritt werde ich hier gelegentlich berichten.
Multi-Threading ist böse - Multi-Threading is evil
Bei Python gibt es da zwei spezielle Probleme:
- Im Gegensatz zu Java lassen sich Threads nicht im Notfall "abschießen".
- Wenn der Haupt-Thread endet, läuft das Programm trotzdem weiter -quasi scheintot - es sei denn, man arbeitet für alle Worker-Threads explizit mit setDaemon(true). Der Prozess wird dann beendet, sobald nur noch Daemon-Threads übrig sind.
Neben diesen Python-spezifischen Problemen gab es am Anfang die üblichen Schwierigkeiten mit
- Race Conditions bei konkurrienden Zugriffen, die zu falschen Ergebnissen führen,
- so dass schließlich an allen Ecken und Enden ein explizites Locking erforderlich war.
- Durch ungeschicktes Locking (unterschiedliche Reihenfolge) kam es dann gelegentlich zu Deadlocks, die schwierig zu finden waren (letzten Endes konnte ich dann aber programmatisch zur Laufzeit Warnungen bei möglichen Deadlocks ausgeben).
Viel gemeiner und bei weitem schwieriger zu entdecken waren aber die Probleme, die durch Race Conditions auftreten können, wenn ein Thread gerade eine Datei schreibt und ein anderer Thread in dieser Zeit einen Unterprozess startet, z.B. mit dem subprocess-Modul. Der Unterprozess erbt dann das Datei-Handles (unter Windows), woran natürlich niemand denkt. Dadurch ist die Datei für Windows quasi so lange zum Schreiben geöffnet, wie der Unterprozess läuft - z.B. kann der Prozess, der die Datei eigentlich geschrieben hat, die Datei anschließend nicht mit os.rename umbenennen. (Ja ja, ans zwischenzeitliche close habe ich gedacht).
Die Ursache ist die, dass alle Handles an Unterprozesse vererbt werden, wenn es nicht für das Handle explizit ausgeschaltet wird. Jedes Handle mit dem normalen open(...) wird also vererbt.
Als Workaround muss dann anstelle von outf = open (fname, "w") der Aufruf outf = os.fdopen (os.open (fname, os.O_WRITE + os.O_NOINHERIT + ....), "w") verwendet werden.
12. September 2006
Die Gelehrten der Scheibenwelt
Ehrlich gesagt war es das erste Buch von Terry Pratchett, das mir nicht gefallen hat. Dieser Mischmasch aus Erklärung unserer Rundwelt und dann wieder 3 Seiten Roman, was die Zauberer der Unsichtbaren Universität so treiben - das ist nichts Halbes und nichts Ganzes.
Der populärwissenschaftliche Teil ("Lügen für Kinder") an und für sich ist ja noch ganz nett gemacht, aber die Romankapitel sind einfach nur langweilig - das gab es bei Terry Pratchett eigentlich noch nie.
Und außerdem störe ich mich gelegentlich an der deutschen Übersetzung, aber das ist sicherlich Geschmackssache. Ich finde eben "L-Space" einfach netter als "B-Raum".
Tagebuch führen ist gar nicht so einfach
Wenn ich sie selbst schon nicht finde, wie soll dann jemand anderes die finden?
Jetzt habe ich mal Titel hinzugefügt. Ob's was hilft? Ich glaube eher nicht.
Weder Zeit noch Lust gehabt. Das Leben rauscht nur so vorbei.
Eigentlich wollte ich diesen Sommer einen Artikel über Python und Oracle für http://otn.oracle.com schreiben, aber auch dafür hat die Zeit nicht gereicht.
Stattdessen nehme ich nochmal meine Silbentrennung (http://deco-cow.sourceforge.net) in Angriff.
Reportlab hat ja jetzt alles auf Unicode umgestellt, arbeitet aber anscheinend intern mit utf8. Das mag die Silbentrennung gar nicht. Im Moment funktioniert daher fast gar nichts.
Mal überlegen, ob ich die Silbentrennung intern auch komplett auf Unicode umstelle und vielleicht nebenbei auch die ganze Regelverarbeitung ändere, oder ob ich umgekehrt lieber alles was an die Silbentrennung geht, nach iso-8859-1 codierere und auf dem Rückweg wieder nach utf8.
Alles nicht so einfach.
1. September 2005
Python und Windows Dienste (Services)
(Python and Windows Services)
Unsinnigerweise sendet Windows an manche Programme ("Konsolenprogramme") ein Signal (CTRL_LOGOFF_EVENT), wenn sie als Dienst gestartet sind und sich ein Benutzer abmeldet. Welcher Benutzer sich abmeldet, erfährt das Programm aber nicht.
Wer also vorhat, Windows-Dienste mit Python zu entwickeln, aufgepasst!
(Dasselbe gilt übrigens auch für JAVA)
Bestandteil des Tests sollte folgender Testfall sein:
- Dienst läuft korrekt.
- Benutzer meldet sich an
- Benutzer meldet sich wieder ab
- Von außen prüfen, ob der Dienst immer noch korrekt läuft.
Windows sendet ein CTRL_LOGOFF_EVENT.
Python steckt gerade in einem time.sleep oder wait oder was auch immer.
Das Programm (in Multi-Threaded-Programmen nur der Hauptthread!!!) läuft dann auf eine Exception IOError: Interrupted System Call.
Alle Versuche, mit Windows-APIs wie SetConsoleCtrlHandler was zu machen, haben in meinem Fall nicht geholfen.
Tief in den Python-Bibliotheken versteckt (im Quelltext von timemodule.c) sieht man, dass die Implementierung von sleep mit MsgWaitForSingleObject arbeitet und eine Exception schmeißt, wenn dabei kein Timeout auftritt.
Besonders ärgerlich ist es, wenn dann bei Multi-Threading-Programmen der Hauptthread endet, die anderen Threads aber weiter laufen (oder kann man da generell was machen?),
und das Programm dann irgendwie in der Luft hängt.
Lösung/Solution:
Bei allen Vorkommen von time.sleep, evt.wait() etc (im Hauptthread des Programms) mit einem IOError rechnen und diesen abfangen:
try:
time.sleep(1)
except IOError:
log.warn ("Error in sleep - probably caused by CTRL_LOGOFF_EVENT")