def __process_next_sms(self):
        signal_strength_percentage = "--"
        time_before_fetch = time.time()
        try:
            sms_fetcher = SmsFetcher(
                self.config["gammuConfigFile"],
                self.config["gammuConfigSection"],
                self.config["logDir"] + "/sms-delete.log",
            )
            signal_strength_percentage = sms_fetcher.get_signal_strength_percentage()
            sms_messages = sms_fetcher.delete_get_next_sms()
        except (gammu.ERR_TIMEOUT, gammu.ERR_DEVICENOTEXIST, gammu.ERR_NOTCONNECTED):
            timeout_after_time = time.time() - time_before_fetch
            debug(
                "Got exception after {} seconds while trying to fetch/delete next sms (signalStrength: {}%).".format(
                    timeout_after_time, signal_strength_percentage
                )
            )
            raise  # re-raise exception so we get the stacktrace to stderr

        absolute_script_path = os.path.abspath(__file__)

        if not sms_messages:
            # would write a log message every minute even if no sms found
            # debug("No sms found by {} - bye.".format(absolute_script_path))
            return TemperatureController.NO_SMS_FOUND

        debug("Start sms processing (found {} messages) by {}".format(len(sms_messages), absolute_script_path))

        sms = sms_messages[0]

        # set system datetime to sent/received DateTime from received sms if delta is big enough
        if self.config["systemDatetimeMaxDiffNoUpdateSeconds"] > 0:
            system_datetime = datetime.now()
            sms_datetime_naive = sms[0]["DateTime"]
            delta_datetime = sms_datetime_naive - system_datetime
            delta_seconds = delta_datetime.total_seconds()
            if abs(delta_seconds) > self.config["systemDatetimeMaxDiffNoUpdateSeconds"]:
                # example unix date: Thu Nov 28 23:29:53 CET 2014
                sms_datetime_unix = sms_datetime_naive.strftime("%a %b %d %H:%M:%S %Y")
                set_date_cmd = 'date -s "{0}" > /dev/null'.format(sms_datetime_unix)
                debug("Updating system datetime (delta: {} seconds) using cmd: {}".format(delta_seconds, set_date_cmd))
                os.system(set_date_cmd)
            # else:
            # debug("system date diff ({} seconds) is not greater than configured delta ({} seconds), skipping updating.".format(delta_seconds, self.config['systemDatetimeMaxDiffNoUpdateSeconds']))

        now_string = datetime.now().strftime(DATETIME_FORMAT)

        sender_number = sms[0]["Number"]
        sender_message_raw = sms[0]["Text"]
        sender_message = repr(sender_message_raw)
        debug("  got sms message from {}: {}".format(sender_number, sender_message))

        if self.config["myNumber"] in sender_number:
            debug("  got sms from my own number - not responding in order to prevent infinite loop. Bye!")
            return TemperatureController.SMS_IGNORED
        elif self.config["blacklistSenders"]:  # empty strings are false in python
            for blacklist_sender in self.config["blacklistSenders"].split(","):
                if len(blacklist_sender) > 0 and blacklist_sender in sender_number:
                    debug("  this sender is in blacklist (matched '{}') - ignoring. Bye!".format(blacklist_sender))
                    return TemperatureController.SMS_IGNORED

        if len(sms_messages) > 1:
            debug(
                "  found sms consisting of {} parts - only the first part will be considered.".format(len(sms_messages))
            )

        response_message = None

        if sender_message_raw and sender_message_raw.lower().startswith("temp"):
            temp_raw = temperaturereader.read_celsius()
            if temp_raw:
                temp = round(temp_raw, 1)
                debug("  responding with temperature: {} Celsius.".format(temp))
                response_message = u"Hi! Current temperature here is {0} Celsius ({1}).".format(temp, now_string)
            else:
                debug("  temperature could not be read.")
                response_message = u"Hi! Temperature sensor is offline, check log files."

        elif sender_message_raw and sender_message_raw.lower().startswith("power autocontrol "):
            message_payload = sender_message_raw[len("power autocontrol ") :]
            parts = message_payload.split(None, 3)
            success = False
            if len(parts) >= 2:
                try:
                    switch_on_temperature = float(parts[0].replace(",", "."))
                    switch_off_temperature = float(parts[1].replace(",", "."))
                except Exception as e:
                    # silently ignore, reply with help message
                    debug("  exception occurred while trying to convert supplied args to float number, ignoring")

                else:
                    pac = PowerAutocontroller(config_parser)

                    switch_on_temp_before = pac.get_switch_on_temperature()
                    switch_off_temp_before = pac.get_switch_off_temperature()
                    current_temp_raw = temperaturereader.read_celsius()

                    pac.set_switch_onoff_temperatures(CONFIG_FILEPATH, switch_on_temperature, switch_off_temperature)

                    switch_on_temp_after = pac.get_switch_on_temperature()
                    switch_off_temp_after = pac.get_switch_off_temperature()

                    if current_temp_raw:
                        current_temp = round(current_temp_raw, 1)
                        debug(
                            "  responding with updated temperature interval [{2}:{3}] (was: [{0}:{1}]), current temperature: {4} Celsius.".format(
                                switch_on_temp_before,
                                switch_off_temp_before,
                                switch_on_temp_after,
                                switch_off_temp_after,
                                current_temp,
                            )
                        )
                        response_message = u"Hi! Successfully set temperature interval to [{0}:{1}]. Current temperature is {2}.".format(
                            switch_on_temp_after, switch_off_temp_after, current_temp
                        )
                    else:
                        debug(
                            "  responding with updated temperature interval [{2}:{3}] (was: [{0}:{1}]), current temperature could not be read.".format(
                                switch_on_temp_before,
                                switch_off_temp_before,
                                switch_on_temp_after,
                                switch_off_temp_after,
                            )
                        )
                        response_message = u"Hi! Successfully set temperature interval to [{0}:{1}]. Current temperature could not be read.".format(
                            switch_on_temp_after, switch_off_temp_after
                        )

                    success = True

            if not success:
                debug("  couldnt understand 'power autocontrol' message, responding with help message.")
                response_message = u'Hi! Didnt understand your message, use "power autocontrol 4 12" to enable power between 4 and 12 degrees.'

        elif sender_message_raw and sender_message_raw.lower().startswith("power"):
            gpio_channels = [int(channel) for channel in self.config["relayGpioChannels"].split(",")]
            powerswitcher = PowerSwitcher(gpio_channels=gpio_channels)
            power_status_before = powerswitcher.get_status_string()

            requested_state = ""
            if sender_message_raw.lower().startswith("power on"):
                requested_state = "ON"
            elif sender_message_raw.lower().startswith("power off"):
                requested_state = "OFF"

            manual_switching_allowed = not self.config["powerAutocontrolEnabled"]
            if manual_switching_allowed and requested_state and requested_state == "ON":
                powerswitcher.set_status_on()
                debug("  power has been set ON")
            elif manual_switching_allowed and requested_state and requested_state == "OFF":
                powerswitcher.set_status_off()
                debug("  power has been set OFF")
            else:
                debug(
                    "  power switching not requested/allowed (manual switching allowed? {})".format(
                        manual_switching_allowed
                    )
                )

            power_status = powerswitcher.get_status_string()
            debug("  responding with power status: {1} (was: {0}).".format(power_status_before, power_status))
            if requested_state and not manual_switching_allowed:
                response_message = u"Hi! Power autocontrol is enabled, cannot switch power manually (currently {0}).".format(
                    power_status
                )
            elif requested_state:
                response_message = u"Hi! Power has been switched {0}, was {1} ({2}).".format(
                    power_status, power_status_before, now_string
                )
            else:
                response_message = u"Hi! Power is currently {0} ({1}).".format(power_status, now_string)

        elif sender_message_raw and sender_message_raw.lower().startswith("systeminfo"):
            debug("  responding with system info:")
            up_since = systeminfo.get_last_reboot_date_time()
            kernelVersion = systeminfo.get_kernel_version()
            rpiSerialNumber = systeminfo.get_rpi_serial_number()
            localInetAddress = systeminfo.get_inet_address()
            gitRevision = systeminfo.get_git_revision()
            balance_info = self.__get_cached_balance_info(short=True)
            debug("    system datetime  : {}".format(now_string))
            debug("    up since         : {}".format(up_since))
            debug("    kernel version   : {}".format(kernelVersion))
            debug("    RPi serial       : {}".format(rpiSerialNumber))
            debug("    inet address     : {}".format(localInetAddress))
            debug("    git revision     : {}".format(gitRevision))
            debug("    Signal strength  : {}%".format(signal_strength_percentage))
            debug("    Balance          : {}".format(balance_info))
            response_message = u"Hi!\n sysTime: {0}\n uptime: {1}\n kernel: {2}\n serial: {3}\n inet: {4}\n gitRev: {5}\n signal: {6}%\n $$: {7}.".format(
                now_string,
                up_since,
                kernelVersion,
                rpiSerialNumber,
                localInetAddress,
                gitRevision,
                signal_strength_percentage,
                balance_info,
            )

        elif sender_message_raw and sender_message_raw.lower().startswith("checkbalance"):
            ussd = self.config["ussdCheckBalance"]
            self.__update_balance_if_necessary(force=True)
            balance_info = self.__get_cached_balance_info()
            debug("  responding with USSD reply for {}:".format(ussd))
            debug("  " + balance_info.encode("ascii", "replace"))
            response_message = u"Hi! Current balance info:\n{0}".format(balance_info)

        else:
            debug("  not recognized, answering with help message.")
            response_message = u"Hi! 'temp' to get current temperature, 'power' (+' on'/' off') to get (change) power status. Other commands: 'systeminfo', 'checkbalance'."

        time_before_send = time.time()
        try:
            sms_sender = SmsSender(self.config["gammuConfigFile"], self.config["gammuConfigSection"])
            sms_sender.send_sms(response_message, sender_number)
            return TemperatureController.SMS_PROCESSED
        except (gammu.ERR_UNKNOWN):
            timeout_after_time = time.time() - time_before_send
            debug("Got exception after {} seconds while trying to send sms.".format(timeout_after_time))
            raise  # re-raise exception so we get the stacktrace to stderr
