Presence Detection in WLAN

Sunday, September 9. 2018

 I was not content with the network/pingdevice based presence detection openhab2 offers. While it finds and detects smarphones connect to the wlan, it soon gets beaten by some energy-savong sleep modes, so the device does not ping. It may still respond to arping, though.

The following script is used on a linux router with several interfaces where a device miight be reachable. It can be configured from a settings.ini, reads it's sc an targets from a different ini file and reports it's findings via openHab's REST api.

settings.ini:

[settings]
baseUrl=http://koel.intern:8080/rest/items/
interval=30
interfaces=eth0,eth2


baseUrl points to the REST api of the openHab2 server, interfaces is a comma separated list of the interface/s that should get scanned and interval gives the number of seconds the scanner will sleep between to scans

scan.ini:

[feng]
item=presence_feng
MAC=C0:EE:FB:43:A8:ED

[goldAmmer]
item=presence_goldammer
MAC=192.168.123.11


[ente]
item=presence_ente
MAC=192.168.123.99


Each device has its own section with the section name acting as the unique id of the device, item is the openHab item (expected to be a Switch) that should get the result (ON or OFF) and MAC has the MAC or IP of the device 

The scanner is done in python (2.7)
arpscan.py:

#!/usr/bin/env python
#############################

import ConfigParser
import string
import os
import subprocess
import time
import sys

conf={}
devices={}
interfaces=""
scantext=""
grace=3

def get_script_path():
   return os.path.dirname(os.path.realpath(sys.argv[0]))
        
def LoadConfig(file, config={}):
   """
   returns a dictionary with keys of the form
   <section>.<option> and the corresponding values
   """
   config = config.copy(  )
   cp = ConfigParser.ConfigParser(  )
   cp.read(file)
   for sec in cp.sections(  ):
       name = string.lower(sec)
       obj  = { }
       for opt in cp.options(sec):
           obj[string.lower(opt)] = string.strip(
               cp.get(sec, opt))
       config[name]=obj
   return config

def ScanThings():
       scantext=""
       for interface in interfaces:
           p = subprocess.Popen(["arp-scan","-l","-q","-r","3", "-I", interface], stdout=subprocess.PIPE)
           output, err = p.communicate()
           scantext += output
                
       for key in devices:
           device=devices[key]
           url=conf['settings']['baseurl']+device['item']
           if device["mac"].lower() in scantext.lower():
               print key+" ja"
               val = "ON"
               device["tap"]=0
           else:
               print key+" nein"
               device["tap"] = device.get("tap", 0) + 1
               if device["tap"] <= grace:
                   print key+" tap: "+str(device["tap"])
                   val="na"
               else:
                   val="OFF"
                    
           if val != "na":         
               os.system('/usr/bin/curl --silent --header "Content-Type: text/plain" --request POST --data '+val+' '+url)


conf=LoadConfig(get_script_path()+"/"+"settings.ini", conf)
devices=LoadConfig(get_script_path()+"/"+"scan.ini",devices)

interfaces=conf['settings']['interfaces'].split(",")


while 1:
   ScanThings()
   time.sleep(float(conf['settings']['interval']))

 

           

 

           

The script is most useful when run as a daemon. You can easily do this with systemd which uses .service files found in /lib/systemd/system/

arpscan.service:

[Unit]
Description=Run arpscan device detection
After=multi-user.target
 
[Service]
Type=simple
ExecStart=/usr/bin/python /usr/local/bin/presence/arpscan.py
Restart=on-abort
 
[Install]
WantedBy=multi-user.target

The scanner reads settings and scan data, runs an arpscan on each configured interface and finally searches the specified MAC (or IP, just one of the two is needed) in the result.
it needs the linux arp-scan package to be installed, which you can test and confirm by running

arp-scan -l -I eth0

from the commandline. It needs to be run as root.

Some devices may show up oscillating between on and off, to get more stable results the script waits for a number of fails before the "OFF" result is sent. This can be finetuned by the value for grace. 

 

FireFox Sync Server

Thursday, November 30. 2017

