I live at a place with a well developed public transport network, within 600 meters from my home there are 1 subway station, 2 city train stations, 3 bus-stops - so there are many alternatives and at times it is not easy to decide which way to turn when I leave my home to get the fastest connection.

They have those info boards at many stations with a count down until the next bus or train arrives. I wanted such a thing for myself, with all the nearby stations on it to check on a glance. And as I build my own smarthome I naturally wanted it to be a part of that.

Well, it can be done:

to build this it took:

  • an access key and some documentation to the database of public transport. VBB, the regional public transport org, gives it out for free if asked nicely. I assume other transport organisations will have something comparable.
  • a daemon running on a computer in the own network which fetches the data from the database, I did that in python
  • mosquitto running on a server in the home network to communicate the json to the smarthome
  • rules and items in openHab2 to receive, parse and format the connection data
  • HTML to format and display the connections into a habPanel page like the image above shows

 Now, the details:

  • The python code to fetch the data from the database:
# -*- coding: utf-8 -*-

# 18.5.2019, v0.1

import RPi.GPIO as GPIO
import time
import os
from datetime import datetime
from datetime import timedelta
import urllib, json
import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import sys




selection=[u'900060101N',u'900054105O', u'900054105U',u'900060107N',u'900060153N',u'900061153N',u'900060101S',u'900054105W',u'900060107S',u'900060153S',u'900061153S']

# mqtt



def reset_halten():
        global halten
        u'900060101N':{'lat': 52.47, 'lon': 13.340594, 'dist': 348, 'walk':5.0, 'dir':u'900600003', u'dep':[], u'to':'hin', 'name': u'S Friedenau (Nord)', 'extId': u'900060101'},
        u'900060101S':{'lat': 52.47, 'lon': 13.340594, 'dist': 348, 'walk':5.0, 'dir':u'900063101', u'dep':[], u'to':'her', 'name': u'S Friedenau (Süd)', 'extId': u'900060101'},
        u'900054105O':{'lat': 52.478099, 'lon': 13.342878, 'dist': 599, 'walk':8.0, 'dir':u'900054104', u'lines':'S42,S46,S45', u'dep':[], u'to':'hin', 'name': u'S Innsbrucker Platz (Ost)', 'extId': u'900054105'},
        u'900054105W':{'lat': 52.478099, 'lon': 13.342878, 'dist': 599, 'walk':8.0, 'dir':u'900044202', u'lines':'S41,S46,S45', u'dep':[], u'to':'her', 'name': u'S Innsbrucker Platz (West)', 'extId': u'900054105'},
        u'900054105U':{'lat': 52.478099, 'lon': 13.342878, 'dist': 599, 'walk':8.0, 'dir':u'900055101', u'dep':[], u'to':'hin', 'name': u'U Innsbrucker Platz', 'extId': u'900054105'},
        u'900060107N':{'lat': 52.47525, 'lon': 13.34313, 'dist': 280, 'walk':3.5, 'dir':u'900054105', u'dep':[], u'to':'hin', 'name': u'Cecilieng\xe4rten (Nord)', 'extId': u'900060107'},
        u'900060107S':{'lat': 52.47484, 'lon': 13.34302, 'dist': 230, 'walk':3.1, 'dir':u'900060104', u'dep':[], u'to':'her', 'name': u'Cecilieng\xe4rten (Süd)', 'extId': u'900060107'},
        u'900060153N':{'lat': 52.47086, 'lon': 13.34385, 'dist': 210, 'walk':2.8, 'dir':u'900054105', u'dep':[], u'to':'hin', 'name': u'Rubensstr. (Nord)', 'extId': u'900060153'},
        u'900060153S':{'lat': 52.46971, 'lon': 13.34377, 'dist': 350, 'walk':4.8, 'dir':u'900060104', u'dep':[], u'to':'her', 'name': u'Rubensstr. (Süd)', 'extId': u'900060153'},
        u'900061153N':{'lat': 52.47591, 'lon': 13.34091, 'dist': 550, 'walk':5.7, 'dir':u'900054105', u'dep':[], u'to':'hin', 'name': u'H\xe4hnelstr. (Nord)', 'extId': u'900061153'},
        u'900061153S':{'lat': 52.47531, 'lon': 13.33969, 'dist': 550, 'walk':5.7, 'dir':u'900061105', u'dep':[], u'to':'her', 'name': u'H\xe4hnelstr. (Süd)', 'extId': u'900061153'}

        halten[u'callTime']=datetime.now().strftime('%Y-%m-%d %H:%M:%S')

