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

import time
import datetime
import re

# Example for output from agent
# ---------------------------------------------------------
#<<<fhem>>>
#Detected devices: Bad.Temp Dachboden.Temp Gaestezimmer.Temp Partyraum.Temp Schlafzimmer.Temp  
#Bad.Temp                                  Bad.Temp                         TYPE                 LaCrosse
#                     2016-11-16 08:39:41  Bad.Temp                         battery              ok
#                     2016-11-16 08:39:41  Bad.Temp                         dewpoint             15.1
#                     2016-11-16 08:39:41  Bad.Temp                         humdiff              0
#                     2016-11-16 08:39:41  Bad.Temp                         humidity             83
#eg.bad.waschmaschine_pwr                  eg.bad.waschmaschine_pwr         TYPE                 Revolt
#                     2016-11-15 16:54:48  eg.bad.waschmaschine_pwr         avgpower             263.19
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         current              0
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         energy               9.27
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         frequency            50
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         pf                   0
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         power                0
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         state                P:   0.0 E:   9.27 V: 226 C:   0.00 F: 50 Pf: 0.00
#                     2016-11-16 09:18:07  eg.bad.waschmaschine_pwr         voltage              226
#eg.wz.heizung                             eg.wz.heizung                    TYPE                 CUL_HM
#                                          eg.wz.heizung                    model                HM-CC-RT-DN
#                     2016-11-15 09:46:13  eg.wz.heizung                    Activity             alive
#                     2016-11-15 23:03:10  eg.wz.heizung                    CommandAccepted      yes
#                     2016-11-07 06:01:06  eg.wz.heizung                    D-firmware           1.4
#                     2016-11-07 06:01:06  eg.wz.heizung                    D-serialNr           MEQ0252852
#                     2016-11-07 06:04:23  eg.wz.heizung                    PairedTo             0x5FF2BE
#                     2016-11-07 06:04:23  eg.wz.heizung                    R-backOnTime         10 s
#                     2016-11-07 06:04:23  eg.wz.heizung                    R-btnLock            on
## ---------------------------------------------------------

def inventory_fhem(info):
        # Detected devices: Bad.Temp Dachboden.Temp Gaestezimmer.Temp
        line = info[0]
        if len(line) > 2:
                for device in line[2:]:
                        yield device, {}

def check_fhem(item, params, info):
        # [[u'Detected', u'fhem:', u'Bad.Temp', u'Dachboden.Temp', u'Gaestezimmer.Temp', u'Partyraum.Temp', u'Schlafzimmer.Temp'], [u'Bad.Temp', u'TYPE', u'LaCrosse'], [u'2016-07-14', u'13:15:07', u'state', u'T:', u'21.4', u'H:', u'77'], [u'2016-07-14', u'13:15:07', u'dewpoint', u'17.2'], [u'Dachboden.Temp', u'TYPE', u'LaCrosse'], [u'2016-07-14', u'13:15:10', u'state', u'T:', u'23.5', u'H:', u'53'], [u'2016-07-14', u'13:15:10', u'dewpoint', u'13.4'], [u'Gaestezimmer.Temp', u'TYPE', u'LaCrosse'], [u'2016-07-14', u'13:15:10', u'state', u'T:', u'22.8', u'H:', u'66'], [u'2016-07-14', u'13:15:10', u'dewpoint', u'16.1'], [u'Partyraum.Temp', u'TYPE', u'LaCrosse'], [u'2016-07-14', u'13:15:12', u'state', u'T:', u'23', u'H:', u'59'], [u'2016-07-14', u'13:15:12', u'dewpoint', u'14.6'], [u'Schlafzimmer.Temp', u'TYPE', u'LaCrosse'], [u'2016-07-14', u'13:15:08', u'state', u'T:', u'22.8', u'H:', u'59'], [u'2016-07-14', u'13:15:08', u'dewpoint', u'14.4']]

        fwarn, fcrit = None, None

        if "level_data_age" in params:
                dage_warn, dage_crit = params["level_data_age"]
        if "level_temp_max" in params:
                tmax_warn, tmax_crit = params["level_temp_max"]
        if "level_temp_min" in params:
                tmin_warn, tmin_crit = params["level_temp_min"]


        # See if any checks are diabled (set to 0)