After years of Chromium as my default browser I've moved to give FF a new go after the release of v57 aka Quantum. Mozilla is still more trustworthy than Google and hey, I prefered Netscape over Mosaic once. Part of the shift was trying out FireFox sync. Since I have not much trust in the cloud unless it is my own server this meant installing Firefox Syncserver on my debian server.

I more or less followed the howto from sathya.de/blog/how-tos/setup-your-own-firefox-1-5-sync-server-on-debian-with-apache2-and-mysql/ , there are links to the mozilla docu included on his site. The basic steps are easy to take, with dns.he.net and letsencrypt with dehydrated setup of a new subdomain with a valid SSL-certificate has gone down to a matter of minutes.

Git clone the server, create a db and db-user, config the virtualhost in the web servers configuration, edit the default syncserver.ini, restart the webserver and then tell the clients in about:config which syncserver to use. Pretty basic, but it still has some potential for confusion and took me two runs to get it running.

the supplied syncserver.ini has an entry   public_url = http://localhost:5000/   which appears to suggest that ports should be defined in the server config, client config. But this is not so, in a production environment with https and a web server non ports are given.

I saw 404 errors in the error_log which stemmed from an error with the client config, I had erased the token/ - ppart of the uri. And I experienced a multitude of 500 server errors with traces in the error_log pointing at a line between Public_url = ... and allow_new_users = ...
The first complained the given secret exceeded the maximum length, while it had been created like the comments advise. Later I saw errors from parsing the sqluri. Many visual checks did not find a problem.

What helped me was a set of voodoo measures including: manually retyping the sqluri line  and  inserting a 'dummy = stupid' line supposed to catch any non-visible carried syntactical elements. Other possible sources of problems here include permissions issues and missing execution flag on the wsgi file.

And then, suddenly, it worked.

 

Xmind Mindmaps konvertieren

Monday, April 9. 2012

There is prior post regarding conversion of .xmind documents to other mind mapping formats and also re: import of text to Freemind, but now there is a new angle to those topics after I discovered a python modul which comes quite handy if you want to parse or create .xmind mindmaps. 

It goes by the name of mekk.xmind, authered by Marcin Kasperski. 