############### MQTT section ##################

# when connecting to mqtt do this;

def on_connect(client, userdata, flags, rc):
        #print("Connected with result code "+str(rc))

# when receiving a mqtt message do this;

def on_message(client, userdata, msg):
        global boostCount,boost
        message = str(msg.payload)
        print(msg.topic+" "+message)

        if message == "boost":

def on_publish(mosq, obj, mid):
        #print("mid: " + str(mid))

def publishDict(obj):
        json_string = json.dumps(obj)

def publishBanner(obj):
        json_string = json.dumps(obj)

def checkOnce():
        global inProgress
        if inProgress:

def getAllTables():
        global selection,banners
        for halteKey in selection:

def doNothing():

def getTableByHaltekey(halteKey):
        global halten, baseurl,suffix,duration,toScreen,banners

        reqTime=datetime.now() + timedelta(minutes=walk)
        datum = reqTime.strftime('%Y-%m-%d')
        zeit  = reqTime.strftime("%H:%M")
        datetimeFormat = '%Y-%m-%d %H:%M:%S'

        banner = "{} {} m {} min. Weg".format( halte[u'name'],halte[u'dist'],int(round(halte[u'walk'])) )
        #banners += banner + '\r\n'
        if toScreen:
                print "\n"+banner
                #print "{} {} m {} min. Weg".format( halte[u'name'],halte[u'dist'],round(halte[u'walk']) )

        # fuer die Ringbahn werden, wie im Witz, Richtungen nicht unterschieden...
        if u'lines' in halte:
                uri = baseUrl+"departureBoard?extId={}&date={}&time={}&rtMode=FULL&duration={}&direction={}&lines={}".format(extId,datum,zeit,duration,direction,lines)+suffix
                uri = baseUrl+"departureBoard?extId={}&date={}&time={}&rtMode=FULL&duration={}&direction={}".format(extId,datum,zeit,duration,direction)+suffix

        # im debug die uri ausgeben
        if toScreen:
                #print uri

        response = urllib.urlopen(uri)
        dat = json.loads(response.read())

        if u'Departure' in dat:
                if len(data)==0:

        # kann sein, dass rtTime in einzelnen records fehlt, und manche records sind unplausibel
        for recnum in range(len(data)):
                if u'rtTime' in record:
                        # verspätung als rtTime-time
                        planTime=record[u'date']+' '+record[u'time']
                        realTime=record[u'rtDate']+' '+record[u'rtTime']
                        lateSec=datetime.strptime(realTime, datetimeFormat)-datetime.strptime(planTime, datetimeFormat)
                        late=int(round(lateSec.seconds / 60.0) )

                        # ha! record ohne rtTime, rtDate -> diese aus time, date kopieren

                # ausgefallene Züge (?) bleiben im Suchergebnis stehen, obwohl ihre Zeit längs um wäre
                leaveTime=record[u'rtDate']+' '+record[u'rtTime']
                diff=datetime.strptime(leaveTime, datetimeFormat)-datetime.now()
                diffMin=int(round((diff.seconds / 60.0) - walk))
                if diffMin>100:
                        print "strange time: {}  rtTime: {} late: {} noch: {}".format(record[u'time'],record[u'rtTime'],record[u'late'],diffMin)
                if diffMin<-10:
                        print "spooky  time: {}  rtTime: {} late: {} noch: {}".format(record[u'time'],record[u'rtTime'],record[u'late'],diffMin)

                # Busse, die zu früh kommen, haben 1440 min offset
                if record[u'late'] > 1000:
                        record[u'late'] -= 1440

        for recnum in reversed(delRecs):
                del data[recnum]

        dat=sorted(data, key = lambda i: (i['rtDate'], i['rtTime']))

        for pos in [0,1]:
                if pos==1 and len(dat)==1:


                leaveTime=useDate+' '+useTime
                diff=datetime.strptime(leaveTime, datetimeFormat)-datetime.now()
                diffMin=int(round((diff.seconds / 60.0) - walk))
                # fix the occasional 1437 diffMin (instead of -3)
                if diffMin > 100:
                        diffMin -= 14400

                splitter = useTime.split(':')
                useTime = splitter[0]+':'+splitter[1]

                aline = "   {} {} {} {} noch: {} late: {}".format( par[u'name'].lstrip(),par[u'direction'],useDate,useTime,diffMin,par[u'late'])
                banner = banner + "