#        if dage_warn != None and dage_warn == 0:
#                dage_warn = None;
#        if dage_crit != None and dage_crit == 0:
#                dage_crit = None;
#        if cbwarn != None and cbwarn == 0:
#                cbwarn = None;
#        if cbcrit != None and cbcrit == 0:
#                cbcrit = None;
        

        errorlevel, warnlevel = 0, 0

        dev_model = ""
        dev_type = ""
        output = []
        perfdata = []
        data = {}
        in_device = 0


#        ## bugfix, at beginning all timestamp up-to-date 
#        ## necessary because some devices don't send all data (weather-provider has no battery)
#        battery_timestamp, dewpoint_timespamp, state_timestamp = time.time(), time.time(), time.time()

        for line in info:
                if len(line) == 4 and line[2] == 'TYPE':
                        if line[0] == item:
                                dev_type = line[3]
                                in_device = 1
                        elif in_device == 1:
                                break
                elif in_device == 1:
                        if line[1] == 'model':
                                dev_model = line[2]
                        if len(line) > 3 and line[3] != 'null':
                                data[line[3]] = {}
                                data[line[3]]['value']=line[4]
                                data[line[3]]['time']=( "%s %s" % (line[0], line[1]))
                                data[line[3]]['channel'] = line[2]
                                ## needed for traffic
                                if len(line) > 6:
                                        if line[5] == 'hour':
                                                # convert hours to minutes 
                                                data[line[3]]['value']=(int(line[4])*60)+int(line[6])

        # mostly config mistake or incorrect agent output
        if in_device == 0:
                return (3, "UNKNOWN - unable to parse %s (type: %s, model: %s)" % (item, dev_type, dev_model ))

# ##################################################################

        ## key          => variable_name
        ## title        => readable output
        ## unit         => °C, %, V., kmh, ... (for output)
        ## channel      => filter for channel (HomeMatic)
        ## show         => print if no error
        ## perfd        => print perfdata
        for key, title, unit, channel, show, perfd in [
             ##  key                            title                   unit                channel            show        perfd
             ##  -------                        -------                 -------             -------            ----        -----
                ('temperature',         'temperature',          u'°C',              '',                1,          1),
                ('desired-temp',        'desiredtemp',          u'°C',              '',                1,          1),
                ('humidity',            'humidity',             u'%',               '',                1,          1),
                ('dewpoint',            'dewpoint',             u'°C',              '',                1,          1),
                ('contact',             'contact',              '',                 '',                1,          1),
                ('valveposition',       'valveposition',        u'%',               '',                1,          1),
                ('battery',             'battery',              '',                 '',                1,          0),
                ('batteryLevel',        'batteryLevel',         'V',                '',                1,          1),
                ('controlMode',         'controlMode',          '',                 'Clima',           0,          0),
                ('Activity',            'activity',             '',                 '',                0,          1),
                ('power',               'power',                'kWh',              '',                1,          1),
                ('energy',              'energy',               'W',                '',                1,          1),
                ('voltage',             'voltage',              'V',                '',                1,          1),
                ## HomeMatic
                ('measured-temp',       'temperature',          u'°C',              '',                1,          1),
                ('actuator',            'valveposition',        u'%',               '',                1,          1),
                ('R-btnLock',           'btnLock',              '',                 '',                0,          0),
                ('R-globalBtnLock',     'globalBtnLock',        '',                 '',                0,          0),
                ('R-modusBtnLock',      'modusBtnLock',         '',                 '',                0,          0),
                ('R-tempOffset',        'tempOffset',           'K',                'Clima',           1,          0),
                ## MilightDevice, WifiLight, ... 
                ('brightness',          'brightness',           u'%',               '',                1,          1),
                ('brightness_on',       'brightness_on',        u'%',               '',                0,          1),
                ('ct',                  'ct',                   'K',                '',                1,          1),
                ('hsv',                 'hsv',                  '',                 '',                0,          0),
                ('transitionInProgress','transitionInProgress', '',                 '',                0,          0),
                ('hue',                 'hue',                  '',                 '',                0,          0),
                ('saturation',          'saturation',           '',                 '',                0,          1),
                ('RGB',                 'RGB',                  '',                 '',                1,          0),
                ## MiLightBridge
                ('protocol',            'protocol',             '',                 '',                0,          0),
                ('checkInterval',       'checkInterval',        's',                '',                0,          0),
                ('sendInterval',        'sendInterval',         's',                '',                0,          0),
                ## Speedtest
                ('download',            'download',             'Mbit/s',           '',                1,          1),
                ('ping',                'ping',                 'ms',               '',                1,          1),
                ('state',               'state',                '',                 '',                1,          0),
                ('upload',              'upload',               'Mbit/s',           '',                1,          1),
#               ('path',                'path',                 '',                 '',                0,          0),
                ## TRAFFIC
                ('delay',               'delay',                'min',              '',                1,          1),
                ('distance',            'distance',             'km',               '',                1,          1),
                ('duration',            'duration',             'mins',             '',                1,          1),
                ('duration_in_traffic', 'duration_in_traffic',  'mins',             '',                1,          1),
                ## ESPEasy
                ('presence',            'presence',             '',                 '',                1,          1),
             ##  key                            title                           unit                channel            show        perfd
             ##  -------                        -------                         -------             -------            ----        -----
                ## LUXTRONIK2
                ('ambientTemperature',          'ambientTemperature',           u'°C',              '',                1,          1),
                ('averageAmbientTemperature',   'averageAmbientTemperature',    u'°C',              '',                0,          1),
                ('hotWaterTemperature',         'hotWaterTemperature',          u'°C',              '',                1,          1),
                ('hotWaterTemperatureTarget',   'hotWaterTemperatureTarget',    u'°C',              '',                0,          1),
                ('heatingSystemCircPump',       'heatingSystemCircPump',        '',                 '',                0,          1),
                ('opModeHeating',               'opModeHeating',                '',                 '',                0,          0),
                ('opModeHotWater',              'opModeHotWater',               '',                 '',                0,          0),
                ('opStateHeatPump1',            'opStateHeatPump1',             '',                 '',                0,          0),
                ('hotGasTemperature',           'hotGasTemperature',            u'°C',              '',                1,          1),
                ('flowTemperature',             'flowTemperature',              u'°C',              '',                1,          1),
                ('returnTemperature',           'returnTemperature',            u'°C',              '',                1,          1),
                ('returnTemperatureTarget',     'returnTemperatureTarget',      u'°C',              '',                0,          1),
#                ('counterHours2ndHeatSource1',  'counterHours2ndHeatSource1',   '',                 '',                0,          0),
#                ('counterHours2ndHeatSource2',  'counterHours2ndHeatSource2',   '',                 '',                0,          0),
#                ('counterHoursHeatPump',        'counterHoursHeatPump',         '',                 '',                0,          0),
#                ('counterHoursHeating',         'counterHoursHeating',          '',                 '',                0,          0),
#                ('counterHoursHotWater',        'counterHoursHotWater',         '',                 '',                0,          0),





        ]:
          ## reset
          display_value = "" 
          ignoreit        = ""