Ejemplo n.º 2
0
    def __process_next_sms(self):
        signal_strength_percentage = '--'
        time_before_fetch = time.time()
        try:
            sms_fetcher = SmsFetcher(self.config['gammuConfigFile'], self.config['gammuConfigSection'], self.config['logDir'] + '/sms-delete.log') 
            signal_strength_percentage = sms_fetcher.get_signal_strength_percentage()
            sms_messages = sms_fetcher.delete_get_next_sms()
        except (gammu.ERR_TIMEOUT, gammu.ERR_DEVICENOTEXIST, gammu.ERR_NOTCONNECTED):
            timeout_after_time = time.time() - time_before_fetch
            debug("Got exception after {} seconds while trying to fetch/delete next sms (signalStrength: {}%).".format(timeout_after_time, signal_strength_percentage))
            raise # re-raise exception so we get the stacktrace to stderr
        
        absolute_script_path = os.path.abspath(__file__)
        
        if not sms_messages:
            # would write a log message every minute even if no sms found 
            #debug("No sms found by {} - bye.".format(absolute_script_path))
            return TemperatureController.NO_SMS_FOUND
        
        debug("Start sms processing (found {} messages) by {}".format(len(sms_messages), absolute_script_path))
        
        sms = sms_messages[0]
        
        # set system datetime to sent/received DateTime from received sms if delta is big enough
        if self.config['systemDatetimeMaxDiffNoUpdateSeconds'] > 0:
            system_datetime = datetime.now()
            sms_datetime_naive = sms[0]['DateTime']
            delta_datetime = sms_datetime_naive - system_datetime
            delta_seconds = delta_datetime.total_seconds()
            if abs(delta_seconds) > self.config['systemDatetimeMaxDiffNoUpdateSeconds']:
                # example unix date: Thu Nov 28 23:29:53 CET 2014
                sms_datetime_unix = sms_datetime_naive.strftime("%a %b %d %H:%M:%S %Y")
                set_date_cmd = "date -s \"{0}\" > /dev/null".format(sms_datetime_unix)
                debug("Updating system datetime (delta: {} seconds) using cmd: {}".format(delta_seconds, set_date_cmd))
                os.system(set_date_cmd)
            #else:
        	#debug("system date diff ({} seconds) is not greater than configured delta ({} seconds), skipping updating.".format(delta_seconds, self.config['systemDatetimeMaxDiffNoUpdateSeconds']))
        
        now_string = datetime.now().strftime(DATETIME_FORMAT)
        
        sender_number = sms[0]['Number']
        sender_message_raw = sms[0]['Text']
        sender_message = repr(sender_message_raw)
        debug("  got sms message from {}: {}".format(sender_number, sender_message))
        
        if self.config['myNumber'] in sender_number:
            debug("  got sms from my own number - not responding in order to prevent infinite loop. Bye!")
            return TemperatureController.SMS_IGNORED
        elif self.config['blacklistSenders']: # empty strings are false in python
           for blacklist_sender in self.config['blacklistSenders'].split(","): 
                if len(blacklist_sender) > 0 and blacklist_sender in sender_number: 
                    debug("  this sender is in blacklist (matched '{}') - ignoring. Bye!".format(blacklist_sender))
                    return TemperatureController.SMS_IGNORED
        
        if len(sms_messages) > 1:
            debug("  found sms consisting of {} parts - only the first part will be considered.".format(len(sms_messages)))
        
        
        response_message = None
        
        if sender_message_raw and sender_message_raw.lower().startswith('temp'):
            temp_raw = temperaturereader.read_celsius()
            if temp_raw: 
                temp = round(temp_raw, 1)
                debug("  responding with temperature: {} Celsius.".format(temp))
                response_message = u'Hi! Current temperature here is {0} Celsius ({1}).'.format(temp, now_string)
            else:
                debug("  temperature could not be read.")
                response_message = u'Hi! Temperature sensor is offline, check log files.'
        
        elif sender_message_raw and sender_message_raw.lower().startswith('power autocontrol '):
            message_payload = sender_message_raw[len('power autocontrol '):]
            parts = message_payload.split(None, 3)
            success = False
            if len(parts) >= 2:
                try:
                    switch_on_temperature = float(parts[0].replace(',', '.'))
                    switch_off_temperature = float(parts[1].replace(',', '.'))
                except Exception as e:
                    # silently ignore, reply with help message
                    debug("  exception occurred while trying to convert supplied args to float number, ignoring")

                else:
                    pac = PowerAutocontroller(config_parser)

                    switch_on_temp_before = pac.get_switch_on_temperature()
                    switch_off_temp_before = pac.get_switch_off_temperature()
                    current_temp_raw = temperaturereader.read_celsius()

                    pac.set_switch_onoff_temperatures(CONFIG_FILEPATH, switch_on_temperature, switch_off_temperature)

                    switch_on_temp_after = pac.get_switch_on_temperature()
                    switch_off_temp_after = pac.get_switch_off_temperature()
                    
                    if current_temp_raw: 
                        current_temp = round(current_temp_raw, 1)
                        debug("  responding with updated temperature interval [{2}:{3}] (was: [{0}:{1}]), current temperature: {4} Celsius.".format(switch_on_temp_before, switch_off_temp_before, switch_on_temp_after, switch_off_temp_after, current_temp))
                        response_message = u'Hi! Successfully set temperature interval to [{0}:{1}]. Current temperature is {2}.'.format(switch_on_temp_after, switch_off_temp_after, current_temp)
                    else:
                        debug("  responding with updated temperature interval [{2}:{3}] (was: [{0}:{1}]), current temperature could not be read.".format(switch_on_temp_before, switch_off_temp_before, switch_on_temp_after, switch_off_temp_after))
                        response_message = u'Hi! Successfully set temperature interval to [{0}:{1}]. Current temperature could not be read.'.format(switch_on_temp_after, switch_off_temp_after)

                    success = True

            if not success:
                debug("  couldnt understand 'power autocontrol' message, responding with help message.")
                response_message = u'Hi! Didnt understand your message, use "power autocontrol 4 12" to enable power between 4 and 12 degrees.'

        elif sender_message_raw and sender_message_raw.lower().startswith('power'):
            gpio_channels = [int(channel) for channel in self.config['relayGpioChannels'].split(',')]
            powerswitcher = PowerSwitcher(gpio_channels=gpio_channels)
            power_status_before = powerswitcher.get_status_string()

            requested_state = ''
            if sender_message_raw.lower().startswith('power on'):
                requested_state = 'ON'
            elif sender_message_raw.lower().startswith('power off'):
                requested_state = 'OFF'

            manual_switching_allowed = not self.config['powerAutocontrolEnabled']
            if manual_switching_allowed and requested_state and requested_state == 'ON':
                powerswitcher.set_status_on()
                debug("  power has been set ON")
            elif manual_switching_allowed and requested_state and requested_state == 'OFF':
                powerswitcher.set_status_off()
                debug("  power has been set OFF")
            else: 
                debug("  power switching not requested/allowed (manual switching allowed? {})".format(manual_switching_allowed))

            power_status = powerswitcher.get_status_string()
            debug("  responding with power status: {1} (was: {0}).".format(power_status_before, power_status))
            if requested_state and not manual_switching_allowed:
                response_message = u'Hi! Power autocontrol is enabled, cannot switch power manually (currently {0}).'.format(power_status)
            elif requested_state:
                response_message = u'Hi! Power has been switched {0}, was {1} ({2}).'.format(power_status, power_status_before, now_string)
            else:
                response_message = u'Hi! Power is currently {0} ({1}).'.format(power_status, now_string)
        
        elif sender_message_raw and sender_message_raw.lower().startswith('systeminfo'):
            debug("  responding with system info:")
            up_since = systeminfo.get_last_reboot_date_time()
            kernelVersion = systeminfo.get_kernel_version()
            rpiSerialNumber = systeminfo.get_rpi_serial_number()
            localInetAddress = systeminfo.get_inet_address()
            gitRevision = systeminfo.get_git_revision()
            balance_info = self.__get_cached_balance_info(short=True)
            debug("    system datetime  : {}".format(now_string))
            debug("    up since         : {}".format(up_since))
            debug("    kernel version   : {}".format(kernelVersion))
            debug("    RPi serial       : {}".format(rpiSerialNumber))
            debug("    inet address     : {}".format(localInetAddress))
            debug("    git revision     : {}".format(gitRevision))
            debug("    Signal strength  : {}%".format(signal_strength_percentage))
            debug("    Balance          : {}".format(balance_info))
            response_message = u'Hi!\n sysTime: {0}\n uptime: {1}\n kernel: {2}\n serial: {3}\n inet: {4}\n gitRev: {5}\n signal: {6}%\n $$: {7}.'.format(now_string, up_since, kernelVersion, rpiSerialNumber, localInetAddress, gitRevision, signal_strength_percentage, balance_info)
        
        
        elif sender_message_raw and sender_message_raw.lower().startswith('checkbalance'):
            ussd = self.config['ussdCheckBalance']
            self.__update_balance_if_necessary(force=True)
            balance_info = self.__get_cached_balance_info()
            debug("  responding with USSD reply for {}:".format(ussd))
            debug("  " + balance_info.encode('ascii', 'replace'))
            response_message = u'Hi! Current balance info:\n{0}'.format(balance_info)

        else:
            debug("  not recognized, answering with help message.")
            response_message = u"Hi! 'temp' to get current temperature, 'power' (+' on'/' off') to get (change) power status. Other commands: 'systeminfo', 'checkbalance'."
        
        time_before_send = time.time()
        try:
            sms_sender = SmsSender(self.config['gammuConfigFile'], self.config['gammuConfigSection']) 
            sms_sender.send_sms(response_message, sender_number)
            return TemperatureController.SMS_PROCESSED
        except (gammu.ERR_UNKNOWN):
            timeout_after_time = time.time() - time_before_send
            debug("Got exception after {} seconds while trying to send sms.".format(timeout_after_time))
            raise # re-raise exception so we get the stacktrace to stderr
    errors_file = work_dir + '/GAMMU_ERRORS'
    if os.path.isfile(errors_file):
        with open(errors_file, 'r') as f:
            gammu_errors_count = f.readlines()[0]
    current_reboot_threshold = '-'
    errors_threshold_file = work_dir + '/ERRORS_THRESHOLD_BEFORE_REBOOT'
    if os.path.isfile(errors_threshold_file):
        with open(errors_threshold_file, 'r') as f:
            current_reboot_threshold = f.readlines()[0]

    send_success = False
    send_attempts = 0
    retry_interval_seconds = 60
    while not send_success:
        try:
            send_attempts += 1
    	    
            sms_sender = SmsSender(gammu_config_file, gammu_config_section)	

            reboot_message = u'Hi Admin! Reboot done @ {0}. Power is {1}. Inet: {2}. {3} sms send attempts needed. 3G dongle error status: {4}/{5}.'.format(log_ts, power_status, localIpAddress, send_attempts, gammu_errors_count, current_reboot_threshold)
            
            sms_sender.send_sms(reboot_message, admin_phone_number)
            send_success = True
        except (gammu.ERR_TIMEOUT) as e:
	    if (('Code' in e and e['Code'] == 14) and ('Where' in e and e['Where'] == 'Init')):
		print("{0} attempt #{1} to send boot-completed sms to admin failed, retrying after sleeping {2}mins ...".format(log_ts, send_attempts, retry_interval_seconds/60))
	        time.sleep(retry_interval_seconds)
	    else:
		raise

        with open(errors_file, 'r') as f:
            gammu_errors_count = f.readlines()[0]
    current_reboot_threshold = '-'
    errors_threshold_file = work_dir + '/ERRORS_THRESHOLD_BEFORE_REBOOT'
    if os.path.isfile(errors_threshold_file):
        with open(errors_threshold_file, 'r') as f:
            current_reboot_threshold = f.readlines()[0]

    send_success = False
    send_attempts = 0
    retry_interval_seconds = 60
    while not send_success:
        try:
            send_attempts += 1

            sms_sender = SmsSender(gammu_config_file, gammu_config_section)

            reboot_message = u'Hi Admin! Reboot done @ {0}. Power is {1}. Inet: {2}. {3} sms send attempts needed. 3G dongle error status: {4}/{5}.'.format(
                log_ts, power_status, localIpAddress, send_attempts,
                gammu_errors_count, current_reboot_threshold)

            sms_sender.send_sms(reboot_message, admin_phone_number)
            send_success = True
        except (gammu.ERR_TIMEOUT) as e:
            if (('Code' in e and e['Code'] == 14)
                    and ('Where' in e and e['Where'] == 'Init')):
                print(
                    "{0} attempt #{1} to send boot-completed sms to admin failed, retrying after sleeping {2}mins ..."
                    .format(log_ts, send_attempts,
                            retry_interval_seconds / 60))
                time.sleep(retry_interval_seconds)