" + aline

                if toScreen:
                        print aline

                zug={u'name':par[u'name'].lstrip(),u'direction':par[u'direction'],u'date':useDate,u'time':useTime,u'left':diffMin,u'late':par[u'late'] }
                #banners += banner +'

# mqtt
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.on_publish = on_publish
client.connect(mqBroker, 1883, 90)

        # loop
        while 1:
                if boost or (slapCount % noBoostMin == 0):

                        if boostCount>=boostMin:
                        print "boost: {} boostCount: {}".format(boost,boostCount)

                slapCount += 1

        print 'bye'

  • openHab items to receive or send data to/from mosquitto:
String mqVbb "incoming" { mqtt="<[mqttBroker:ext/vbb/timetable/state:state:default]" }  
String mqVbbCmd "outgoing" { mqtt=">[mqttBroker:ext/vbb/timetable/cmd:state:*:default]" } 
  • openHab items to obtain the parsed data; this is trivial and not shown
  • an openHab rule to parse and format the connection info into dedicated items (only the first part of it is shown)
rule "rule triggered  by mqVbb changed"
    Item  mqVbb changed 

    var String halte=""
    var String station=""
    var String zug1=""
    var String zug2="" 
    var String noch1=""
    var String noch2=""

    var String json = (mqVbb.state as StringType).toString
    halte="900060101N" // S Friedenau Nord
    station = transform("JSONPATH", "$['"+halte+"'].name", json) + " (" + transform("JSONPATH", "$['"+halte+"'].dist", json) + "m / " + transform("JSONPATH", "$['"+halte+"'].walk", json) + " min) "
    zug1= transform("JSONPATH", "$['"+halte+"'].dep[0].name", json) +" -> " + transform("JSONPATH", "$['"+halte+"'].dep[0].direction", json) +": " +transform("JSONPATH", "$['"+halte+"'].dep[0].time", json)+" ("+ transform("JSONPATH", "$['"+halte+"'].dep[0].late", json)+") "
    zug2= transform("JSONPATH", "$['"+halte+"'].dep[1].name", json) +" -> " + transform("JSONPATH", "$['"+halte+"'].dep[1].direction", json) +": " +transform("JSONPATH", "$['"+halte+"'].dep[1].time", json)+" ("+ transform("JSONPATH", "$['"+halte+"'].dep[1].late", json)+") "
    noch1=transform("JSONPATH", "$['"+halte+"'].dep[0].left", json)
    noch2=transform("JSONPATH", "$['"+halte+"'].dep[1].left", json)

    if (zug1.length()<100){vbbHalte1Zug1.sendCommand(zug1);vbbHalte1Noch1.sendCommand(noch1)}else{vbbHalte1Zug1.sendCommand(" ");vbbHalte1Noch1.sendCommand(" ")}
    if (zug2.length()<100){vbbHalte1Zug2.sendCommand(zug2);vbbHalte1Noch2.sendCommand(noch2)}else{vbbHalte1Zug2.sendCommand(" ");vbbHalte1Noch2.sendCommand(" ")}

    halte="900060101S" // S Friedenau Süd
    station = transform("JSONPATH", "$['"+halte+"'].name", json) + " (" + transform("JSONPATH", "$['"+halte+"'].dist", json) + "m / " + transform("JSONPATH", "$['"+halte+"'].walk", json) + " min) "
    zug1= transform("JSONPATH", "$['"+halte+"'].dep[0].name", json) +" -> " + transform("JSONPATH", "$['"+halte+"'].dep[0].direction", json) +": " +transform("JSONPATH", "$['"+halte+"'].dep[0].time", json)+" ("+ transform("JSONPATH", "$['"+halte+"'].dep[0].late", json)+") "
    zug2= transform("JSONPATH", "$['"+halte+"'].dep[1].name", json) +" -> " + transform("JSONPATH", "$['"+halte+"'].dep[1].direction", json) +": " +transform("JSONPATH", "$['"+halte+"'].dep[1].time", json)+" ("+ transform("JSONPATH", "$['"+halte+"'].dep[1].late", json)+") "
    noch1=transform("JSONPATH", "$['"+halte+"'].dep[0].left", json)
    noch2=transform("JSONPATH", "$['"+halte+"'].dep[1].left", json)

    if (zug1.length()<100){vbbHalte2Zug1.sendCommand(zug1);vbbHalte2Noch1.sendCommand(noch1)}else{vbbHalte2Zug1.sendCommand(" ");vbbHalte2Noch1.sendCommand(" ")}
    if (zug2.length()<100){vbbHalte2Zug2.sendCommand(zug2);vbbHalte2Noch2.sendCommand(noch2)}else{vbbHalte2Zug2.sendCommand(" ");vbbHalte2Noch2.sendCommand(" ")}