#          var_data_age = ""
          fetch_plain = None


          ## check if device output data (temperature, humidity, ... )
          try:
                if data[key]['value']:
                    value = data[key]['value']

                    ## build dynamic variable names ...
                    var_max = 'level_%s_max' % title
                    var_min = 'level_%s_min' % title
                    var_data_age = '%s_date' % title
                    var_plain = 'var_%s' % title        ## e.g. contact: open (var_plain => open)
                    var_params = '%s_params' % title

                    ## set upper level to default values if exist else we set to null
                    if eval("var_max") in params:
                            upper_warn, upper_crit = params[eval("var_max")]
                    else:
                            upper_warn, upper_crit = 0, 0

                    ## set lower level to default values if exist else we set to null
                    if eval("var_min") in params:
                            lower_warn, lower_crit = params[eval("var_min")]
                    else:
                            lower_warn, lower_crit = 0, 0

                    ## set plain text variables to default
                    if eval("var_plain") in params:
                            fetch_plain = params[eval("var_plain")]

        #            print var_data_age
        #                    if  params[eval("desiredtemp_date")]:
        #                        print desiredtemp_date
        #                      pass
        #            except:
        #                   pass

                    for param_group in params.keys():

                      ## just loop over param categories (WATO)
                      ## all categories need to named "*_params"
                      if re.match('[a-z]+_params', param_group) is not None:

                        ## loop over sub-categories
                        for param_subgroup in params[param_group]:

                            ## get configuration from WATO
                            ## we create the runtime variable "fetch_params"
                            try:
                                ## like: fetch_params = params["climate_params"]
                                fetch_params = params[param_group][param_subgroup]
                            except:
                                pass

                            ## get minimal thresholds
                            try:
                                if eval("var_min") in fetch_params:
                                        ## like: if level_temperature_min in fetch_params["climate_params"]:
                                        lower_warn, lower_crit = fetch_params[eval("var_min")]
                            except:
                                       pass

                            ## get maximal thresholds
                            try:
                                ## like: if level_temperature_max in fetch_params[]:
                                if eval("var_max") in fetch_params: 
                                        upper_warn, upper_crit = fetch_params[eval("var_max")]
                            except:
                                pass

                            ## get plain text
                            try:
                                ## like: if level_temperature_min in fetch_params[]:
                                if eval("var_plain") in fetch_params:
                                        fetch_plain = fetch_params[eval("var_plain")]
                            except:
                                pass
 
                     ## not used (yet)                                    
