#!/usr/bin/python3.6 -s
# mqtt2stationmaintd - Update station information with the last status
# received from MQTT maint rootpath
#
# Copyright (C) 2021  Paolo Patruno <p.patruno@iperbole.bologna.it>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Author: Paolo Patruno <p.patruno@iperbole.bologna.it>

OVERWRITE_DATE=False
DELAYSEC=3
CLOSEOLDCONNECTIONSEC=300

import os,sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'rmap.settings'
import django
django.setup()

import signal
import re
import json
from django.utils import timezone
import paho.mqtt.client as mqtt
import logging,traceback
from rmap.stations.models import StationMaintStatus,StationMetadata,Board,BoardMaintStatus
from datetime import datetime
from django.contrib.auth.models import User, Group #,Permission
from django.db.models import Q
from django.template.loader import render_to_string
from django.core.mail import send_mail
from django.contrib.sites.models import Site
from django.db import close_old_connections
from django.utils.translation import ugettext as _

from rmap import daemon
import rmap.settings

TOPIC_RE_V0 = re.compile((
    r'^.*/'
    r'(?P<user>[^/]+)/'
    r'(?P<lon>[0-9-]+),'
    r'(?P<lat>[0-9-]+)/'
    r'(?P<rep>[^/]+)/'
    r'(?P<pind>[0-9]+|-),'
    r'(?P<p1>[0-9]+|-),'
    r'(?P<p2>[0-9]+|-)/'
    r'(?P<lt1>[0-9]+|-),'
    r'(?P<lv1>[0-9]+|-),'
    r'(?P<lt2>[0-9]+|-),'
    r'(?P<lv2>[0-9]+|-)/'
    r'(?P<var>B[0-9]{5})$'
))

TOPIC_RE_V1 = re.compile((
    r'^1/[^/]+/'
    r'(?P<user>[^/]+)/'
    r'(?P<ident>[^/]*)/'
    r'(?P<lon>[0-9-]+),'
    r'(?P<lat>[0-9-]+)/'
    r'(?P<rep>[^/]+)/'
    r'(?P<pind>[0-9]+|-),'
    r'(?P<p1>[0-9]+|-),'
    r'(?P<p2>[0-9]+|-)/'
    r'(?P<lt1>[0-9]+|-),'
    r'(?P<lv1>[0-9]+|-),'
    r'(?P<lt2>[0-9]+|-),'
    r'(?P<lv2>[0-9]+|-)/'
    r'(?P<var>B[0-9]{5})$'
))