and finally some HTML to go into the template widget of a HabPanel page:

<div align="left" class "row">

  <div class="col-sm-6"><span style="color: red; font-size: 12pt">{{itemValue('vbbHalte1')}} </span>
      <li>  <span style="color: orange; font-size: 9pt">{{itemValue('vbbHalte1Zug1')}} </span><span style="color: cyan; font-size: 10pt">{{itemValue('vbbHalte1Noch1')}} </span></li>
      <li>  <span style="color: orange; font-size: 9pt">{{itemValue('vbbHalte1Zug2')}} </span><span style="color: cyan; font-size: 10pt">{{itemValue('vbbHalte1Noch2')}} </span></li>

  <div class="col-sm-6"><span style="color: red; font-size: 12pt">{{itemValue('vbbHalte2')}} </span>
      <li>  <span style="color: orange; font-size: 9pt">{{itemValue('vbbHalte2Zug1')}} </span><span style="color: cyan; font-size: 10pt">{{itemValue('vbbHalte2Noch1')}} </span></li>
      <li>  <span style="color: orange; font-size: 9pt">{{itemValue('vbbHalte2Zug2')}} </span><span style="color: cyan; font-size: 10pt">{{itemValue('vbbHalte2Noch2')}} </span></li>

Again this is just the start of it and those lines are repeated for all the stations and their items.

This is all just Alpha and there's room left for improvements, But 'm glad it works!

My little thing uses but a tiny subset of what functionality the api offers

Host your own doodles with jawanndenn

Thursday, March 14. 2019

For a website where event dates are offered to a group of users who need to find those dates that most users can attend, doodles are a good solution. There are well known providers for that sort of thing like doodle.com/, dudle.inf.tu-dresden.de/ or www.dfn.de/dienstleistungen/dfnterminplaner/, with the latter two offering better privacy for european users (no transfer of data to servers in the USA).

Still, hosting your very own service is an attractive alternative. For this there is jawanndenn.de/  which can be downloaded from github.com/hartwork/jawanndenn. It is a web application written in python by Sebastian Pipping, libre software.

Installation is easy with

pip install jawanndenn

and then run it from the command line with something like

jawanndenn --host your-domain.tld --port 23456  (the port is arbitrary).
navigate your browser to
https://your-domain.tld:23456 and create your first own poll.

You see the log entries written out on the console. This is nice for testing but to actually use jawanndenn you'll need something more stable than a manually started programm.

As my web server runs with apache this is how I did it:

- set up a subdomain dedicated for the application, like doodle.your-domain.tld

- get certificates for it from Let's Encrypt (i.e. using github.com/lukas2511/dehydrated )