#                    try:
#                        if eval(var_data_age) and eval(var_data_age) != "":
#                                local_timestamp = time.time()
#                                var_data_timestamp = time.mktime(datetime.datetime.strptime(eval(var_data_age) , "%Y-%m-%d %H:%M:%S").timetuple())
#                                data_age = int( ( local_timestamp - var_data_timestamp ) / 60 )
#                    except:
#                        ## bugfix, at beginning all timestamp up-to-date 
#                        ## necessary because some devices don't send all data (weather-provider has no battery)
#                        data_age = 0
#                        pass

        #            if data_age > dage_crit:
        ##                 print ("data_age = %s, dage_crit = %s" % (data_age, dage_crit))
        #                        display_value = ("data to old [" + str(data_age)  + "min] (!!)")
        #                 errorlevel = 2
        #            elif data_age > dage_crit: 
        #                 display_value = ("data to old [" + str(data_age)  + "min] (!)")
        #                 warnlevel = 1
        #            else:

                    ## calulate warn level for dewpoint 
                    if key == 'dewpoint':
                        upper_warn = float(data['temperature']['value']) - upper_warn
                        upper_crit = float(data['temperature']['value']) - upper_crit

                    ## ignore humidity by default if dewpoint is given
                    if key == 'humidity' and params['var_dewpoint_override'] == 'true':
                        ignoreit = 'true'


                    ## there are sometime duplicate keys on channel like controlmode in '_Clima' and '_Climate' 
                    ## Just filter out some HomeMatic-channel
                    if channel == '' or data[key]['channel'] == "%s_%s" % (item, channel):
                            ## check critical level (max/min)
                            if ( (upper_crit != 0 and float(value) >= upper_crit) or (lower_crit != 0 and float(value) <= lower_crit) ) and not ignoreit:
                                if upper_crit != 0 and float(value) >= upper_crit:
                                        display_value = ('%s%s (warn/crit at %s%s/%s%s) (!!)' % ( value, unit, upper_warn, unit, upper_crit, unit))
                                elif float(value) <= lower_crit:
                                        display_value = ('%s%s (warn/crit at %s%s/%s%s) (!!)' % ( value, unit, lower_warn, unit, lower_crit, unit)) 
                                errorlevel = 2
                            ## check warning level (max/min)
                            elif ( (upper_warn != 0 and float(value) >= upper_warn) or (lower_warn != 0 and float(value) <= lower_warn) ) and not ignoreit:
                                if upper_warn != 0 and float(value) >= upper_warn:
                                        display_value = ('%s%s (warn/crit at %s%s/%s%s) (!)' % ( value, unit, upper_warn, unit, upper_crit, unit))
                                elif float(value) <= lower_warn:
                                        display_value = ('%s%s (warn/crit at %s%s/%s%s) (!)' % ( value, unit, lower_warn, unit, lower_crit, unit))
                                warnlevel = 1
                            elif fetch_plain:
                                ## just alert if value not identic or ignored
                                if value == fetch_plain or fetch_plain == "ignore":
                                        if show:
                                                display_value = ('%s%s' % ( value, unit ))
                                        if perfd:
                                                perfdata.append( ( title, 1, "" ) )
                                else:
                                        display_value = ("%s (expected: %s) (!!)" % (value, fetch_plain))
                                        errorlevel = 2
                                        if perfd:
                                                perfdata.append( ( title, 0, "" ) )
                            ## define output
                            elif show:
                                display_value = ('%s%s' % ( value, unit))


                            ## define perfdata (just add numeric values)
                            if perfd and not fetch_plain:
                                perfdata.append( ( title, value, upper_warn, upper_crit, "", "" ) )


                            ## not used (yet)