Download it with mercurial, (if you don't have this installed, you can get it on debian by apt-get install mercurial)

hg clone https://bitbucket.org/Mekk/mekk.xmind
cd mekk.xmind
python setup.py install

You will find some example scripts next to he sources and it worked fine here immediately..

Frustrated over Kontact I still search for a suitable way to handle task lists so that task administration integrates with my desktop thunderbird/lightning, google calendars and my androisd smartphone which nicely interfaces to google tasks. And as I like to structure my ideas with mindmaps and quite like XMind I tried to use XMind as a means to write structured task lists. Unfortunately lightning doesn't do subtasks. It has been asked for for years but not implemented.

Among the alternatives for task managment on linux there is  Task Coach (pro: open source, does sub tasks SyncML, file format XML, some export options, con: usage could need enhancement, export formats uncomplete, so after reimporting an exported task list it doesn't necessarily rebuild the same thing, supports iOS but no direct support for android). For android they recommend  Todo.txt and task coach exports and imports task lists in todo.tx - Format. 

Can't say I'm content with all this, it's more like a waypoint on my search. Below is a little python script that lets you excerpt a todo.txt task list from an xMind mindmap.

 

#!/usr/bin/python
# -*- coding: utf-8 -*-

# (c) 2012, Daniel Plaenitz
#
# depends on mekk.xmind by Marcin Kasperski
# hg clone https://bitbucket.org/Mekk/mekk.xmind
#

# python xmind2todo.py inputFile [outputFile]

from mekk.xmind import XMindDocument

from datetime import date
import sys
import codecs

output = u""

def parse_sheet(file_name):
    xmind = XMindDocument.open(file_name)
    sheet = xmind.get_first_sheet()
    root = sheet.get_root_topic()
    
    for topic in root.get_subtopics():
        parse_level(topic,[],root.get_title())


def parse_level(topic,names,rootTitle):
    global output
   
    _names = names[:]
    _names.append(topic.get_title())
    _line = u""
    
    for name in _names:
        _line += u" -> "+name
    
    _line = _line[4:]
    _line = " ".join(_line.split('\n'))
    
    _prio = u""
    if 'priority-1' in list(topic.get_markers()):
        _prio = "(A) "
    elif 'priority-2' in list(topic.get_markers()):
        _prio = "(B) "
    elif 'priority-3' in list(topic.get_markers()):
        _prio = "(C) "   
    elif 'priority-4' in list(topic.get_markers()):
        _prio = "(D) "
    elif 'priority-5' in list(topic.get_markers()):
        _prio = "(E) "
    elif 'priority-6' in list(topic.get_markers()):
        _prio = "(F) "
    
    if 'task-done' in list(topic.get_markers()):
        _prio = u"x " + _prio
        
    _line = _prio + date.today().isoformat() + " " + _line
    _line += " +"+rootTitle
    
    
    output += _line + "\n"
    
    for stopic in topic.get_subtopics():
        parse_level(stopic,_names,rootTitle)
        
def main(argv):
    global output

    if len(argv) < 1 :
        print "xmind2todo.py inputFile [outputFile]"
        sys.exit(2)  
        
    infilePath = argv[0]
    outfilePath =  infilePath+".txt"
    
    if (len(argv) > 1):
        outfilePath = argv[1]

    parse_sheet(infilePath)
        
    print "writing to "+outfilePath    
    f = codecs.open(outfilePath, encoding='utf-8', mode='w')
    f.write( output )
    
if __name__ == "__main__":
    main(sys.argv[1:])
 

 

denyhosts sync error

Sunday, January 29. 2012

 Nach einem dist-upgrade von lenny zu squeeze auf meinem root server stelle ich fest, dass denyhosts nicht mehr richtig funktioniert. 
Im denyhosts.log findet sich die Zeile:

sync        : ERROR    long int exceeds XML-RPC limits

und dem folgt eine Traceback. Es ist für debian ein bekannter bug und anscheinend auch gefixt und archiviert.
"Found in version denyhosts/2.6-6. Fixed in version denyhosts/2.6-10." Na super! Squeeze kommt mit 2.6.7 und da ist es nicht gefixt. 
Stattdessen fand ich in den ubuntu-Foren einen hilfreichen Beitrag mit einer filigranen Änderung an der auch im Trackback benannten /usr/share/denyhosts/DenyHosts/sync.py :

Die hat, bei mir auf Zeile 55/56, 
fp = open(os.path.join(self.__work_dir, SYNC_TIMESTAMP), "a")     und das ändern wir (als root) in:
fp = open(os.path.join(self.__work_dir, SYNC_TIMESTAMP), "w")

Dann noch das misshandelte Timestamp resetten:

date +%s > /var/lib/denyhosts/sync-timestamp

und fertig! 

 

Strukturierten Text -> Freemind

Friday, December 2. 2011

 Ein voriger Beitrag behandelte Wege, eine Mindmap aus Xmind ins Freemind-Format zu bekommen, diesmal wollte ich einen strukturierten Text einlesen. Im Grunde eine Gliederung, bis auf die oberste Überschrift - den root node der Mindmap - beginnt jede Zeile mit (mindestens) einem Tab. Zeilen mit einem Tab sind Unterpunkte der obersten Überschrift, haben sie selbst auch Unterpunkte, stehen diese unter ihnen und die Unterpunkte haben alle einen Tab mehr als ihr Oberpunkt. Entsprechend weiter... 

Das .mm - Format ist an sich sehr aehnlich, es ist ein XML, das binnen <map> </map> die Struktur als inneinander eingenistete <node /> abbildet. Ein node hat die Properties CREATED, MODIFIED, ID, TEXT und optional POSITION. Eigentlich wundert es mich, dass Freemind und seine Derivate (Freeplane zB.) keinen Import von Text direkt anbieten. Aber ich fand ein kleines Python-Tool, dass mir die Umwandlung erledigte: download

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