- create a folder for document root of the new subdomain and put a file app.wsgi and a favicon.ico there, the former you'll find at github.com/hartwork/jawanndenn/blob/master/jawanndenn/app.wsgi , the latter can be done with touch as we won't see that icon anyways but it saves 404 log entries to have a file named like it.

- make sure that apache2 has mod_wsgi enabled

- set up a virtual host for it in apache2/sites-available. Example:

#  doodle.your-domain.tld

# subdomain dedicated for running the jawanndenn web application
# don't separate .conf and .common since this will lead to errors when both :80 and :443 try to set
# a WSGIDaemonProcess with the same name.
# Don't include processes=1 into the WSGIDaemonProcess definition for this will lead to errors "Single process needed"

<VirtualHost :80>
#       include sites-available/doodle.
your-domain.tld.common ## this won't do, see above
        ServerName doodle.

        WSGIDaemonProcess doodle user=web123 group=web123 threads=5
        WSGIScriptAlias / /var/www/web123/doodle/app.wsgi

    <Directory /var/www/web123/doodle>
        WSGIProcessGroup doodle
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all

   CustomLog /var/log/apache2/doodle.
your-domain.tld.access.log combined
   ErrorLog  /var/log/apache2/doodle.your-domain.tld.error.log



ServerName doodle.your-domain.tld

    # supply a different name for the WSGIDaemonProcess on :443
    WSGIDaemonProcess doodler user=web123 group=web123 threads=5
    WSGIScriptAlias / /var/www/web123/doodle/app.wsgi

    <Directory /var/www/web123/doodle>
        WSGIProcessGroup doodler
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all

   CustomLog /var/log/apache2/doodle.your-domain.tld.access.log combined
   ErrorLog  /var/log/apache2/doodle.your-domain.tld.error.log

        SSLEngine on

        SSLCertificateFile      /etc/dehydrated/certs/
        SSLCertificateKeyFile   /etc/dehydrated/certs/
        SSLCertificateChainFile /etc/dehydrated/certs/


Now restart apache2, weed out typos and your done. You create new doodles from doodle.your-domain.tld,
make note of the handle of the new poll and embed it on a web site with an iframe like

<div class="dudel">
<iframe src="https://doodle.your-domain.tld/poll/a9108dcfa12006ce3c229d6f0110c5f976df14e963ddd9b7ffc8618013a0bd7e"
        width="100%" height="600" frameborder="0"
        allowfullscreen >
  <p> <a href="https://developer.mozilla.org/en-US/docs/Glossary">
    Fallback link for browsers that don't support iframes
  </a> </p>


I had no luck trying to use sandbox with the iframe, YMMV


Now, is that secure?

Do a little exploration with the 'View Web Source' feature of your browser to see all the relevant URIs and handles visible in plain text. Who ever gets to that page with the doodles can do shyte like

  • create 'unofficial' doddle polls
  • post spam as the name of a doodle
  • what the heck

Thus, we do have the security feature of 'data never leave my own site'. Which is cool.
All the rest of your desired security you will have to provide on your own.

Now, can I edit a vote? As it comes jawanndenn does not support any editing of votes which is regarded a security feature but makes doodles pretty useless in real life cause people do change opinion all the time. You cannot manually edit the stored votes cause they are stored in a binary format that's hard to access.

What you can do is add that functionality by yoursel like modify the vote() method of the _Poll object to replace the prior vote with the new vote if the name is exactly the same. 


dash ruft smartHome

Wednesday, March 7. 2018

Und wie ?

das Teil hat eine recht kleine Batterie und Amazon hat die Button deshalb so ausgelegt, dass sie nur nach dem Knopfdruck kurz booten, ins WLAN gehen, ihre Message abgeben und sich dann wieder runterfahren.
Bei der Ersteinrichtung lernt das dash die SSID und das Passwort für das Wlan, aber die IP holt es sich jedesmal vom dhcpd.
Und dabei kann man den Button erwischen.