#                            if data_age > dage_crit:
#                                 display_value += (" (data obsolet: %s min) (warn/crit at %s/%smin) (!!)" % (str(data_age), dage_warn, dage_crit))
#                                 errorlevel = 2
#                            elif data_age > dage_warn:
#                                 display_value += (" (data obsolet: %s min) (warn/crit at %s/%smin) (!)" % (str(data_age), dage_warn, dage_crit)) 
#                                 warnlevel = 1


                            if display_value != "":
                                output.append('%s: %s' % (title, display_value))
                pass

          except:
                pass



# ##################################################################

          if errorlevel > 1:
                exitcode = 2
          elif warnlevel > 0:
                exitcode = 1
          else:
                exitcode = 0

        return exitcode, ', '.join(output), perfdata


factory_settings["fhem_default_params"] = {
        "level_data_age"               : (30, 90),         # warn/crit for data age (min)
        "level_temperature_max"        : (26, 30),         # warn/crit for max. temperature
        "level_temperature_min"        : (15, 12),         # warn/crit for min. temperature
        "level_humidity_max"           : (70, 80),         # warn/crit for max. humidity
        "level_humidity_min"           : (50, 45),         # warn/crit for min. humidity
        "level_dewpoint_max"           : (3, 1),           # warn/crit for diff to temperatur (exp. 17°C (dewp) vs 20°C(temp))
        "level_batteryLevel_min"       : (2.3, 2.1),       # warn/crit for max. temperature
        "level_download_min"           : (10.0, 8.0),      # warn/crit for min download (speedtest)
        "level_upload_min"             : (1.5, 1.0),       # warn/crit for min upload (speedtest)
        "level_ping_max"               : (100, 150),       # warn/crit for ping (speedtest)
        "var_activity"                 : ("alive"),        # default for alive
        "var_presence"                 : ("present"),      # default for presence
        "var_contact"                  : ("ignore"),       # default for contact
        "var_battery"                  : ("ok"),           # default for battery
        "var_dewpoint_override"        : ("true"),         # don't alert humidity if dewpoint given
        "var_btnLock"                  : ("ignore"),       # simple button lock
        "var_globalBtnLock"            : ("ignore"),       # full button lock
        "var_modusBtnLock"             : ("ignore"),       # modus button lock
        "var_RGB_color"                : ("ignore"),       # RGB color hexcode
        "var_brightness"               : ("ignore"),       # color brightness
        "var_state"                    : ("ignore"),       # reading state
        ## LUXTRONIK2
        "level_ambientTemperature_max"          : (26, 30),         # warn/crit for max. ambientTemperature
        "level_ambientTemperature_min"          : (15, 12),         # warn/crit for min. ambientTemperature
        "level_averageAmbientTemperature_max"   : (26, 30),         # warn/crit for max. averageAmbientTemperature
        "level_averageAmbientTemperature_min"   : (15, 12),         # warn/crit for min. averageAmbientTemperature
        "level_hotWaterTemperature_max"         : (60, 65),         # warn/crit for max hotWaterTemperature
        "level_hotWaterTemperature_min"         : (40, 37),         # warn/crit for min hotWaterTemperature
#        "level_hotGasTemperature_max"           : (26, 30),         # warn/crit for max hotWaterTemperature
#        "level_hotGasTemperature_min"           : (15, 12),         # warn/crit for min hotWaterTemperature
#        "level_returnTemperature_max"           : (35, 38),         # warn/crit for max hotWaterTemperature
#        "level_returnTemperature_min"           : (25, 23),         # warn/crit for min hotWaterTemperature
#        "level_flowTemperature_max"             : (35, 38),         # warn/crit for max hotWaterTemperature
#        "level_flowTemperature_min"             : (25, 23),         # warn/crit for min hotWaterTemperatur
        "var_heatingSystemCircPump"             : ("ignore"),       # default for heatingSystemCircPump
        "var_opStateHeatPump1"                  : ("ignore"),       # default for opStateHeatPump1
        "var_opModeHeating"                     : ("Automatik"),    # default for opModeHeating 
        "var_opModeHotWater"                    : ("Automatik"),    # default for opModeHotWater
        
}

check_info['fhem'] = {
    "check_function"          : check_fhem,
    "inventory_function"      : inventory_fhem,
    "service_description"     : "FHEM %s",
    "has_perfdata"            : True,
    "group"                   : "fhem",
    "default_levels_variable" : "fhem_default_params",
}