class MyMQTTClass(mqtt.Client):


    def parse_topic_v0(self,topic):
        match = TOPIC_RE_V0.match(topic)
        if match is None:
            logging.info("SKIP topic V0: {}".format(topic))
            return None
        else:
            g = match.groupdict()
            return {
                "user": None if g["user"] == "-" else g["user"],
                "ident": None,
                "lon": int(g["lon"]),
                "lat": int(g["lat"]),
                "rep_memo": g["rep"],
                "level": (
                    None if g["lt1"] == "-" else int(g["lt1"]),
                    None if g["lv1"] == "-" else int(g["lv1"]),
                    None if g["lt2"] == "-" else int(g["lt2"]),
                    None if g["lv2"] == "-" else int(g["lv2"]),
                ),
                "trange": (
                    None if g["pind"] == "-" else int(g["pind"]),
                    None if g["p1"] == "-" else int(g["p1"]),
                    None if g["p2"] == "-" else int(g["p2"]),
                ),
                "var": g["var"],
            }


    def parse_topic_v1(self,topic):
        match = TOPIC_RE_V1.match(topic)
        if match is None:
            logging.info("SKIP topic V1: {}".format(topic))
            return None
        else:
            g = match.groupdict()
            return {
                "user": None if g["user"] == "" else g["user"],
                "ident": g["ident"],
                "lon": int(g["lon"]),
                "lat": int(g["lat"]),
                "rep_memo": g["rep"],
                "level": (
                    None if g["lt1"] == "-" else int(g["lt1"]),
                    None if g["lv1"] == "-" else int(g["lv1"]),
                    None if g["lt2"] == "-" else int(g["lt2"]),
                    None if g["lv2"] == "-" else int(g["lv2"]),
                ),
                "trange": (
                    None if g["pind"] == "-" else int(g["pind"]),
                    None if g["p1"] == "-" else int(g["p1"]),
                    None if g["p2"] == "-" else int(g["p2"]),
                ),
                "var": g["var"],
            }

        
    def parse_payload(self,payload):
        return json.loads(payload)


    def parse_message(self,topic, payload):

        logging.info("parse message: {} {}".format(topic,payload))

        if topic.startswith("1/"):
            msg = self.parse_topic_v1(topic)
        else:
            msg = self.parse_topic_v0(topic)
        
        if msg is None:
            return None
        try:
            p = self.parse_payload(payload)
        except json.decoder.JSONDecodeError:
            logging.error("invalid payload: {}".format(payload))
            return None
            
        try:
            msg["stationstatus"]={}
            msg["stationstatus"]["value"] = p["v"]
            msg["stationstatus"]["majorversion"] = p.get("s")
            msg["stationstatus"]["minorversion"] = p.get("m")
        except:
            msg.pop("stationstatus")
            logging.info("this is not a maint message for station status")
            logging.debug(traceback.format_exc())
        try:
            msg["boardstatus"]={}
            msg["boardstatus"]["boardslug"] = p["bs"]                
            msg["boardstatus"]["statusb"] = int(p["b"],2)                
        except:
            msg.pop("boardstatus")
            logging.info("this is not a maint message for board status")
            logging.debug(traceback.format_exc())
        try:
            msg["boardstatus"]["statusv"] = p.get("c")
        except:
            logging.info("statusv missed in maint message for board status")
            logging.debug(traceback.format_exc())
            
            
        if all([
                msg["level"] != (None, None, None, None),
                msg["trange"] != (None, None, None)]):
            if "t" in p:
                msg["datetime"] = datetime.strptime(p["t"], "%Y-%m-%dT%H:%M:%S")
            else:
                msg["datetime"] = timezone.now()
        else:
            msg["datetime"] = None

        return msg

    def on_connect(self,client, userdata, flags, rc):
        for topic in userdata["topics"]:
            logging.info("subscribe to topic: {}".format(topic))
            client.subscribe(topic,qos=userdata.get("qos",0))

    def on_disconnect(self,client, userdata, flags):
        logging.info("disconnected!")


            
    def on_message(self,client, userdata, message):
        
        if userdata["logging"]:
            logging=userdata["logging"]            

        try:

            # clear and skip retained messages !
            if message.retain != 0:
                logging.info("Clearing retained message: {} {}".format(message.topic,message.payload.decode("utf-8")))
                (rc, mid) = self.publish(message.topic, None, 1, True)
                return
                
            m = self.parse_message(message.topic, message.payload.decode("utf-8"))
            logging.debug("message {}".format(m))
            if m is None:
                return

            if all([
                m["level"] != (None, None, None, None),
                m["trange"] != (None, None, None),
                userdata["overwrite_date"],
            ]):
                m["datetime"] = timezone.now()
                
            logging.debug(">>>>>>>>>>>>>>>>>>>>>>>>")
            logging.debug(m["datetime"])
            logging.debug(m["rep_memo"])
            logging.debug(m["user"])
            logging.debug(m["ident"])
            logging.debug(m["lon"])
            logging.debug(m["lat"])
            logging.debug(m["level"])
            logging.debug(m["trange"])
            logging.debug(m["var"])


            if all([
                m["level"] == (265, 0, None, None),
                m["trange"] == (254, 0, 0),
                m["var"] == "B01213",
                    ]):

                try:
                    mystation = StationMetadata.objects.get(network=m["rep_memo"]
                                                            ,lon=m["lon"]/100000.
                                                            ,lat=m["lat"]/100000.
                                                            ,user__username=m["user"]
                                                            ,ident=m["ident"] )

                    mypreviouslaststatus=None
                    
                    if ('stationstatus' in m):
                        if hasattr(mystation, 'stationmaintstatus'):

                            logging.info("update maint information to station {} {} {} {} {}: {} {} {} {}".format
                                         (mystation.network
                                          ,mystation.lon
                                          ,mystation.lat
                                          ,mystation.user
                                          ,mystation.ident
                                          ,m["stationstatus"]["value"]
                                          ,m["stationstatus"]["majorversion"]
                                          ,m["stationstatus"]["minorversion"]
                                          ,m["datetime"])                                
                                         )

                            mypreviouslaststatus=mystation.stationmaintstatus.laststatus
                            mystation.stationmaintstatus.laststatus    = m["stationstatus"]["value"]
                            mystation.stationmaintstatus.lastupdate    = m["datetime"]
                            mystation.stationmaintstatus.firmwaremajor = m["stationstatus"]["majorversion"]
                            mystation.stationmaintstatus.firmwareminor = m["stationstatus"]["minorversion"]
                            if (m["stationstatus"]["value"] == "conn"):
                                mystation.stationmaintstatus.save(force_update=True)
                            else:
                                mystation.stationmaintstatus.save(update_fields=['laststatus','lastupdate'])

                        else:

                            logging.info("add maint information to station {} {} {} {} {}: {} {} {} {}".format
                                         (mystation.network
                                          ,mystation.lon
                                          ,mystation.lat
                                          ,mystation.user
                                          ,mystation.ident
                                          ,m["stationstatus"]["value"]
                                          ,m["datetime"]                            
                                          ,m["stationstatus"]["majorversion"]
                                          ,m["stationstatus"]["minorversion"])
                                         )

                            laststatus=m["stationstatus"]["value"]
                            mypreviouslaststatus=laststatus

                            sms=StationMaintStatus(station=mystation,laststatus=laststatus, lastupdate=m["datetime"],
                                                   firmwaremajor=m["stationstatus"]["majorversion"], firmwareminor=m["stationstatus"]["minorversion"])
                            sms.save()

                    myboard=None
                    mypreviousboard=None
                    if ('boardstatus' in m):

                        try:
                            myboard = Board.objects.get(slug=m["boardstatus"]["boardslug"]
                                                        ,stationmetadata=mystation)
							
                        except Board.DoesNotExist :
                            logging.error("board not present on DB; ignore it: {}".format(m["boardstatus"]["boardslug"]))
                    
                        if hasattr(myboard, 'boardmaintstatus'):

                            logging.info("update maint information date {} to station {} {} board {}".format
                                         ( m["datetime"]
                                           ,mystation.user
                                           ,mystation.slug
                                           ,myboard.slug)                                
                                         )

                            myboard.boardmaintstatus.lastupdate     = m["datetime"]

                            for bit,statusb in ((0, "statusb1" ),  (1,"statusb2" ), (2, "statusb3" ), (3, "statusb4" )
                                            ,   (4, "statusb5" ),  (5,"statusb6" ), (6, "statusb7" ), (7, "statusb8" )
                                            ,   (8, "statusb9" ),  (9,"statusb10"), (10,"statusb11"), (11,"statusb12")
                                            ,   (12,"statusb13"), (13,"statusb14"), (14,"statusb15"), (15,"statusb16")):

                                exec("myboard.boardmaintstatus."+statusb+"  = bool((m[\"boardstatus\"][\"statusb\"] >> "+str(bit)+") & 1)")


                            if (m["boardstatus"]["statusv"] is not None):
                                
                                for ele,statusv in ((0, "statusv1" ),  (1,"statusv2" ), (2, "statusv3" ), (3, "statusv4")
                                                    ,   (4, "statusv5" ),  (5,"statusv6" ), (6, "statusv7" ), (7, "statusv8")):
                                    if (ele == len(m["boardstatus"]["statusv"])): m["boardstatus"]["statusv"].append(None)
                                    exec("myboard.boardmaintstatus."+statusv+" =  m[\"boardstatus\"][\"statusv\"]["+str(ele)+"]")

                            test=bool((m["boardstatus"]["statusb"] >> 0) & 1)
                            logging.info("test {}".format(test))

                            mypreviousboard = Board.objects.get(slug=m["boardstatus"]["boardslug"]                            
                                                        ,stationmetadata=mystation)
                            repr(mypreviousboard.boardmaintstatus)  # this is required to evaluate the one to one related object !
			    
                            myboard.boardmaintstatus.save()
                            
                        else:

                            logging.info("add maint information date {} to station {} {} board {}".format
                                         ( m["datetime"]
                                           ,mystation.user
                                           ,mystation.slug
                                           ,myboard.slug)                                
                                         )

                            bms=BoardMaintStatus(board=myboard, lastupdate=m["datetime"])

                            for bit,statusb in ((0, "statusb1" ),  (1,"statusb2" ), (2, "statusb3" ), (3, "statusb4" )
                                            ,   (4, "statusb5" ),  (5,"statusb6" ), (6, "statusb7" ), (7, "statusb8" )
                                            ,   (8, "statusb9" ),  (9,"statusb10"), (10,"statusb11"), (11,"statusb12")
                                            ,   (12,"statusb13"), (13,"statusb14"), (14,"statusb15"), (15,"statusb16")):
                                exec("myboard.boardmaintstatus."+statusb+"  = bool((m[\"boardstatus\"][\"statusb\"] >> "+str(bit)+") & 1)")

                            if (m["boardstatus"]["statusv"] is not None):
                                for ele,statusv in ((0, "statusv1" ),  (1,"statusv2" ), (2, "statusv3" ), (3, "statusv4")
                                                    ,   (4, "statusv5" ),  (5,"statusv6" ), (6, "statusv7" ), (7, "statusv8")):

                                    if (ele == len(m["boardstatus"]["statusv"])): m["boardstatus"]["statusv"].append(None)
                                    exec("myboard.boardmaintstatus."+statusv+"  =  m[\"boardstatus\"][\"statusv\"]["+str(ele)+"]")

                            bms.save()
                            
                except StationMetadata.DoesNotExist :
                    logging.error("user/station not present on DB; ignore it: {} {} {} {}".format
                                  (m["rep_memo"]
                                   ,m["lon"]/100000.
                                   ,m["lat"]/100000.
                                   ,m["user"]
                                   ,m["ident"]))

                try:
                    self.changeStatusSendMail(mystation,mypreviouslaststatus,myboard,mypreviousboard)
                except:
                    logging.error("error sending email for status change")	
                    logging.error(traceback.format_exc())
			      
            else:
                logging.info("Ignore maint message {} {}".format(message.topic, message.payload.decode("utf-8")))

        except Exception:
            logging.error(traceback.format_exc())

            
    def changeStatusSendMail(self,mystation,mypreviouslaststatus,myboard,mypreviousboard):

        changed_laststatus=False
        changed_statusb=None
        changed_statusv=None
        diff=False

        if (not myboard is None and not mypreviousboard is None):
            changed_statusb=[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
            changed_statusv=[False,False,False,False,False,False,False,False]
            logging.info("board status {} {}".format(mypreviousboard.boardmaintstatus.statusb1,myboard.boardmaintstatus.statusb1))

            if (mypreviousboard.boardmaintstatus.statusb1  != myboard.boardmaintstatus.statusb1):
                diff=True
                changed_statusb[0]=True
                
            if (mypreviousboard.boardmaintstatus.statusb2  != myboard.boardmaintstatus.statusb2):
                diff=True
                changed_statusb[1]=True
                
            if (mypreviousboard.boardmaintstatus.statusb3  != myboard.boardmaintstatus.statusb3):
                diff=True
                changed_statusb[2]=True
                    
            if (mypreviousboard.boardmaintstatus.statusb4  != myboard.boardmaintstatus.statusb4):
                diff=True
                changed_statusb[3]=True
        
            if (mypreviousboard.boardmaintstatus.statusb5  != myboard.boardmaintstatus.statusb5):
                diff=True
                changed_statusb[4]=True
        
            if (mypreviousboard.boardmaintstatus.statusb6  != myboard.boardmaintstatus.statusb6):
                diff=True
                changed_statusb[5]=True

            if (mypreviousboard.boardmaintstatus.statusb7  != myboard.boardmaintstatus.statusb7):
                diff=True
                changed_statusb[6]=True
        
            if (mypreviousboard.boardmaintstatus.statusb8  != myboard.boardmaintstatus.statusb8):
                diff=True
                changed_statusb[7]=True
        
            if (mypreviousboard.boardmaintstatus.statusb9  != myboard.boardmaintstatus.statusb9):
                diff=True
                changed_statusb[8]=True
        
            if (mypreviousboard.boardmaintstatus.statusb10 != myboard.boardmaintstatus.statusb10):
                diff=True
                changed_statusb[9]=True
        
            if (mypreviousboard.boardmaintstatus.statusb11 != myboard.boardmaintstatus.statusb11):
                diff=True
                changed_statusb[10]=True
        
            if (mypreviousboard.boardmaintstatus.statusb12 != myboard.boardmaintstatus.statusb12):
                diff=True
                changed_statusb[11]=True
        
            if (mypreviousboard.boardmaintstatus.statusb13 != myboard.boardmaintstatus.statusb13):
                diff=True
                changed_statusb[12]=True
        
            if (mypreviousboard.boardmaintstatus.statusb14 != myboard.boardmaintstatus.statusb14):
                diff=True
                changed_statusb[13]=True
        
            if (mypreviousboard.boardmaintstatus.statusb15 != myboard.boardmaintstatus.statusb15):
                diff=True
                changed_statusb[14]=True
        
            if (mypreviousboard.boardmaintstatus.statusb16 != myboard.boardmaintstatus.statusb16):
                diff=True
                changed_statusb[15]=True
        

            if (mypreviousboard.boardmaintstatus.statusv1 != myboard.boardmaintstatus.statusv1):
                diff=True
                changed_statusv[0]=True
            
            if (mypreviousboard.boardmaintstatus.statusv2 != myboard.boardmaintstatus.statusv2):
                diff=True
                changed_statusv[1]=True
        
            if (mypreviousboard.boardmaintstatus.statusv3 != myboard.boardmaintstatus.statusv3):
                diff=True
                changed_statusv[2]=True
            
            if (mypreviousboard.boardmaintstatus.statusv4 != myboard.boardmaintstatus.statusv4):
                diff=True
                changed_statusv[3]=True
        
            if (mypreviousboard.boardmaintstatus.statusv5 != myboard.boardmaintstatus.statusv5):
                diff=True
                changed_statusv[4]=True
        
            if (mypreviousboard.boardmaintstatus.statusv6 != myboard.boardmaintstatus.statusv6):
                diff=True
                changed_statusv[5]=True
        
            if (mypreviousboard.boardmaintstatus.statusv7 != myboard.boardmaintstatus.statusv7):
                diff=True
                changed_statusv[6]=True
        
            if (mypreviousboard.boardmaintstatus.statusv8 != myboard.boardmaintstatus.statusv8):
                diff=True
                changed_statusv[7]=True
        if mypreviouslaststatus is not None:
            if (mypreviouslaststatus != mystation.stationmaintstatus.laststatus):
                if (mypreviouslaststatus != "disconn" and mystation.stationmaintstatus.laststatus != "disconn"):
                    diff=True
                    changed_laststatus=True            
                    if (mystation.stationmaintstatus.laststatus == "ERROR 01"):
                        mystation.stationmaintstatus.laststatus = "Unexpected MQTT disconnection"
                
                
        if (not diff):
            logging.info("no difference no mail")
            return	   		       

        context={"station":mystation,"board":myboard,"url":"http://"+Site.objects.get(id=rmap.settings.SITE_ID).domain,
                 "changed_statusb":changed_statusb,
                 "changed_statusv":changed_statusv,
                 "changed_laststatus":changed_laststatus}


        # station is setted for email monitoring
        if (hasattr(mystation, "stationmaintstatus")):
            if (mystation.stationmaintstatus.monit_status_with_email):
        
                # work with permissions
                #perm = Permission.objects.get(codename='maint_mail_alert')  
                #users = User.objects.filter(Q(groups__permissions=perm) | Q(user_permissions=perm) ).distinct()
                #recipient_list = list(i for i in users.values_list('email', flat=True) if bool(i))

                # work with groups
                # collect users in "status alert recipients" group
                users = Group.objects.get(name="status alert recipients").user_set.all()
                recipient_list = list(i for i in users.values_list('email', flat=True) if bool(i))


                # work with user
                # add user owner of the station if the user in 'status email recipients' group 
                if (mystation.user.is_active):
                    if mystation.user.groups.filter(name='status email recipients').exists():
                        recipient_list.append(mystation.user.email)
                        recipient_list=list(set(recipient_list))    # remove duplicates
            
                message_html = render_to_string('changed_status_email.html', context)
                logging.info("send email: {}".format(recipient_list))

                mailobject="Status for station: "+mystation.name
                if (not myboard is None):
                    mailobject+=" board: "+myboard.name+" is changed"
                send_mail(mailobject, 
                          mailobject+"; details in this mail in HTML part only ...",
                          from_email=None,
                          recipient_list=recipient_list,
                          html_message=message_html,
                          fail_silently = False)

        
mqtt2stationmaint = daemon.Daemon(
    stdin="/dev/null",
    stdout=rmap.settings.logfilemqtt2stationmaintd,
    stderr=rmap.settings.errfilemqtt2stationmaintd,
    pidfile=rmap.settings.lockfilemqtt2stationmaintd,
    user=rmap.settings.usermqtt2stationmaintd,
    group=rmap.settings.groupmqtt2stationmaintd
)

# catch signal to terminate the process
class GracefulKiller:
    kill_now = False
    def __init__(self):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

    def exit_gracefully(self,signum, frame):
        self.kill_now = True

    def keyboard_interrupt(self):
        self.kill_now = True

    def terminate(self):
        self.kill_now = True

def main(self):

    import os,sys,time
    import logging.handlers
    import subprocess

    #arm the signal handler
    killer = GracefulKiller()

    # configure the logger
    formatter=logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S")
    handler = logging.handlers.RotatingFileHandler(self.options.stdout, maxBytes=5000000, backupCount=10)
    handler.setFormatter(formatter)
    
    # Add the log message handler to the root logger
    logging.getLogger().addHandler(handler)
    logging.getLogger().setLevel(logging.INFO)

    logging.info('Starting up mqtt2stationmaintd')

    # here we can start other procs/consumer
    #send_queue = queue.Queue(maxsize=1000000)
    #amqp=amqpConsumerProducer(host=host,exchange=exchange,user=user,password=password,send_queue=send_queue,logging=logging)
    #amqp.start()
    
    # If you want to use a specific client id, use
    # mqttc = MyMQTTClass("client-id")
    # but note that the client id must be unique on the broker. Leaving the client
    # id parameter empty will generate a random id for you.

    #    mqtt2stationmaint.main(
    #        host=self.options.host,
    #        topics=rmap.settings.subtopicmaint, username=self.options.username, password=self.options.pw,
    #        debug=self.options.debug, overwrite_date=self.options.overwrite_date,
    #    )

    mqttuser=rmap.settings.mqttuser
    mqttpassword=rmap.settings.mqttpassword
    host=rmap.settings.mqtthost
 
    mqttclient = MyMQTTClass(
        userdata={
            "topics": [rmap.settings.subtopicmaint,"1/"+rmap.settings.subtopicmaint],
            "logging": logging,
            "overwrite_date": OVERWRITE_DATE,
            "qos":0},
        client_id=mqttuser+"/mqttstaionmaint/"+rmap.settings.subtopicmaint,
        clean_session=True
        # qos=1 required by clean_session=False
    )

    if mqttuser:
        mqttclient.username_pw_set(mqttuser, mqttpassword)
    
    mqttclient.enable_logger(logging)
    mqttclient.loop_start()
    mqttclient.connect(host=host)
    
    # infinite loop
    loop=0
    while True:
        try:
            loop+=1
            time.sleep(DELAYSEC) # do nothing
            if ((loop*DELAYSEC) > CLOSEOLDCONNECTIONSEC):
                loop=0
                # avoid InterfaceError: connection already closed
                logging.info("close old connections")
                close_old_connections()
            
        except Exception as exception:
            logging.error('Exception occured: ' + str(exception))
            logging.error(traceback.format_exc())
            killer.terminate()
            
        # terminate on keyboard interrupt
        except KeyboardInterrupt:
            sys.stdout.write("keyboard interrupt\n")
            logging.info("keyboard interrupt\n")
            killer.keyboard_interrupt()

        # terminate without error
        # no exception was raised
        #logging.info('work finished')


        # check if we have to terminate together with other exceptions
        if killer.kill_now:
            logging.info("killed by signal\n")

            mqttclient.disconnect()
            mqttclient.loop_stop()
            logging.debug("MQTT connection closed")
            
            return False
    

if __name__ == '__main__':

    import sys, os
    
    mqtt2stationmaint.cwd=os.getcwd()

    if mqtt2stationmaint.service():

        sys.stdout.write("Daemon started with pid %d\n" % os.getpid())

        main(mqtt2stationmaint)  # (this code was run as script)
            
        for proc in mqtt2stationmaint.procs:
            proc.wait()

        sys.stdout.write("Daemon stoppped\n")
        sys.exit(0)