Im Prinzip konfigurierst du dir den dash erst mal so, wie Amazon vorgibt. (Wähle dir einen dash, der Bestellungen ~5€ von Produkten erlaubt, die du gebrauchen kannst. Du bekommst nicht 4,99 erstattet sondern auf die erste Bestellung bis zu 4,99€ Rabatt. Zu billig darf das Produkt also nicht sein) 
Und ja, einmal bestellen, um den Rabatt zu kassieren. (Persönlich finde ich, dass A. da ausreichend 'Ne-doch-nich'-Schwellen und Kindersicherungen eingebaut hat. )
Dann setzt du den Dash zurück und gehst den Weg noch einmal, aber den letzten Schritt: Produktwahl, lässt du aus. Schon hast du einen Dash, der funktioniert, aber nicht richtig bestellen kann. An sich genau, was wir wollen. A. will das nicht und schickt pro Klick auf den dash eine Mail...

Jetzt ist es an der Zeit, in /var/log/daemon.log (oder wo bei dir der dhcpd seine logs ablegt) nachzuschauen, welche MAC der dash benutzt. Und für die MAC legt du eine feste IP an. 
Mit WireShark kannst du nun zuschauen, was der dash mit A. so zu besprechen hat. Und dann machst du in der firewall genau diese Verbindung zu. Aus ist's mit den Mails.

So, jetzt fehlt eigentlich nur noch die Hauptsache. Etwas muss im WLAN die Fühler hochhalten und die dhcp-requests des Dash erschnüffeln. python mit scapy können sowas. Wie genau, tja, da gibt es einen Rüstungswettlauf zwischen A. und Technikforschern. Also googeln und durchprobieren. (siehe unten)

Letztlich hast du etwas, das nach ~3 sec einen event auf deinem smarthome*-Bus auslösen kann, dann ~10 Sekunden Unerreichbarkeit (der dash versucht verzweifelt, A. zu erreichen und schmollt anschliessend noch etwas.)

Alles in allem ein konkurrenzlos günstiger Wifi-Switch mit heftiger Latenz.

 Bei mir tut dies:

from scapy.all import *
import sys, os

def arp_display(pkt):
    if pkt.haslayer(DHCP):

        if pkt[Ether].src == "fc:65:de:bb:xx:yy": 
            os.system('/usr/bin/curl --silent --header "Content-Type: text/plain" --request POST --data "ON"')

        if pkt[Ether].src == "fc:a6:67:cc:xx:yy": 
            os.system('/usr/bin/curl --silent --header "Content-Type: text/plain" --request POST --data "ON"')

print sniff(prn=arp_display, store=0,count=0)

und damit das als daemon läuft und auch nach einem Neustart des raspi wieder läuft, habe ich es als einen service bei systemd angelegt:

Description=dash dhcp Paket Sniffer                                                                                                                                                              
ExecStart=/usr/bin/python /var/local/scripts/dash/dashSniffer.py                                                                                                                                               


Xmind to object

Monday, November 19. 2012

Eine Powerpoint-Folie als Vorlage um dann mit dem Editor ein mehrfach verschachteltes object in javascript-Notation zusammenzuschreiben, und trotz aller Umsicht ist bei copy&paste-Aktionen dann doch irgendwo ein Komma oder eine Klammer verloren gegangen - eine Aufgabe, die mich recht schnell zu der Überlegung führt, dass da ja wohl etwas nicht stimmt, wenn ich als Mensch versuche, was die Maschine doch wirklich besser kann: 
stur und unablenkbar genau eine langweilige, umfangreiche Routine abzuarbeiten.

Die erste Idee - ein script, dass aus einer .pptx direkt  das gewünschte javascript-Object erzeugt - lasse ich schnell wieder fallen, zu gross die Variabilität des Inputs und noch dazu ist das XML der .pptx ist ein wenig abschreckend geraten...

Stattdessen entscheide ich mich für ein freundliches Zwischenformat, und das ist für mich eine Xmind - mindmap. Ein besonders freundlicher Zug dieses Formates ist die Tatsache, dass es dank MEKK mit python (2.x) sehr einfach ist, mindmaps zu generieren oder zu parsen. 

Wie man MEKK unter debian installiert, habe ich in einem vorigen Artikel schon aufgeschrieben, diesmal unter cygwin auf win7 war es nicht so sehr anders: mercurial und gcc mit setup installieren, hg clone https://bitbucket.org/Mekk/mekk.xmind, im ersten Durchlauf beklagt hg noch dass fehlen einiger Pakete (libxml und libxslt in dev-Version) und dann steht das MEKK-Modul bereit. 

Die Umwandlung der powerpoint-Folien in Xmind-Maps habe ich mir als zweiten Schritt aufgehoben, hier erstmal das Script mon der Map zum Objekt. Als frühe alpha ist es ein wenig beschränkt, was die Verschachtelungstiefe anbelangt, weil es mein Anwendungsfall so wollte, geht das Script von einer Struktur aus, wo unter dem Wurzelknoten zwei Hierarchiebenen vorgesehen sind, die optional noch childNodes haben  können, während für die dritte Ebene diese Möglichkeit nicht mehr erwartet wird.   Das kann man sich aber gegebenenfalls anpassen.

Mit dem Aufruf    python x2o.py test.xmind   gestartet erzeugt das Script ein HTML-Dokument, dessen Name teils von dem geparsten .xmind bestimmt wird. Im beiliegenden Beispiel lautet der Text des Wurzelknotens "project" und die erzeugte Datei heisst test_project.html.

Das HTML-Dokument enthält das generierte js-object, mit einem Editor kann man es sich herauspicken, mit einem Browser auf syntaktische Korrektheit pruefen. Wenn das Objekt funktioniert, gibt das .html abschliessend mit einem alert("Es geht!" Bescheid, wenn man es im fireFox öffnet, zeigt der Alert gleich die source des object an.

Download: x2o.zip

OpenAstro.org auf Precise Pangolin

Thursday, September 6. 2012

 Ein länger schon abgelegter Laptop sollte mit Linux als Surf-Rechner eingerichtet werden, mit der Musiksammlung und als dritte Anforderung ein Astrologie-Programm.

Es wurde Mint Maya mit Cinnamon Desktop, unter der Haube steckt da ein Ubuntu 12.04 Precise Penguin. Die Musiksammlung fügte sich mühelos zu banshee und meine anfängliche Sorge, dass es mit dem vorwiegend benutzten Audioformat .wma Ärger geben könnte war ganz unbegründet, Banshee weiss es locker zu nehmen.

Surfen - zu FireFox noch Chrome und Opera installiert, fertig. Aber nun das Astrologie-Programm...
Synaptics fand gleich gar nichts passendes, eine Google-Suche fand mir einen Artikel zum Thema und wiederum verwies mich auf www.openastro.org
Es handelt sich um Python-Programm, wohl unter Ubuntu entwickelt und es gibt sowohl ein Ubuntu repository als auch Pakete zum Download für Ubuntu Maverick, Natty und Oneiric, die Version ist 1.1.25 vom 28.9.2011. Für Precise ist nichts vorbereitet, man muss selbst Hand anlegen.

Es gibt eine Anleitung zur Installation auf Generic Linux, aber sie funktioniert nicht ganz. Deshalb hier die Schritte, die hier zum Erfolg führten:

mkdir /tmp/openastro
cd  /tmp/openastro
apt-get install python-gtk2 python-dateutil python-rsvg python-cairo imagemagick python-dev
wget https://launchpad.net/~pellesimon/+archive/+files/openastro.org_1.1.25.orig.tar.gz
tar -xzf openastro.org_1.1.25.orig.tar.gz
sudo ./setup.py install

Als Ergebnis liegt nun in /usr/local/bin/openastro.py das eigentlich Programm, dessen Ressourcen stehen unter /usr/local/share/openastro.org
Soweit, so gut, nur dass das Programm selbst seine Ressourcen stattdessen unter /usr/share/openastro.org/ vermutet, also einen Fehler wirft statt zu starten. Man kann nun entweder einen symlink von /usr/local/share/openastro.org nach /usr/share/openastro.org legen oder, wie ich es gemacht habe, dem Programm bei der Pfadkonstruktion etwas nachhelfen.

Dazu öffnet man /usr/local/bin/openastro.py und kommentiert Zeile 52 aus und fügt danach diese Zeile ein:

Dann geht's.


(Page 1 of 2, totaling 8 entries) » next page