python time module

Posted on
Tue Nov 21, 2017 9:10 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

python time module

Would anyone know why i'm getting this instead of the time

<module 'time' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/time.so'>
when it should a time string instead?

Posted on
Tue Nov 21, 2017 9:33 pm
jay (support) offline
Site Admin
User avatar
Posts: 18218
Joined: Mar 19, 2008
Location: Austin, Texas

Re: python time module

Not following you - are you running a python script? If so, please post the entire script.

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Tue Nov 21, 2017 10:13 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

It’s the dsc plugin that has been running for a while, and since the upgrade to high sierra this is happening! I can extract that part of the script if it would help... I taught it was an env problem.

Posted on
Wed Nov 22, 2017 10:05 am
jay (support) offline
Site Admin
User avatar
Posts: 18218
Joined: Mar 19, 2008
Location: Austin, Texas

Re: python time module

Can you copy/paste the full error from the Event Log window? Context is everything... ;)

Jay (Indigo Support)
Twitter | Facebook | LinkedIn

Posted on
Wed Nov 22, 2017 2:47 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

Can't it doesn't generate any error..... It just doesn't populate the variable properly anymore....

Will post the part of the script that populated the variable and take it from there!

I have this at the beginning of the script
import re
import time
import indigo
from datetime import datetime
import serial
import indigoPluginUtils
import indigoPluginUpdateChecker
------------------------------------------------
if zone.pluginProps['zoneLogChanges'] == 1:
if newState == kZoneStateOpen:
self.mylogger.log(1, u"Alarm Zone '%s' Opened." % zone.name)
# if zoneType != "zoneTypeMotion":
indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Opened at %s" % (zone.name, time))
elif newState == kZoneStateClosed:
self.mylogger.log(1, u"Alarm Zone '%s' Closed." % zone.name)
# if zoneType != "zoneTypeMotion":
indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Closed at %s" % (zone.name, time))

if zoneType == "zoneTypeMotion":
indigo.variable.updateValue("DSC_Last_Motion_Active", value = "%s at %s" % (zone.name, time))
---------------------------------------------------------------------
# Converts given time in minutes to a human format
# 3m, 5h, 2d, etc.
#
def getShortTime(self, minutes):

# If time is less than 100 min then show XXm
if minutes < 100:
return str(minutes) + 'm'
# If it's less than 49 hours then show XXh
elif minutes < 2881:
return str(int(minutes / 60)) + 'h'
# If it's less than 100 days then show XXd
elif minutes < 144000:
return str(int(minutes / 1440)) + 'd'
# If it's anything more than one hundred days then show nothing
else:
return ''


Not sure if i was able to pick all the piece that related to the time function....

Posted on
Wed Nov 22, 2017 3:24 pm
FlyingDiver offline
User avatar
Posts: 7213
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: python time module

kidney wrote:
indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Opened at %s" % (zone.name, time))
indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Closed at %s" % (zone.name, time))
indigo.variable.updateValue("DSC_Last_Motion_Active", value = "%s at %s" % (zone.name, time))


All of these statements are broken. You're putting "time" in as a variable or object, when it's only been declared as the library. ("import time"). You need to actually get a time object using the time library, then use that to get a string with the current time to put in those statements.

I just looked at the DSC plugin code, and it looks like you're trying to modify it to add the timestamp to the variables. Is that right?

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Wed Nov 22, 2017 3:29 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

Well it's possible, i'm not a coder and always try my best to do stuff on my own....

I know that it will mean nothing, but that code used to works fine before upgrading to High Sierra for at least a year..... the only change is the upgrade to high sierra!

I can send you my version of the DSC plugin if you want to see better?

Posted on
Wed Nov 22, 2017 3:31 pm
FlyingDiver offline
User avatar
Posts: 7213
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: python time module

Just delete the DSC plugin and reinstall it from the the link in viewtopic.php?f=56&t=10287

If you didn't deliberately modify it, then the version you have is either strangely corrupted, very old, or something else. What you posted is NOT in the current version.

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Wed Nov 22, 2017 3:51 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

Can't do that otherwise a lot of stuff wont work anymore if I don't certain info!

Here is the one i'm using right now and that used to work before the update

Code: Select all
#! /usr/bin/env python
# -*- coding: utf-8 -*-
####################
# DSC Alarm Plugin
# Developed by Travis Cook and modified by Monstergerm
# www.frightideas.com

import re
import time
import indigo
from datetime import datetime
import serial
import indigoPluginUtils
import indigoPluginUpdateChecker

kSocketPort = 1514
kSocketBufferSize = 1024
kSocketTimeout = 1

kZoneStateOpen = 'open'
kZoneStateClosed = 'closed'
kZoneStateTripped = 'tripped'
kZoneBypassNo = 'nobypass'
kZoneBypassYes = 'bypassed'
kZoneGroupStateOpen = 'zoneOpen'
kZoneGroupStateClosed = 'allZonesClosed'
kZoneGroupStateTripped = 'zoneTripped'
kAlarmStateDisarmed = u'disarmed'
kAlarmStateExitDelay = u'exitDelay'
kAlarmStateFailedToArm = u'FailedToArm'
kAlarmStateArmed = u'armed'
kAlarmStateEntryDelay = u'entryDelay'
kAlarmStateTripped = u'tripped'
kKeypadStateChimeEnabled = u'enabled'
kKeypadStateChimeDisabled = u'disabled'
kAlarmArmedStateDisarmed = u'disarmed'
kAlarmArmedStateStay = u'stay'
kAlarmArmedStateAway = u'away'
kReadyStateTrue = u'ready'
kReadyStateFalse = u'notready'
kPanicStateNone = u'none'
kPanicStateFire = u'fire'
kPanicStateAmbulance = u'ambulance'
kPanicStatePanic = u'panic'
kPanicStateDuress = u'duress'

kPluginUpdateUrl = u'http://www.frightideas.com/hobbies/dscAlarm/dscAlarmVersionInfo.html'

kLedIndexList = ['None', 'Ready', 'Armed', 'Memory', 'Bypass', 'Trouble', 'Program', 'Fire', 'Backlight', 'AC']
kLedStateList = ['off', 'on', 'flashing']
kArmedModeList = ['Away', 'Stay', 'Away, No Delay', 'Stay, No Delay']
kPanicTypeList = ['None', 'Fire', 'Ambulance', 'Panic', 'Duress']
kMonthList = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']


kCmdNormal = 0
kCmdThermoSet = 1
kPingInterval = 301
kHoldRetryTimeMinutes = 3





# Note the "indigo" module is automatically imported and made available inside
# our global name space by the host process.

################################################################################
class Plugin(indigo.PluginBase):

   ########################################
   def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs):
      indigo.PluginBase.__init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs)

      self.mylogger = indigoPluginUtils.logger(self)
      self.updater = indigoPluginUpdateChecker.updateChecker(self, kPluginUpdateUrl)

      self.States = self.enum(STARTUP=1, HOLD=2, HOLD_RETRY=3, HOLD_RETRY_LOOP=4, BOTH_INIT=5, SO_CONNECT=6, ENABLE_TIME_BROADCAST=7, BOTH_PING=8, BOTH_POLL=9)
      self.state = self.States.STARTUP
      self.logLevel = 1
      self.shutdown = False
      self.configRead = False
      self.interfaceState = 0
      self.zoneList = {}
      self.tempList = {}
      self.zoneGroupList = {}
      self.trippedZoneList = []
      self.triggerList = []
      self.keypadList = {}
      self.createVariables = False
      self.port = None
      self.repeatAlarmTripped = False
      self.isPortOpen = False
      self.useSerial = False
      self.txCmdList = []
      self.closeTheseZonesList = []
      self.currentHoldRetryTime = kHoldRetryTimeMinutes
      self.ourVariableFolder = None
      self.configEmailUrgent = ""
      self.configEmailNotice = ""
      self.configEmailDisarm = ""
      self.configEmailUrgentSubject = ""
      self.configEmailNoticeSubject = ""
      self.configEmailDisarmSubject = ""
      self.userCode = ""
      self.userLabel = ""
      self.userLabelDict = {}
      self.configSpeakVariable = None
      self.configKeepTimeSynced = True
      self.troubleCode = 0
      self.troubleClearedTimer = 0
      if "DSC" not in indigo.variables.folders:
         indigo.variables.folder.create("DSC")
      if "DSC_RAW_Data" not in indigo.variables:
         indigo.variable.create("DSC_RAW_Data", value="", folder="DSC")
      if "DSC_Alarm_Text" not in indigo.variables:
         indigo.variable.create("DSC_Alarm_Text", value="", folder="DSC")
      if "DSC_Alarm_Memory" not in indigo.variables:
         indigo.variable.create("DSC_Alarm_Memory", value="no tripped zones", folder="DSC")
      if "DSC_Command" not in indigo.variables:
         indigo.variable.create("DSC_Command", value="#", folder="DSC")
      if "DSC_Last_User_Disarm" not in indigo.variables:
         indigo.variable.create("DSC_Last_User_Disarm", value="", folder="DSC")
      if "DSC_Last_User" not in indigo.variables:
         indigo.variable.create("DSC_Last_User", value="", folder="DSC")
      if "DSC_Last_Zone_Active" not in indigo.variables:
         indigo.variable.create("DSC_Last_Zone_Active", value="", folder="DSC")
      if "DSC_Last_Motion_Active" not in indigo.variables:
         indigo.variable.create("DSC_Last_Motion_Active", value="", folder="DSC")
      if "DSC_Temp_Outside" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Outside", value="", folder="DSC")
      if "DSC_Temp_Inside" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Inside", value="", folder="DSC")
      if "DSC_Temp_Cool_Setpoint" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Cool_Setpoint", value="", folder="DSC")
      if "DSC_Temp_Heat_Setpoint" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Heat_Setpoint", value="", folder="DSC")




   def enum(self, **enums):
      return type('Enum', (), enums)

   def __del__(self):
      indigo.PluginBase.__del__(self)

   ########################################
   def startup(self):
      self.mylogger.log(4, u"startup called")
      self.configRead = self.getConfiguration(self.pluginPrefs)
      self.updater.checkVersionPoll()

   def shutdown(self):
      self.mylogger.log(4, u"shutdown called")


   ######################################################################################
   # Indigo Device Start/Stop
   ######################################################################################

   def deviceStartComm(self, dev):
      self.mylogger.log(4, u"<<-- entering deviceStartComm: %s (%d - %s)" % (dev.name, dev.id, dev.deviceTypeId))

      props = dev.pluginProps

      if dev.deviceTypeId == u'alarmZoneGroup':

         if dev.id not in self.zoneGroupList:
            self.zoneGroupList[dev.id] = props[u'devList']

         if dev.states[u'state'] == 0:
            dev.updateStateOnServer(key=u"state", value=kZoneGroupStateClosed)
            
         if 'AnyMemberLastChangedShort' not in dev.states:   
            dev.stateListOrDisplayStateIdChanged()

         if 'EntireGroupLastChangedShort' not in dev.states:   
            dev.stateListOrDisplayStateIdChanged()

      elif dev.deviceTypeId == u'alarmZone':
         if 'zoneNumber' not in props:
            return

         zone = int(props['zoneNumber'])
         if zone not in self.zoneList.keys():
            self.zoneList[zone] = dev.id
         else:
            self.mylogger.logError("Zone %s is already assigned to another device." % zone)

         # Check for new version zone states.
         # If they're not present tell Indigo to reread the Devices.xml file
         if 'LastChangedShort' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()

         # If state is invalid or not there, set to closed
         if dev.states['state'] == 0:
            dev.updateStateOnServer(key=u'state', value=kZoneStateClosed)

         # If plugin is v2 we won't have a bypass key so set it to default
         if 'bypass' not in dev.states or dev.states['bypass'] == 0:
            dev.stateListOrDisplayStateIdChanged()
            dev.updateStateOnServer(key=u'bypass', value=kZoneBypassNo)

         # If plugin is v2 we won't have a zonePartition key so set it to 1 by default
         if 'zonePartition' not in props:
            props.update({'zonePartition': '1'})
            dev.replacePluginPropsOnServer(props)

         dev.updateStateOnServer(key=u"LastChangedShort", value=self.getShortTime(dev.states[u"LastChangedTimer"]))


         # Check for new version properties to see if we need to refresh the device
         if 'occupancyGroup' not in props:
            self.mylogger.log(3, u"Adding occupancyGroup to device %s properties." % dev.name)
            props.update({"occupancyGroup": 0})
            dev.replacePluginPropsOnServer(props)

         # If the variable we used no longer exists then remove the varID
         if "var" in props:
            if props["var"] not in indigo.variables:
               props["var"] = None
               dev.replacePluginPropsOnServer(props)
         else:
            props["var"] = None
            dev.replacePluginPropsOnServer(props)


      elif dev.deviceTypeId == u'alarmKeypad':
         partition = int(dev.pluginProps['partitionNumber'])
         if partition not in self.keypadList.keys():
            self.keypadList[partition] = dev.id
            self.mylogger.log(3, u"Adding keypad: %s" % self.keypadList)
         else:
            self.mylogger.logError("Partition is already assigned to another device.")

         # If plugin is v2 we won't have a partitionName key so set it to default
         if 'partitionName' not in dev.pluginProps:
            props['partitionName'] = 'Default'
            dev.replacePluginPropsOnServer(props)

         dev.updateStateOnServer(key=u'state', value=kAlarmStateDisarmed)
         dev.updateStateOnServer(key=u'ReadyState', value=kReadyStateTrue)
         dev.updateStateOnServer(key=u'PanicState', value=kPanicStateNone)

         # Check for new keypad states.
         # If they're not present tell Indigo to reread the Devices.xml file
         if 'ArmedState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()
         elif 'ReadyState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()
         elif 'PanicState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()


      elif dev.deviceTypeId == u'alarmTemp':
         sensor = int(dev.pluginProps['sensorNumber'])
         if sensor not in self.tempList.keys():
            self.tempList[sensor] = dev

      self.mylogger.log(4, u"exiting deviceStartComm -->>")


   def deviceStopComm(self, dev):
      self.mylogger.log(4, u"<<-- entering deviceStopComm: %s (%d - %s)" % (dev.name, dev.id, dev.deviceTypeId))

      if dev.deviceTypeId == u'alarmZoneGroup':
         if dev.id in self.zoneGroupList:
            del self.zoneGroupList[dev.id]

      elif dev.deviceTypeId == u'alarmZone':
         if 'zoneNumber' in dev.pluginProps:
            zone = int(dev.pluginProps['zoneNumber'])
            if zone in self.zoneList.keys():
               del self.zoneList[zone]
            #self.mylogger.log(3, u"ZoneList is now: %s" % self.zoneList)

      elif dev.deviceTypeId == u'alarmKeypad':
         if 'partitionNumber' in dev.pluginProps:
            keyp = int(dev.pluginProps['partitionNumber'])
            if keyp in self.keypadList:
               del self.keypadList[keyp]

      elif dev.deviceTypeId == u'alarmTemp':
         if 'sensorNumber' in dev.pluginProps:
            tmp = int(dev.pluginProps['sensorNumber'])
            if tmp in self.tempList:
               del self.tempList[int(dev.pluginProps['sensorNumber'])]

      self.mylogger.log(4, u"exiting deviceStopComm -->>")


   #def deviceUpdated(self, origDev, newDev):
   #   self.mylogger.log(4, u"<<-- entering deviceUpdated: %s" % origDev.name)
   #   origDev.name = newDev.name
   #   self.mylogger.log(4, u"OrigDev now: %s" % origDev.name)
   #   self.DigiTemp.deviceStop(origDev)
   #   self.DigiTemp.deviceStart(newDev)

   ######################################################################################
   # Indigo Trigger Start/Stop
   ######################################################################################

   def triggerStartProcessing(self, trigger):
      self.mylogger.log(4, u"<<-- entering triggerStartProcessing: %s (%d)" % (trigger.name, trigger.id))
      self.triggerList.append(trigger.id)
      self.mylogger.log(4, u"exiting triggerStartProcessing -->>")

   def triggerStopProcessing(self, trigger):
      self.mylogger.log(4, u"<<-- entering triggerStopProcessing: %s (%d)" % (trigger.name, trigger.id))
      if trigger.id in self.triggerList:
         self.mylogger.log(4, u"TRIGGER FOUND")
         self.triggerList.remove(trigger.id)
      self.mylogger.log(4, u"exiting triggerStopProcessing -->>")

   #def triggerUpdated(self, origDev, newDev):
   #   self.mylogger.log(4, u"<<-- entering triggerUpdated: %s" % origDev.name)
   #   self.triggerStopProcessing(origDev)
   #   self.triggerStartProcessing(newDev)


   ######################################################################################
   # Indigo Trigger Firing
   ######################################################################################

   def triggerEvent(self, eventId):
      self.mylogger.log(4, u"<<-- entering triggerEvent: %s " % eventId)
      for trigId in self.triggerList:
         trigger = indigo.triggers[trigId]
         if trigger.pluginTypeId == eventId:
            indigo.trigger.execute(trigger)
      return


   ######################################################################################
   # Indigo Menu Action Methods
   ######################################################################################

   def checkForUpdates(self):
      self.mylogger.log(1, u"Manually checking for updates")
      self.updater.checkVersionNow()


   ######################################################################################
   # Indigo Action Methods
   ######################################################################################
   #These are partition specific commands, except global and panic alarms.

   def methodDisarmAlarm(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      #keyp = keyp[0][0] might return first character as string
      self.mylogger.log(1, u"Disarming Alarm. (Partition %s '%s')" % (keyp, keypname))
      tx = "".join(["040", keyp, self.pluginPrefs[u'code'], "0"*(6-len(self.pluginPrefs[u'code']))])
      self.txCmdList.append((kCmdNormal, tx))


   def methodArmStay(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      self.mylogger.log(1, u"Arming Alarm in Stay Mode. (Partition %s '%s')" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '031' + keyp))


   def methodArmAway(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      self.mylogger.log(1, u"Arming Alarm in Away Mode. (Partition %s '%s')" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '030' + keyp))


   def methodArmStayForce(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keyp = dev.pluginProps["partitionNumber"]
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition is Already Armed.")
         return
      if keyp !="1": #this sets all zones to nobypass if they are in partition 2-8, since those partitions do not report zone bypass status
         tx = "".join(["071", keyp, "*100#"])
         self.mylogger.log(3, u"We have partition 2-8 and will cancel all bypass")
         self.txCmdList.append((kCmdNormal, tx))
         self.sleep(3) #add delay if keybus buffer overruns
      for zoneNum in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneNum]]
         zonePartition = zone.pluginProps['zonePartition']
         if zone.states[u'state.open'] is True and zone.states[u'bypass'] == kZoneBypassNo and zonePartition == keyp:
            self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
            zoneNum = str(zoneNum).zfill(2)
            tx = "".join(["071", keyp, "*1", zoneNum, "#"])
            self.txCmdList.append((kCmdNormal, tx))
            self.sleep(2) #add delay if keybus buffer overruns
      self.mylogger.log(1, u"Arming Alarm in Forced Stay Mode. (Partition %s%s)" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '031' + keyp))


   def methodArmAwayForce(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keyp = dev.pluginProps["partitionNumber"]
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition is Already Armed.")
         return
      if keyp !="1": #this sets all zones to nobypass if they are in partition 2-8, since those partitions do not report zone bypass status
         tx = "".join(["071", keyp, "*100#"])
         self.mylogger.log(3, u"We have partition 2-8 and will cancel all bypass")
         self.txCmdList.append((kCmdNormal, tx))
         self.sleep(3) #add delay if keybus buffer overruns
      for zoneNum in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneNum]]
         zonePartition = zone.pluginProps['zonePartition']
         if zone.states[u'state.open'] is True and zone.states[u'bypass'] == kZoneBypassNo and zonePartition == keyp:
            self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
            zoneNum = str(zoneNum).zfill(2)
            tx = "".join(["071", keyp, "*1", zoneNum, "#"])
            self.txCmdList.append((kCmdNormal, tx))
            self.sleep(2) #add delay if keybus buffer overruns
      self.mylogger.log(1, u"Arming Alarm in Forced Away Mode. (Partition %s%s)" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '030' + keyp))


   def methodArmGlobal(self, action):
      #this action arms all defined partitions in away mode.
      self.mylogger.log(1, u"Arming Alarm in Global Mode (All Partitions).")
      for i in range(1, 9):
         if i in self.keypadList.keys():
            key = str(i)
            self.txCmdList.append((kCmdNormal, '030' + key))
            time.sleep(1)


   def methodPanicAlarm(self, action):
      panicType = action.props[u'panicAlarmType']
      self.mylogger.log(1, u"Activating Panic Alarm! (%s)" % kPanicTypeList[int(panicType)])
      self.txCmdList.append((kCmdNormal, '060' + panicType))


   def methodSendKeypress070(self, action):
      self.mylogger.log(3, u"Received Send Keypress 070 Action")
      keys = action.props[u'keys']
      firstChar = True
      sendBreak = False
      for char in keys:
         if char == 'L':
            time.sleep(2)
            sendBreak = False

         if (firstChar is False):
            self.txCmdList.append((kCmdNormal, '070^'))

         if char != 'L':
            self.txCmdList.append((kCmdNormal, '070' + char))
            sendBreak = True

         firstChar = False
      if (sendBreak is True):
         self.txCmdList.append((kCmdNormal, '070^'))


   def methodSendKeypress071(self, action, dev):
       keypname = str(dev.pluginProps['partitionName'])
       keypname = " '" + keypname + "'"
       keyp = dev.pluginProps["partitionNumber"]
       keys = action.props[u'keys']
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
       self.mylogger.log(1, u"Received Send Keypress Action [%s]. (Partition %s%s)" % (keys, keyp, keypname))
       cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
      if len(keys) != len(cleanKeys) or "*8" in keys:
         self.mylogger.log(1, u"There are Invalid Keys in your Command.")
         return
      if len(keys) > 6:
         self.mylogger.log(1, u"The Key Command is too long.")
         return
       tx = "".join(["071", keyp, keys])
       self.txCmdList.append((kCmdNormal, tx))


   def methodSendKeypressVariable(self, action):
      keys = indigo.variables ["DSC_Command"]
      keys = keys.value
      self.mylogger.log(1, u"Received Send Keypress Action [%s]." % keys)
      cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if len(keys) != len(cleanKeys) or "*8" in keys:
         self.mylogger.log(1, u"There are Invalid Keys in your DSCcommand Variable.")
         return
      if len(keys) > 7:
         self.mylogger.log(1, u"The Key Command is too long.")
         return
      if keys[0] < "0" or keys[0] > "8":
         self.mylogger.log(1, u"The First Character in your DSCcommand Needs to be a Valid Partition (1-8).")
      else:
         self.txCmdList.append((kCmdNormal, '071' + keys))


   def methodBypassZone(self, action, dev):
      key = dev.pluginProps["zoneNumber"]
      keyp = dev.pluginProps["zonePartition"]
      zone = indigo.devices[self.zoneList[int(key)]]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Zone '%s' is Armed and Cannot be Bypassed." % zone.name)
         return
      self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
      key = str(key).zfill(2)
      tx = "".join(["071", keyp, "*1", key, "#"])
      self.txCmdList.append((kCmdNormal, tx))
      #This is a toggle action, i.e. already bypassed zones will turn to non-bypassed state.
      #Zones in partition 2-8 do not report bypass status, therefore there will be no zone state update.


   def methodBypassZoneCancel(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition %s%s is Armed and Cannot Accept Bypass Cancel." % (keyp, keypname))
         return
      self.mylogger.log(1, u"Received All Zones Bypass Cancel for Partition %s%s." % (keyp, keypname))
      tx = "".join(["071", keyp, "*100#"])
      self.txCmdList.append((kCmdNormal, tx))


   def methodBypassZoneRecall(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition %s%s is Armed and Cannot Accept Bypass Recall." % (keyp, keypname))
         return
      self.mylogger.log(1, u"Received Zone(s) Bypass Recall for Partition %s%s." % (keyp, keypname))
      tx = "".join(["071", keyp, "*199#"])
      self.txCmdList.append((kCmdNormal, tx))


   def methodDoorChimeEnable(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['KeypadChime'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      self.mylogger.log(1, u"Received Keypad Chime Enable for Partition %s%s." % (keyp, keypname))
      if keypstate != kKeypadStateChimeEnabled:
         tx = "".join(["071", keyp, "*4"])
         self.txCmdList.append((kCmdNormal, tx))
         return


   def methodDoorChimeDisable(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['KeypadChime'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      self.mylogger.log(1, u"Received Keypad Chime Disable for Partition %s%s." % (keyp, keypname))
      if keypstate == kKeypadStateChimeEnabled:
         tx = "".join(["071", keyp, "*4"])
         self.txCmdList.append((kCmdNormal, tx))
         return


    # Queue a command to set DSC Thermostat Setpoints
   #
   def methodAdjustThermostat(self, action):
      self.mylogger.log(3, u"Device %s:" % action)
      self.txCmdList.append((kCmdThermoSet, action))


   # The command queued above calls this routine to create the packet
   #
   def setThermostat(self, action):
      #find this thermostat in our list to get the number
      for sensorNum in self.tempList.keys():
         if self.tempList[sensorNum].id == action.deviceId:
            break

      self.mylogger.log(3, u"SensorNum = %s" % sensorNum)

      #send 095 for thermostat in question, wait for 563 response
      #self.mylogger.log(3, u'095' + str(sensorNum))
      rx = self.sendPacket(u'095' + str(sensorNum), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error getting current thermostat setpoints, aborting adjustment.')
         return

      if (action.props[u'thermoAdjustmentType'] == u'+') or (action.props[u'thermoAdjustmentType'] == u'-'):
         sp = 0
      else:
         sp = int(action.props[u'thermoSetPoint'])

      # then 096TC+000 to inc cool,
      #      096Th-000 to dec heat
      #      096Th=### to set setpoint
      # wait for 563 response
      #self.mylogger.log(3, u'096%u%c%c%03u' % (sensorNum, action.props[u'thermoAdjustWhich'], action.props[u'thermoAdjustmentType'],sp) )
      rx = self.sendPacket(u'096%u%c%c%03u' % (sensorNum, action.props[u'thermoAdjustWhich'], action.props[u'thermoAdjustmentType'], sp), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error changing thermostat setpoints, aborting adjustment.')
         return

      # send 097T
      #send 097 for thermostat in question to save setting, wait for 563 response
      rx = self.sendPacket(u'097' + str(sensorNum), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error saving thermostat setpoints, aborting adjustment.')
         return


   # Reset an Alarm Zone Group's timers to 0
   #
   def methodResetZoneGroupTimer(self, action):
      if action.deviceId in indigo.devices:
         zoneGrp = indigo.devices[action.deviceId]
         self.mylogger.log(3, u"Manual timer reset for alarm zone group \"%s\"" % zoneGrp.name)
         zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=0)
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=0)
         zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedShort", value="0m")
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedShort", value="0m")


   ######################################################################################
   # Indigo Pref UI Methods
   ######################################################################################

   # Validate the pluginConfig window after user hits OK
   # Returns False on failure, True on success
   #
   def validatePrefsConfigUi(self, valuesDict):
      self.mylogger.log(3, u"validating Prefs called")
      errorMsgDict = indigo.Dict()
      wasError = False

      if valuesDict[u'configInterface'] == u'serial':
         if len(valuesDict[u'serialPort']) == 0:
            errorMsgDict[u'serialPort'] = u"Select a valid serial port."
            wasError = True
      else:
         if len(valuesDict[u'TwoDS_Address']) == 0:
            errorMsgDict[u'TwoDS_Address'] = u"Enter a valid IP address or host name."
            wasError = True
         if len(valuesDict[u'TwoDS_Password']) == 0:
            errorMsgDict[u'TwoDS_Password'] = u"Enter the password for the 2DS."
            wasError = True

      if len(valuesDict[u'code']) > 6:
         errorMsgDict[u'code'] = u"The code must be 6 digits or less."
         wasError = True

      if len(valuesDict[u'code']) == 0:
         errorMsgDict[u'code'] = u"You must enter the alarm's arm/disarm code."
         wasError = True

      if len(valuesDict[u'emailUrgent']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'emailUrgent']):
            errorMsgDict[u'emailUrgent'] = u"Please enter a valid email address."
            wasError = True

      if len(valuesDict[u'updaterEmail']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'updaterEmail']):
            errorMsgDict[u'updaterEmail'] = u"Please enter a valid email address."
            wasError = True

      if len(valuesDict[u'EmailDisarm']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'EmailDisarm']):
            errorMsgDict[u'EmailDisarm'] = u"Please enter a valid email address."
            wasError = True

      userCodeList = valuesDict['userCode'].split(",")
      userLabelList = valuesDict['userLabel'].split(",")
      newuserCodeList = [s for s in userCodeList if s.isdigit()]   #transfers only numbers to newUserCodeList
      length = len(userCodeList)

      if newuserCodeList and (any(len(lst) != length for lst in [newuserCodeList])):
         errorMsgDict[u'userCode'] = u"Config Error: The user code contains invalid characters (2-digit numbers only)."
         wasError = True

      if any(len(lst) != length for lst in [userLabelList]):
         errorMsgDict[u'userCode'] = u"Config Error: The user code and label lists are not of the same length."
         errorMsgDict[u'userLabel'] = u"Config Error: The user code and label lists are not of the same length."
         wasError = True

      if len(userCodeList) != len(set(userCodeList)):
         errorMsgDict[u'userCode'] = u"Config Error: The user code list contains duplicate codes."
         wasError = True

      for key in newuserCodeList:
         if int(key) < 1 or int(key) > 40:
            errorMsgDict[u'userCode'] = u"Config Error: The user code is out of bound."
            wasError = True


      if wasError is True:
         return (False, valuesDict, errorMsgDict)

      # Tell DSC module to reread it's config
      self.configRead = False

      # User choices look good, so return True (client will then close the dialog window).
      return (True, valuesDict)

   def validateActionConfigUi(self, valuesDict, typeId, actionId):
      self.mylogger.log(3, u"validating Action Config called")
      if typeId == u'actionSendKeypress':
         keys = valuesDict[u'keys']
         cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
         if len(keys) != len(cleanKeys) or "*8" in keys:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'keys'] = u"There are invalid keys in your keystring."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)


   def validateEventConfigUi(self, valuesDict, typeId, eventId):
      self.mylogger.log(3, u"validating Event Config called")
      #self.mylogger.log(3, u"Type: %s, Id: %s, Dict: %s" % (typeId, eventId, valuesDict))
      if typeId == u'userArmed' or typeId == u'userDisarmed':
         code = valuesDict[u'userCode']
         if len(code) != 2:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'userCode'] = u"The user code must be 2 digits in length."
            return (False, valuesDict, errorMsgDict)

         cleanCode = re.sub(r'[^0-9]+', '', code)
         if len(code) != len(cleanCode):
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'userCode'] = u"The code can only contain digits 0-9."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)

   def validateDeviceConfigUi(self, valuesDict, typeId, devId):
      self.mylogger.log(3, u"validating Device Config called")
      #self.mylogger.log(3, u"Type: %s, Id: %s, Dict: %s" % (typeId, devId, valuesDict))
      if typeId == u'alarmZone':
         zoneNum = int(valuesDict[u'zoneNumber'])
         if zoneNum in self.zoneList.keys() and devId != indigo.devices[self.zoneList[zoneNum]].id:
            #self.mylogger.log(3, u"ZONEID: %s" % self.DSC.zoneList[zone].id)
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'zoneNumber'] = u"This zone has already been assigned to a different device."
            return (False, valuesDict, errorMsgDict)
      if typeId == u'alarmKeypad':
         partitionNum = int(valuesDict[u'partitionNumber'])
         if partitionNum in self.keypadList.keys() and devId != indigo.devices[self.keypadList[partitionNum]].id:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'partitionNumber'] = u"This partition has already been assigned to a different device."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)


   def getZoneList(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []
      for i in range(1, 65):
         zoneName = str(i)
         if i in self.zoneList.keys():
            zoneDev = indigo.devices[self.zoneList[i]]
            zoneName = ''.join([str(i), ' - ', zoneDev.name])
         myArray.append((str(i), zoneName))
      return myArray


   def getZoneDevices(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []

      for dev in indigo.devices:
         try:
            if dev.deviceTypeId == 'alarmZone':
               myArray.append((dev.id, dev.name))
         except:
            pass
      return myArray

   def getKeypadList(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []
      for i in range(1, 9):
         keypadName = str(i)
         if i in self.keypadList.keys():
            keypDev = indigo.devices[self.keypadList[i]]
            keypadName = str(keypDev.pluginProps['partitionName'])
            keypadName = ''.join([str(i), ' - ', keypadName])
            #keypadName = ''.join([str(i), ' - ', keypDev.name])
         myArray.append((str(i), keypadName))
      return myArray




   ######################################################################################
   # Configuration Routines
   ######################################################################################

   # Reads the plugins config file into our own variables
   #
   def getConfiguration(self, valuesDict):

      # Tell our logging class to reread the config for level changes
      self.mylogger.readConfig()

      self.mylogger.log(3, u"getConfiguration start")

      try:

         # Get setting of Create Variables checkbox
         if valuesDict[u'createVariables'] is True:
            self.createVariables = True
         else:
            self.createVariables = False

         # If the variable folder doesn't exist, disable variables, we're done!
         if valuesDict[u'variableFolder'] not in indigo.variables.folders:
            self.createVariables = False

         self.useSerial = False
         if valuesDict[u'configInterface'] == 'serial':
            # using serial port interface IT-100 or similar
            self.useSerial = True

         self.configKeepTimeSynced = valuesDict.get(u'syncTime', True)

         self.configSpeakVariable = None
         if u'speakToVariableEnabled' in valuesDict:
            if valuesDict[u'speakToVariableEnabled'] is True:
               self.configSpeakVariable = int(valuesDict[u'speakToVariableId'])
               if self.configSpeakVariable not in indigo.variables:
                  self.mylogger.logError('Speak variable not found in variable list')
                  self.configSpeakVariable = None

         self.configEmailUrgent = valuesDict.get(u'emailUrgent', '')
         self.configEmailNotice = valuesDict.get(u'updaterEmail', '')
         self.configEmailDisarm = valuesDict.get(u'EmailDisarm', '')
         self.configEmailUrgentSubject = valuesDict.get(u'emailUrgentSubject', 'Alarm Tripped')
         self.configEmailNoticeSubject = valuesDict.get(u'updaterEmailSubject', 'Alarm Trouble')
         self.configEmailDisarmSubject = valuesDict.get(u'DisarmEmailSubject', 'Who Disarmed')

         self.userCodeList = valuesDict.get(u'userCode', '').split(",")
         self.userLabelList = valuesDict.get(u'userLabel', '').split(",")
         self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))

         self.mylogger.log(3, u"Configuration read successfully")

         return True

      except:
         self.mylogger.log(2, u"Error reading plugin configuration. (happens on very first launch)")

         return False


   ######################################################################################
   # Communication Routines
   ######################################################################################

   def calcChecksum(self, s):
      calcSum = 0
      for c in s:
         calcSum += ord(c)
      calcSum %= 256
      return calcSum

   def closePort(self):
      if self.port is None:
         return
      if self.port.isOpen() is True:
         self.port.close()
         self.port = None

   def openPort(self):
      self.closePort()
      if self.useSerial is False:
         adr = self.pluginPrefs[u'TwoDS_Address'] + ':4025'
         self.mylogger.log(1, u"Initializing communication at address: %s" % adr)
         try:
            self.port = serial.serial_for_url('socket://' + adr, baudrate=115200)
         except Exception, err:
            self.mylogger.logError('Error opening socket: %s' % (str(err)))
            return False
      else:
         self.mylogger.log(1, u"Initializing communication on port %s" % self.pluginPrefs[u'serialPort'])
         try:
            self.port = serial.Serial(self.pluginPrefs[u'serialPort'], 9600, writeTimeout=1)
         except Exception, err:
            self.mylogger.logError('Error opening serial port: %s' % (str(err)))
            return False

      if self.port.isOpen() is True:
         self.port.flushInput()
         self.port.timeout = 1
         self.mylogger.log(1, u"Communication established")
         return True

      return False


   def readPort(self):
      if self.port.isOpen() is False:
         self.state = self.States.BOTH_INIT
         return ""

      data = ""
      try:
         data = self.port.readline()
      except Exception, err:
         self.mylogger.logError('Connection RX Error: %s' % (str(err)))
         # Return with '-' signaling calling subs to abort so we can re-init.
         data = '-'
         #exit()
      except:
         self.mylogger.logError('Connection RX Problem, plugin quitting')
         exit()
      return data

### Needed for programming purpose pure raw DATA from the port it self oin it's raw nature.
###
      indigo.variable.updateValue("DSC_RAW_Data", value = data)
###
   
   def writePort(self, data):
      self.port.write(data)


   def sendPacketOnly(self, data):
      pkt = "%s%02X\r\n" % (data, self.calcChecksum(data))
      self.mylogger.log(4, u"TX: %s" % pkt)
      try:
         self.writePort(pkt.encode())
      except Exception, err:
         self.mylogger.logError('Connection TX Error: %s' % (str(err)))
         exit()
      except:
         self.mylogger.logError('Connection TX Problem, plugin quitting')
         exit()


   def sendPacket(self, tx, waitFor='500', rxTimeout=3, txRetries=3):
      retries = txRetries
      txCmd = tx[:3]

      while txRetries > 0:
         self.sendPacketOnly(tx)
         ourTimeout = time.time() + rxTimeout
         txRetries -= 1
         while time.time() < ourTimeout:
            if self.shutdown is True:
               return ''
            (rxCmd, rxData) = self.readPacket()

            # If rxCmd == - then the socket closed, return for re-init
            if rxCmd == '-':
               return '-'

            if rxCmd == '502':
               self.mylogger.logError('Received system error after sending command, aborting.')
               return ''

            # If rxCmd is not 0 length then we received a response
            if len(rxCmd) > 0:
               if waitFor == '500':
                  if (rxCmd == '500') and (rxData == txCmd):
                     return rxData
               elif (rxCmd == waitFor):
                  return rxData
         if txCmd != '000':
            self.mylogger.logError('Timed out after waiting for response to command %s for %u seconds, retrying.' % (tx, rxTimeout))
      self.mylogger.logError('Resent command %s %u times with no success, aborting.' % (tx, retries))
      return ''


   def readPacket(self):

      data = self.readPort()
      if len(data) == 0:
         return ('', '')
      elif data == '-':
         # socket has closed, return with signal to re-initialize
         return ('-', '')

      data = data.strip()

      m = re.search(r'^(...)(.*)(..)$', data)
      if not m:
         return ('', '')

      # Put this try in to try to catch exceptions when non-ascii characters
      # were received, not sure why they are being received.
      try:
         self.mylogger.log(4, u"RX: %s" % data)
         (cmd, dat, sum) = (m.group(1), m.group(2), int(m.group(3), 16))
      except:
         self.mylogger.logError(u'IT-100/Envisalink Error: Received a response with invalid characters')
         return ('', '')

      if sum != self.calcChecksum("".join([cmd, dat])):
         self.mylogger.logError("Checksum did not match on a received packet.")
         return ('', '')

      # Parse responses based on cmd value
      #
      
      if cmd == '500':
         self.mylogger.log(3, u"ACK for cmd %s." % dat)
         self.cmdAck = dat

      elif cmd == '501':
         self.mylogger.logError(u'IT-100/Envisalink Error: Received a command with a bad checksum')

      elif cmd == '502':
         errText = u'Unknown'

         if dat == '001':
            errText = u'Receive Buffer Overrun (a command is received while another is still being processed)'
         elif dat == '002':
            errText = u'Receive Buffer Overflow'
         elif dat == '003':
            errText = u'Transmit Buffer Overflow'

         elif dat == '010':
            errText = u'Keybus Transmit Buffer Overrun'
         elif dat == '011':
            errText = u'Keybus Transmit Time Timeout'
         elif dat == '012':
            errText = u'Keybus Transmit Mode Timeout'
         elif dat == '013':
            errText = u'Keybus Transmit Keystring Timeout'
            # this error is sometimes received after disarming a tripped partition with TPI disarm command 040
         elif dat == '014':
            errText = u'Keybus Interface Not Functioning (the TPI cannot communicate with the security system)'
         elif dat == '015':
            errText = u'Keybus Busy (Attempting to Disarm or Arm with user code)'
         elif dat == '016':
            errText = u'Keybus Busy – Lockout (The panel is currently in Keypad Lockout – too many disarm attempts)'
         elif dat == '017':
            errText = u'Keybus Busy – Installers Mode (Panel is in installers mode, most functions are unavailable)'
         elif dat == '018':
            errText = u'Keybus Busy – General Busy (The requested partition is busy)'

         elif dat == '020':
            errText = u'API Command Syntax Error'
         elif dat == '021':
            errText = u'API Command Partition Error (Requested Partition is out of bounds)'
         elif dat == '022':
            errText = u'API Command Not Supported'
         elif dat == '023':
            errText = u'API System Not Armed (sent in response to a disarm command)'
         elif dat == '024':
            errText = u'API System Not Ready to Arm (not secure, in delay, or already armed)'
            self.triggerEvent(u'eventFailToArm')
            self.speak('speakTextFailedToArm')
         elif dat == '025':
            errText = u'API Command Invalid Length'
         elif dat == '026':
            errText = u'API User Code not Required'
         elif dat == '027':
            errText = u'API Invalid Characters in Command'

         self.mylogger.logError(u"IT-100/Envisalink Error (%s): %s" % (dat, errText))

      elif cmd == '505':
         if dat == '3':
            self.mylogger.log(3, u'Received login request')

      elif cmd == '510':
         # Keypad LED State Updates for Partition 1 only
         leds = int(dat, 16)

         if leds & 1 > 0:
            self.updateKeypad(1, u'LEDReady', 'on')
         else:
            self.updateKeypad(1, u'LEDReady', 'off')

         if leds & 2 > 0:
            self.updateKeypad(1, u'LEDArmed', 'on')
         else:
            self.updateKeypad(1, u'LEDArmed', 'off')

         if leds & 16 > 0:
            self.updateKeypad(1, u'LEDTrouble', 'on')
         else:
            self.updateKeypad(1, u'LEDTrouble', 'off')

         if leds & 4 > 0:
            self.updateKeypad(1, u'LEDMemory', 'on')
         else:
            self.updateKeypad(1, u'LEDMemory', 'off')

         if leds & 8 > 0:
            self.updateKeypad(1, u'LEDBypass', 'on')
         else:
            self.updateKeypad(1, u'LEDBypass', 'off')

      elif cmd == '511':
         # Keypad LED Flashing State Update
         # Same as 510 above but means an LED is flashing
         # We don't use this right now
         pass

      elif cmd == '550':

         m = re.search(r'^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$', dat)
         if m:
            tHour = int(m.group(1))
            tMin = int(m.group(2))
            dMonth = int(m.group(3)) - 1
            dMonthDay = int(m.group(4))
            dYear = int(m.group(5))

            # Check if we should sync time
            if self.configKeepTimeSynced is True:
               d = datetime.now()
               # Is the hour different or minute off by more than one minute?
               if (d.hour != tHour) or (abs(d.minute - tMin) > 1):
                  self.mylogger.log(1, u"Setting alarm panel time and date.")
                  self.txCmdList.append((kCmdNormal, u"010%s" % d.strftime("%H%M%m%d%y")))
               else:
                  self.mylogger.log(3, u"Alarm panel time is within 1 minute of actual time, no update necessary.")

            # If this is a 2DS/Envisalink interface then lets insert the time (updated every 4 minutes)
            if self.useSerial is False:
               tAmPm = u'a'
               if tHour >= 12:
                  tAmPm = u'p'

               if tHour > 12:
                  tHour -= 12
               elif tHour == 0:
                  tHour = 12
               self.updateKeypad(0, u'LCDLine1', u'  Date     Time ')
               self.updateKeypad(0, u'LCDLine2', u"%s %02u/%02u %2u:%02u%s" % (kMonthList[dMonth], dMonthDay, dYear, tHour, tMin, tAmPm))

      elif cmd == '560':
         self.mylogger.log(1, u"Telephone Ring Tone Has Been Detected.")
         for trig in self.triggerList:
            trigger = indigo.triggers[trig]
            if trigger.pluginTypeId == u'eventNoticeTelephone_Ring':
               indigo.trigger.execute(trigger.id)


      elif cmd == '561' or cmd == '562':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (sensor, temp) = (int(m.group(1)), int(m.group(2)))
            if cmd == '562':
               self.updateSensorTemp(sensor, 'outside', temp)
            else:
               self.updateSensorTemp(sensor, 'inside', temp)

      elif cmd == '563':
         m = re.search(r'^(.)(...)(...)$', dat)
         if m:
            (sensor, cool, heat) = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
            self.updateSensorTemp(sensor, 'cool', cool)
            self.updateSensorTemp(sensor, 'heat', heat)

      elif cmd == '601':
         # a zone goes into alarm
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.updateZoneState(zone, kZoneStateTripped)
            if (len(self.trippedZoneList) == 0):
               if "DSC_Alarm_Memory" in indigo.variables:
                  indigo.variable.updateValue("DSC_Alarm_Memory", value ="")
            if zone not in self.trippedZoneList:
               self.trippedZoneList.append(zone)
               self.sendZoneTrippedEmail()
               indigoVar =""
               for zoneNum in self.trippedZoneList:
                  zone = indigo.devices[self.zoneList[zoneNum]]
                  indigoVar += (zone.name + "; ")
               if "DSC_Alarm_Memory" in indigo.variables:
                  indigo.variable.updateValue("DSC_Alarm_Memory", indigoVar)
               self.triggerEvent(u'eventZoneTripped')

      elif cmd == '602':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            dev = indigo.devices[self.keypadList[partition]]
            zonedev = indigo.devices[self.zoneList[zone]]
            keyp = str(dev.pluginProps['partitionName'])
            self.mylogger.log(1, u"Zone '%s' Restored. (Partition %d '%s')" % (zonedev.name, partition, keyp))

      elif cmd == '603':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.mylogger.log(3, u"Zone Number %d Has a Tamper Condition." % zone)

      elif cmd == '604':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.mylogger.log(3, u"Zone Number %d Tamper Condition has been Restored." % zone)

      elif cmd == '605':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Has a Fault Condition." % zone)

      elif cmd == '606':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Fault Condition has been Restored." % zone)

      elif cmd == '609':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Open." % zone)
         self.updateZoneState(zone, kZoneStateOpen)
         if self.repeatAlarmTripped is True:
            if zone in self.closeTheseZonesList:
               self.closeTheseZonesList.remove(zone)

      elif cmd == '610':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Closed." % zone)
         # Update the zone to closed ONLY if the alarm is not tripped
         # We want the tripped states to be preserved so someone looking
         # at their control page will see all the zones that have been
         # opened since the break in.
         if self.repeatAlarmTripped is False:
            self.updateZoneState(zone, kZoneStateClosed)
         else:
            self.closeTheseZonesList.append(zone)

      elif cmd == '616':
         #cmd is sent after a zone is bypassed or bypass is cancelled.
         #This dump can be forced with keypress command [1*1#] if partition is not armed. If partition is armed it will switch armed state from stay to away and vice versa.
         #Routine that identifies bypassed zones and updates zone status kZoneBypassNo or kZoneBypassYes via newState (unfortunately for partition 1 only)
         BypassHexDump = str(dat)       #this is the 16-digit hex string for bypassed zones. Partition 1 only!
         self.mylogger.log(3, u"Bypass Hex Dump (%s)" % BypassHexDump)
         BBDump = format(int(BypassHexDump, 16), '0>64b')
         BypassBinaryDump = "".join(["".join([m[i:i+1] for i in range(8-1,-1,-1)]) for m in [BBDump[i:i+8] for i in range(0,len(BBDump),8)]])
         #reordered in 8 bit words to get zones in 1-64 order
         self.mylogger.log(2, u"Bypass Binary Dump (%s)" % BypassBinaryDump)
         for i in range(64):
            if BypassBinaryDump[i-1] == '1':
               self.updateZoneBypass(i, kZoneBypassYes)
            elif BypassBinaryDump[i-1] == '0':
               self.updateZoneBypass(i, kZoneBypassNo)

      elif cmd == '620':
         self.mylogger.log(1, u"Duress Alarm Detected")
         self.sendDuressEmail(u"Duress Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateDuress)

      elif cmd == '621':
         self.mylogger.log(1, u"Fire Key Alarm Detected")
         self.sendPanicEmail(u"Fire Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateFire)

      elif cmd == '623':
         self.mylogger.log(1, u"Auxiliary/Medical Key Alarm Detected")
         self.sendPanicEmail(u"Ambulance/Medical Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateAmbulance)

      elif cmd == '625':
         self.mylogger.log(1, u"Panic Key Alarm Detected")
         self.sendPanicEmail(u"Police/Panic Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStatePanic)

      elif cmd == '631':
         self.mylogger.log(1, u"Auxiliary/Smoke Input Alarm Detected")

      elif cmd == '632':
         self.mylogger.log(1, u"Auxiliary/Smoke Input Alarm Restored")

      elif cmd == '650':
         self.mylogger.log(3, u"Partition %d Ready" % int(dat))
         partition = int(dat)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         # This reports ready state only for keypads i.e. partitions that are ready.

      elif cmd == '651':
         self.mylogger.log(3, u"Partition %d Not Ready" % int(dat))
         partition = int(dat)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')


      elif cmd == '652':
         if len(dat) == 1:
            partition = int(dat)
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            self.mylogger.log(3, u"Alarm Panel Armed. (Partition %d '%s')" % (partition, keyp))
            self.updateKeypad(partition, u'state', kAlarmStateArmed)
            self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
            self.updateKeypad(partition, u'LEDReady', 'off')
            self.updateKeypad(partition, u'LEDArmed', 'on')     # these update LEDs for partitions 1-8
            self.speak('speakTextArmed')
            self.trippedZoneList = []

         elif len(dat) == 2:
            m = re.search(r'^(.)(.)$', dat)
            if m:
               (partition, mode) = (int(m.group(1)), int(m.group(2)))
               dev = indigo.devices[self.keypadList[partition]]
               keyp = str(dev.pluginProps['partitionName'])
               self.mylogger.log(1, u"Alarm Panel Armed in %s Mode. (Partition %d '%s')" % (kArmedModeList[mode], partition, keyp))
               if (mode == 0) or (mode == 2):
                  armedEvent = u'armedAway'
                  self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateAway)
               else:
                  armedEvent = u'armedStay'
                  self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateStay)
                  self.updateKeypad(partition, u'LEDBypass', 'on')   #LED on since motion sensors are bypassed

               self.triggerEvent(armedEvent)
               for trig in self.triggerList:
                  trigger = indigo.triggers[trig]
                  if trigger.pluginTypeId == u'eventPartitionArmed':
                     if trigger.pluginProps[u'partitionNum'] == str(partition):
                        indigo.trigger.execute(trigger.id)

               self.updateKeypad(partition, u'state', kAlarmStateArmed)
               self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
               self.updateKeypad(partition, u'LEDReady', 'off')
               self.updateKeypad(partition, u'LEDArmed', 'on')     # these update LEDs for partitions 1-8
               self.speak('speakTextArmed')
               self.trippedZoneList = []


      elif cmd == '653':
         # Partition Ready - Forced Arming Enabled
         # We don't do anything with this now.
         pass

      elif cmd == '654':
         # partition is in alarm due to zone violations or panic & fire alarm
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         keyp = " '" + keyp + "'"
         self.mylogger.log(1, u"Alarm TRIPPED! (Partition %d%s)" % (partition, keyp))
         self.updateKeypad(int(dat), u'state', kAlarmStateTripped)
         self.updateKeypad(int(dat), u'LEDMemory', 'on')     # this updates LED for partitions 1-8
         self.triggerEvent(u'eventAlarmTripped')
         self.repeatAlarmTrippedNext = time.time()
         self.repeatAlarmTripped = True

      elif cmd == '655':
         # This command is send after user disarms an alarm that was not tripped (also see cmd 750, 751).
         # This is only disarm cmd sent if arming is cancelled during exit delay (which also cancels zone bypass)
         # If the alarm has been disarmed while it was tripped, update any zone states
         # that were closed during the break in.  We don't update them during the event
         # so that Indigo's zone states will represent a zone as tripped during the entire event.
         if self.repeatAlarmTripped is True:
            self.repeatAlarmTripped = False
            for zone in self.closeTheseZonesList:
               self.updateZoneState(zone, kZoneStateClosed)
            self.closeTheseZonesList = []

         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         keypstate = str(dev.states['state'])
         if partition == 1 and keypstate == kAlarmStateExitDelay:
            self.mylogger.log(1, u"Alarm Disarmed. (Partition %d '%s')" % (partition, keyp))

            #Only request bypassed zone list if we are not using IT-100
            if self.useSerial is False:
               self.sleep(1)    #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
               self.txCmdList.append((kCmdNormal, '071' + str(partition) + '*1#'))      #triggers cmd 616 only if partition was in exit delay and cmd 750 is not sent.
         self.trippedZoneList = []
         self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
         self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
         self.updateKeypad(partition, u'LEDArmed', 'off')
         self.updateKeypad(0, u'PanicState', kPanicStateNone)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'off')

         #self.triggerEvent(u'eventAlarmDisarmed') #use cmd 750 & 751 triggers
         #self.speak('speakTextDisarmed')  #use cmd 750 & 751 triggers


      elif cmd == '656':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Exit Delay. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateExitDelay)
         self.updateKeypad(partition, u'LEDArmed', 'on')
         #keyp = indigo.devices[self.keypadList[partition]]
         #LEDMemoryState = keyp.states["LEDMemory"]
         self.updateKeypad(partition, u'LEDMemory', 'off')
         if "DSC_Alarm_Memory" in indigo.variables:
            indigo.variable.updateValue("DSC_Alarm_Memory", value = "no tripped zones")
         self.speak('speakTextArming')

      elif cmd == '657':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Entry Delay. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateEntryDelay)
         self.speak('speakTextEntryDelay')

      elif cmd == '663':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Keypad Chime Enabled. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'KeypadChime', kKeypadStateChimeEnabled)

      elif cmd == '664':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Keypad Chime Disabled. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'KeypadChime', kKeypadStateChimeDisabled)

      elif cmd == '672':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Panel Failed to Arm. (Partition %d '%s')" % (partition, keyp))
         self.triggerEvent(u'eventFailToArm')
         self.speak('speakTextFailedToArm')

      elif cmd == '673':
         #sends busy reply for partitions that are not defined by the plugin
         partition = int(dat)
         self.mylogger.log(3, u"Partition %d Busy." % (partition))

      elif cmd == '700':
         m = re.search(r'^(.)..(..)$', dat)
         if m:
            (partition, user) = (int(m.group(1)), m.group(2))
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            #self.userCodeList = self.pluginPrefs[u'userCode'].split(",")
            #self.userLabelList = self.pluginPrefs[u'userLabel'].split(",")
            #self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))
            keyu = self.userLabelDict.get(user, "")
            if keyu:
               keyu = " '" + keyu +"'"
            self.mylogger.log(1, u"Alarm Panel Armed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            #No info on whether stay or away armed, but cmd 652 is also triggered with that info
            self.updateKeypad(partition, u'state', kAlarmStateArmed)
            self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
            self.updateKeypad(partition, u'LEDReady', 'off')
            self.updateKeypad(partition, u'LEDArmed', 'on')
            self.speak('speakTextArmed')
            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userArmed':
                  if trigger.pluginProps[u'userCode'] == user:
                     indigo.trigger.execute(trigger.id)

      elif cmd == '701':
         #special arming, e.g. IndigoTouch on iPhone
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(2, u"Alarm Panel Specially Armed. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateArmed)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')
         self.updateKeypad(partition, u'LEDArmed', 'on')
         self.speak('speakTextArmed')


      elif cmd == '702':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Panel Armed. (Partition %d '%s' with zone(s) bypass)" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateArmed)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')
         self.updateKeypad(partition, u'LEDArmed', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'on')
         self.speak('speakTextArmed')


      elif cmd == '750':
         # this command is send after user disarms an alarm that was tripping or not tripping (vs cmd 655).
         m = re.search(r'^(.)..(..)$', dat)
         if m:
            (partition, user) = (int(m.group(1)), m.group(2))
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            #self.userCodeList = self.pluginPrefs[u'userCode'].split(",")
            #self.userLabelList = self.pluginPrefs[u'userLabel'].split(",")
            #self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))
            keyu = self.userLabelDict.get(user, "")
            if keyu:
               keyu = " '" + keyu +"'"
            self.mylogger.log(1, u"Alarm Panel Disarmed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            self.sendEmailDisarm(u"Alarm Panel Disarmed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            if "DSC_Last_User_Disarm" in indigo.variables:
               indigo.variable.updateValue("DSC_Last_User_Disarm", value = "User " + user + keyu)
               indigo.variable.updateValue("DSC_Last_User", value = keyu[2:-1])
               
            # self.trippedZoneList = []    #We do not want to delete list of tripped zones here
            self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
            self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
            self.updateKeypad(partition, u'LEDArmed', 'off')
            self.updateKeypad(0, u'PanicState', kPanicStateNone)
            self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
            self.updateKeypad(partition, u'LEDReady', 'on')
            self.updateKeypad(partition, u'LEDBypass', 'off')
            self.triggerEvent(u'eventAlarmDisarmed')
            self.speak('speakTextDisarmed')

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userDisarmed':
                  if trigger.pluginProps[u'userCode'] == user:
                     indigo.trigger.execute(trigger.id)

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userDisarmedPartition':
                  if trigger.pluginProps[u'userCode'] == user and trigger.pluginProps[u'partitionNum'] == str(partition):
                     indigo.trigger.execute(trigger.id)

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'eventPartitionDisarmed':
                  if trigger.pluginProps[u'partitionNum'] == str(partition):
                     indigo.trigger.execute(trigger.id)

         # If the alarm has been disarmed while it was tripped, update any zone states
         # that were closed during the break in.  We don't update them during the event
         # so that Indigo's zone states will represent a zone as tripped during the entire event.
         if self.repeatAlarmTripped is True:
            self.repeatAlarmTripped = False
            for zone in self.closeTheseZonesList:
               self.updateZoneState(zone, kZoneStateClosed)
            self.closeTheseZonesList = []

         #Only request bypassed zone list if we are on partition 1 and not using IT-100
         if (partition == 1) and (self.useSerial is False):
            self.sleep(1) #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
            #self.txCmdList.append((kCmdNormal, '071' + str(partition) + '*1#'))      #triggers cmd 616
            self.txCmdList.append((kCmdNormal, '0711*1#'))      #triggers cmd 616


      elif cmd == '751':
         #special opening (triggered by keyswitch but not by Indigo Touch)
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Disarmed by Special Opening (Partition %d '%s')" % (partition, keyp))
         # self.trippedZoneList = []    #We do not want to delete list of tripped zones here
         self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
         self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
         self.updateKeypad(partition, u'LEDArmed', 'off')
         self.updateKeypad(0, u'PanicState', kPanicStateNone)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'off')

         self.triggerEvent(u'eventAlarmDisarmed')
         self.speak('speakTextDisarmed')

         for trig in self.triggerList:
            trigger = indigo.triggers[trig]
            if trigger.pluginTypeId == u'eventPartitionDisarmed':
               if trigger.pluginProps[u'partitionNum'] == str(partition):
                  indigo.trigger.execute(trigger.id)

         #Only request bypassed zone list if we are not using IT-100
         if (partition == 1) and (self.useSerial is False):
            self.sleep(1) #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
            self.txCmdList.append((kCmdNormal, '0711*1#'))      #triggers cmd 616


      elif cmd == '800':
         self.mylogger.log(1, u"Alarm Panel Battery is low.")
         self.sendTroubleEmail(u"Alarm panel battery is low.")

      elif cmd == '801':
         self.mylogger.log(1, u"Alarm Panel Battery is now ok.")
         self.sendTroubleEmail(u"Alarm panel battery is now ok.")

      elif cmd == '802':
         self.mylogger.log(1, u"AC Power Lost.")
         self.sendTroubleEmail(u"AC Power Lost.")
         self.triggerEvent(u'eventNoticeAC_Trouble')

      elif cmd == '803':
         self.mylogger.log(1, u"AC Power Restored.")
         self.sendTroubleEmail(u"AC Power Restored.")
         self.triggerEvent(u'eventNoticeAC_Restore')

      elif cmd == '806':
         self.mylogger.log(1, u"An open circuit has been detected across the bell terminals.")
         self.sendTroubleEmail(u"An open circuit has been detected across the bell terminals.")

      elif cmd == '807':
         self.mylogger.log(1, u"The bell circuit has been restored.")
         self.sendTroubleEmail(u"The bell circuit has been restored.")

      elif cmd == '814':
         self.mylogger.log(1, u"FTC Trouble.")
         self.sendTroubleEmail(u"The panel has failed to communicate successfully to the monitoring station.")

      elif cmd == '840':
         partition = int(dat)
         if partition in self.keypadList:
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            keyp = " '" + keyp + "'"
            self.mylogger.log(1, u"Trouble Status (LED ON). (Partition %d%s)" % (partition, keyp))
            self.updateKeypad(partition, u'LEDTrouble', 'on')   # this updates LED for partitions 1-8
            self.troubleClearedTimer = 0

      elif cmd == '841':
         #Sends Trouble off for all partitions, including undefined ones.
         partition = int(dat)
         self.mylogger.log(2, u"Trouble Status Restore (LED OFF). (Partition %d)" % (partition))
         self.updateKeypad(partition, u'LEDTrouble', 'off')  # this updates LED for partitions 1-8
         if self.troubleCode > 0:
            # If the trouble light goes off, set a 10 second timer.
            # If the light is still off after 10 seconds we'll clear our status
            # This is required because the panel turns the light off/on quickly
            # when the light is actually on.
            self.troubleClearedTimer = 10

      elif cmd == '849':
         self.mylogger.log(3, u"Received trouble code byte 0x%s" % dat)
         newCode = int(dat, 16)

         if newCode != self.troubleCode:
            self.troubleCode = newCode
            if self.troubleCode > 0:
               body = "Trouble Code Received:\n"
               if self.troubleCode & 1: body += "- Service is Required\n"
               if self.troubleCode & 2: body += "- AC Power Lost\n"
               if self.troubleCode & 4: body += "- Telephone Line Fault\n"
               if self.troubleCode & 8: body += "- Failure to Communicate\n"
               if self.troubleCode & 16: body += "- Sensor/Zone Fault\n"
               if self.troubleCode & 32: body += "- Sensor/Zone Tamper\n"
               if self.troubleCode & 64: body += "- Sensor/Zone Low Battery\n"
               if self.troubleCode & 128: body += "- Loss of Time\n"
               self.sendTroubleEmail(body)

      elif cmd == '851':
         self.mylogger.log(3, u"Partition Busy Restore. (Partition %d)" % int(dat))
      elif cmd == '896':
         self.mylogger.log(3, u"Keybus Fault")
      elif cmd == '897':
         self.mylogger.log(3, u"Keybus Fault Restore")
      elif cmd == '900':
         self.mylogger.logError(u"Code Required")

      elif cmd == '901':
         #this updates the virtual keypad
         #for char in dat:
         #   self.mylogger.log(3, u"LCD DEBUG: %d" % ord(char))
         m = re.search(r'^...(..)(.*)$', dat)
         if m:
            lcdText = re.sub(r'[^ a-zA-Z0-9_/\:-]+', ' ', m.group(2))
            half = len(lcdText)/2
            half1 = lcdText[:half]
            half2 = lcdText[half:]
            self.mylogger.log(3, u"LCD Update, Line 1:'%s' Line 2:'%s'" % (half1, half2))
            self.updateKeypad(0, u'LCDLine1', half1)
            self.updateKeypad(0, u'LCDLine2', half2)

      elif cmd == '903':
         m = re.search(r'^(.)(.)$', dat)
         if m:
            (ledName, ledState) = (kLedIndexList[int(m.group(1))], kLedStateList[int(m.group(2))])
            self.mylogger.log(3, u"LED '%s' is '%s'." % (ledName, ledState))

            if ledState == 'flashing':
               ledState = 'on'

            if ledName == 'Ready':
               self.updateKeypad(1, u'LEDReady', ledState)
            elif ledName == 'Armed':
               self.updateKeypad(1, u'LEDArmed', ledState)
            elif ledName == 'Trouble':
               self.updateKeypad(1, u'LEDTrouble', ledState)
            elif ledName == 'Bypass':
               self.updateKeypad(1, u'LEDBypass', ledState)
            elif ledName == 'Memory':
               self.updateKeypad(1, u'LEDMemory', ledState)


      elif cmd == '904':
         self.mylogger.log(3, u"Beep Status")

      elif cmd == '905':
         self.mylogger.log(3, u"Tone Status")

      elif cmd == '906':
         self.mylogger.log(3, u"Buzzer Status")

      elif cmd == '907':
         self.mylogger.log(3, u"Door Chime Status")

      elif cmd == '908':
         m = re.search(r'^(..)(..)(..)$', dat)
         if m:
            self.mylogger.log(3, u"DSC Software Version %s.%s" % (m.group(1), m.group(2)))
      else:
         #self.mylogger.log(3, u"RX: %s" % data)
         self.mylogger.log(3, u"Unrecognized command received (Cmd:%s Dat:%s Sum:%d)" % (cmd, dat, sum))

      return (cmd, dat)



   ######################################################################################
   # Indigo Device State Updating
   ######################################################################################

   # Updates temperature of DSC temperature sensor
   #
   def updateSensorTemp(self, sensorNum, key, temp):
      if temp > 127:
         temp = 127 - temp
      self.mylogger.log(3, u"Temp sensor %d %s temp now %d degrees." % (sensorNum, key, temp))

      if sensorNum in self.tempList.keys():
         if key == 'inside':
            self.tempList[sensorNum].updateStateOnServer(key=u"temperatureInside", value=temp)
            indigo.variable.updateValue("DSC_Temp_Inside", value = "%d" % temp )
         elif key == 'outside':
            self.tempList[sensorNum].updateStateOnServer(key=u"temperatureOutside", value=temp)
            indigo.variable.updateValue("DSC_Temp_Outside", value = "%d" % temp)
         elif key == 'cool':
            self.tempList[sensorNum].updateStateOnServer(key=u"setPointCool", value=temp)
            indigo.variable.updateValue("DSC_Temp_Cool_Setpoint", value = "%d" % temp)
         elif key == 'heat':
            self.tempList[sensorNum].updateStateOnServer(key=u"setPointHeat", value=temp)
            indigo.variable.updateValue("DSC_Temp_Heat_Setpoint", value = "%d" % temp)
   
         if self.tempList[sensorNum].pluginProps['zoneLogChanges'] == 1:
            self.mylogger.log(1, u"Temp sensor %d %s temp now %d degrees." % (sensorNum, key, temp))


   # Updates zone group
   #
   def updateZoneGroup(self, zoneGroupDevId):

      zoneGrp = indigo.devices[zoneGroupDevId]

      zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=0)
      zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedShort", value="0m")

      newState = kZoneGroupStateClosed
      for zoneId in self.zoneGroupList[zoneGroupDevId]:
         zoneState = indigo.devices[int(zoneId)].states[u'state']
         if (zoneState != kZoneStateClosed) and (newState != kZoneGroupStateTripped):
            if zoneState == kZoneStateOpen:
               newState = kZoneGroupStateOpen
            elif zoneState == kZoneStateTripped:
               newState = kZoneGroupStateTripped

      if zoneGrp.states[u'state'] != newState:
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=0)
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedShort", value="0m")
         zoneGrp.updateStateOnServer(key=u"state", value=newState)


   # Updates indigo variable instance var with new value varValue
   #
   def updateZoneState(self, zoneKey, newState):

      if zoneKey in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneKey]]
         zoneType = zone.pluginProps['zoneType']
         #zonePartition = zone.pluginProps['zonePartition']

         # If the new state is different from the old state
         # then lets update timers and set the new state
         if zone.states[u'state'] != newState:
            # This is a new state, update all states and timers
            zone.updateStateOnServer(key=u"LastChangedShort", value="0m")
            zone.updateStateOnServer(key=u"LastChangedTimer", value=0)
            zone.updateStateOnServer(key=u"state", value=newState)
            time = datetime.now().strftime("%H:%M:%S")

            # Check if this zone is assigned to a zone group so we can update it
            for devId in self.zoneGroupList:
               #self.mylogger.log(3, u"Zone Group id: %s contains %s" % (zone.id,self.zoneGroupList[devId]))
               if str(zone.id) in self.zoneGroupList[devId]:
                  self.updateZoneGroup(devId)

            if 'var' in zone.pluginProps.keys():
               self.updateVariable(zone.pluginProps['var'], newState)

            if newState == kZoneStateTripped:
               self.mylogger.log(1, u"Alarm Zone '%s' TRIPPED!" % zone.name)

            if zone.pluginProps['zoneLogChanges'] == 1:
               if newState == kZoneStateOpen:
                  self.mylogger.log(1, u"Alarm Zone '%s' Opened." % zone.name)
#                  if zoneType != "zoneTypeMotion":
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Opened at %s" % (zone.name, time))
               elif newState == kZoneStateClosed:
                  self.mylogger.log(1, u"Alarm Zone '%s' Closed." % zone.name)
#                  if zoneType != "zoneTypeMotion":
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Closed at %s" % (zone.name, time))
                  
            if zoneType == "zoneTypeMotion":
               indigo.variable.updateValue("DSC_Last_Motion_Active", value = "%s at %s" % (zone.name, time))
               

   def updateZoneBypass(self, zoneKey, newState):

      if zoneKey in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneKey]]
         #zoneType = zone.pluginProps['zoneType']

         # If the new bypass state is different from the old state
         # then lets set the new state
         if zone.states[u'bypass'] != newState:
            # This is a new bypass state, update all states
            zone.updateStateOnServer(key=u"bypass", value=newState)

            if 'var' in zone.pluginProps.keys():
               self.updateVariable(zone.pluginProps['var'], newState)

            #if zone.pluginProps['zoneLogChanges'] == 1:
            if newState == kZoneBypassYes:
               self.mylogger.log(1, u"Alarm Zone '%s' Bypassed." % zone.name)
            elif newState == kZoneBypassNo:
               self.mylogger.log(1, u"Alarm Zone '%s' Bypass Cancelled." % zone.name)


   def updateKeypad(self, partition, stateName, newState):

      self.mylogger.log(4, u"Updating state %s for keypad on partition %u to %s." % (stateName, partition, newState))

      # If we're updating the main keypad state, update the variable too
      if stateName == u'state':
         self.updateVariable(self.pluginPrefs[u'variableState'], newState)

      if partition == 0:
         for keyk in self.keypadList.keys():
            keyp = indigo.devices[self.keypadList[keyk]]
            keyp.updateStateOnServer(key=stateName, value=newState)
         return

      if partition in self.keypadList.keys():
         keyp = indigo.devices[self.keypadList[partition]]
         keyp.updateStateOnServer(key=stateName, value=newState)


   ######################################################################################
   # Misc
   ######################################################################################

   def sendZoneTrippedEmail(self):

      if (len(self.configEmailUrgent) == 0) or (len(self.trippedZoneList) == 0):
         return

      theBody = "The following zone(s) have been tripped:\n\n"

      for zoneNum in self.trippedZoneList:

         if zoneNum in self.closeTheseZonesList:
            stateNow = "closed"
         else:
            stateNow = "open"

         zone = indigo.devices[self.zoneList[zoneNum]]

         theBody += "%s (currently %s)\n" % (zone.name, stateNow)

      theBody += "\n--\nDSC Alarm Plugin\n\n"

      self.mylogger.log(1, u"Sending zone tripped email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         theBody = contentPrefix + "\n\n" + theBody

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=theBody)

########################################################################################
############# Kidney514 ##########  Sending email of who is disarming
########################################################################################

   def sendEmailDisarm(self, bodyText):

      if len(self.configEmailDisarm) == 0:
         return

      self.mylogger.log(1, u"Sending Disarmed by: email to %s." % self.configEmailDisarm)

      contentPrefix = self.pluginPrefs.get(u'EmailDisarmContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailDisarm, subject=self.configEmailDisarmSubject, body=bodyText)

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


   def sendTroubleEmail(self, bodyText):

      if len(self.configEmailNotice) == 0:
         return

      self.mylogger.log(1, u"Sending trouble email to %s." % self.configEmailNotice)

      contentPrefix = self.pluginPrefs.get(u'updaterEmailContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailNotice, subject=self.configEmailNoticeSubject, body=bodyText)


   def sendPanicEmail(self, bodyText):

      if len(self.configEmailUrgent) == 0:
         return

      self.mylogger.log(1, u"Sending panic alert email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=bodyText)
      #if no email address is specified the speak part does not trigger
      say = self.pluginPrefs['speakTextPanic'] + ' Keypad: ' + bodyText + '.'
      self.sayThis(say)



   def sendDuressEmail(self, bodyText):

      if len(self.configEmailUrgent) == 0:
         return

      self.mylogger.log(1, u"Sending duress alarm email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=bodyText)
      #if no email address specified the speak part does not trigger
      say = self.pluginPrefs['speakTextPanic'] + ' Keypad: ' + bodyText + '.'
      self.sayThis(say)



   def sayThis(self, text):
      self.mylogger.log(3, u"SAY: %s" % text)
      #The default variable is DSC_Alarm_Text
      if self.configSpeakVariable is not None:
         if self.configSpeakVariable in indigo.variables:
            indigo.variable.updateValue(self.configSpeakVariable, value=text)
      else:
         indigo.server.speak(text)


   def speak(self, textId):
      self.mylogger.log(3, u"ID: %s" % textId)
      if self.pluginPrefs['speakingEnabled'] is False:
         return

      if len(self.pluginPrefs[textId]) == 0:
         return

      if textId == 'speakTextFailedToArm':
         zones = 0
         zoneText = ''
         for zoneNum in self.zoneList.keys():
            zone = indigo.devices[self.zoneList[zoneNum]]
            if zone.states[u'state.open'] is True:
               if zones > 0:
                  zoneText += ', '
               zoneText += zone.name.replace("Alarm_", "")
               zones += 1

         if zones == 0:
            say = self.pluginPrefs[textId]
         if zones == 1:
            say = self.pluginPrefs[textId] + '  The ' + zoneText + ' is open.'
         else:
            say = self.pluginPrefs[textId] + '  The following zones are open: ' + zoneText + '.'

         self.sayThis(say)

      elif textId == 'speakTextTripped':
         zones = 0
         zoneText = ''
         for zoneNum in self.trippedZoneList:
            zone = indigo.devices[self.zoneList[zoneNum]]
            if zones > 0:
               zoneText += ', '
            zoneText += zone.name.replace("Alarm_", "")
            zones += 1

         if zones == 0:
            return
            #if alarm is tripped due to panic, we want the panic alert to handle the talking
         if zones == 1:
            say = self.pluginPrefs[textId] + '  The ' + zoneText + ' has been tripped.'
         else:
            say = self.pluginPrefs[textId] + '  The following zones have been tripped: ' + zoneText + '.'

         self.sayThis(say)


      else:
         self.sayThis(self.pluginPrefs[textId])


   # Updates indigo variable instance var with new value varValue
   #
   def updateVariable(self, varID, varValue):

      if self.createVariables is False:
         return

      #self.mylogger.log(3, u"Variable: %s" % varID)

      if varID is None:
         return

      if varID in indigo.variables:
         indigo.variable.updateValue(varID, value=varValue)


   # Converts given time in minutes to a human format
   # 3m, 5h, 2d, etc.
   #
   def getShortTime(self, minutes):

      # If time is less than 100 min then show XXm
      if minutes < 100:
         return str(minutes) + 'm'
      # If it's less than 49 hours then show XXh
      elif minutes < 2881:
         return str(int(minutes / 60)) + 'h'
      # If it's less than 100 days then show XXd
      elif minutes < 144000:
         return str(int(minutes / 1440)) + 'd'
      # If it's anything more than one hundred days then show nothing
      else:
         return ''



   ######################################################################################
   # Concurrent Thread
   ######################################################################################

   def runConcurrentThread(self):
      self.mylogger.log(3, u"runConcurrentThread called")
      self.minuteTracker = time.time() + 60
      self.nextUpdateCheckTime = 0

      # While Indigo hasn't told us to shutdown
      while self.shutdown is False:

         self.timeNow = time.time()

         if self.state == self.States.STARTUP:
            self.mylogger.log(3, u"STATE: Startup")

            if self.configRead is False:
               if self.getConfiguration(self.pluginPrefs) is True:
                  self.configRead = True

            if self.configRead is True:
               self.state = self.States.BOTH_INIT

            self.sleep(1)

         elif self.state == self.States.HOLD:
            if self.configRead is False:
               self.state = self.States.STARTUP
            self.sleep(1)

         elif self.state == self.States.HOLD_RETRY:
            self.mylogger.log(1, "Plugin will attempt to re-initialize again in %u minutes." % self.currentHoldRetryTime)
            self.nextRetryTime = self.timeNow + (kHoldRetryTimeMinutes*60)
            self.state = self.States.HOLD_RETRY_LOOP

         elif self.state == self.States.HOLD_RETRY_LOOP:
            if self.configRead is False:
               self.state = self.States.STARTUP
            if self.timeNow >= self.nextRetryTime:
               self.state = self.States.BOTH_INIT
            self.sleep(1)

         elif self.state == self.States.BOTH_INIT:
            if self.openPort() is True:
               if self.useSerial is False:
                  self.state = self.States.SO_CONNECT
                  self.sleep(1)

                  # Enable pinging every 5 minutes
                  self.nextPingTime = self.timeNow + kPingInterval
               else:
                  self.state = self.States.ENABLE_TIME_BROADCAST

            else:
               #self.mylogger.logError('Error opening port, will retry in %u minutes.' % self.currentHoldRetryTime)
               self.state = self.States.HOLD_RETRY

         elif self.state == self.States.SO_CONNECT:
            err = True

            # Read packet to clear the port of the 5053 login request
            self.readPacket()

            attemptLogin = True
            while attemptLogin is True:
               attemptLogin = False
               rx = self.sendPacket('005' + self.pluginPrefs[u'TwoDS_Password'], waitFor='505')
               if len(rx) == 0:
                  self.mylogger.logError('Timeout waiting for Envisalink to respond to login request.')
               else:
                  rx = int(rx)
                  if rx == 0:
                     self.mylogger.logError('Envisalink refused login request.')
                  elif rx == 1:
                     err = False
                     self.mylogger.log(1, "Connected to Envisalink.")
                  elif rx == 3:
                     # 2DS sent login request, retry (Happens when socket is first opened)
                     self.mylogger.log(3, u"Received login request, retrying login...")
                     attemptLogin = True
                  else:
                     self.mylogger.logError('Unknown response from Envisalink login request.')

            # This delay is required otherwise 2DS locks up
            self.sleep(1)

            if err is True:
               self.state = self.States.HOLD_RETRY
            else:
               self.state = self.States.ENABLE_TIME_BROADCAST


         elif self.state == self.States.ENABLE_TIME_BROADCAST:

            # Enable time broadcast
            self.mylogger.log(2, "Enabling Time Broadcast")
            rx = self.sendPacket('0561')
            if len(rx) > 0:
               self.mylogger.log(2, u"Time Broadcast enabled.")
               self.state = self.States.BOTH_PING
            else:
               self.mylogger.logError(u'Error enabling Time Broadcast.')
               self.state = self.States.HOLD_RETRY

         elif self.state == self.States.BOTH_PING:

            #Ping the panel to confirm we are in communication
            err = True
            self.mylogger.log(2, u"Pinging the panel to test communication...")
            rx = self.sendPacket('000')
            if len(rx) > 0:
               self.mylogger.log(2, u"Ping was successful.")
               err = False
            else:
               self.mylogger.logError('Error pinging panel, aborting.')

            if err is True:
               self.state = self.States.HOLD_RETRY
            else:
               #Request a full state update
               self.mylogger.log(2, "Requesting a full state update.")
               rx = self.sendPacket('001')
               if len(rx) == 0:
                  self.mylogger.logError('Error getting state update.')
                  self.state = self.States.HOLD_RETRY
               else:
                  self.mylogger.log(2, "State update request successful, initialization complete, starting normal operation.")
                  self.state = self.States.BOTH_POLL

         elif self.state == self.States.BOTH_POLL:
            if self.configRead is False:
               self.state = self.States.STARTUP
            else:

               if (self.useSerial is False) and (self.timeNow > self.nextPingTime):
                  #self.mylogger.log(1,"Pinging 2DS")
                  self.txCmdList.append((kCmdNormal, '000'))
                  self.nextPingTime = self.timeNow + kPingInterval

               if len(self.txCmdList) > 0:
                  (cmdType, data) = self.txCmdList[0]
                  if cmdType == kCmdNormal:
                     txRsp = self.sendPacket(data)
                     if txRsp == '-':
                        # If we receive - socket has closed, lets re-init
                        self.mylogger.logError('Tried to send data but socket seems to have closed.  Trying to re-initialize.')
                        self.state = self.States.BOTH_INIT
                     else:
                        # send was a success, remove command from queue
                        del self.txCmdList[0]

                  elif cmdType == kCmdThermoSet:
                     self.setThermostat(data)
                     del self.txCmdList[0]
               else:
                  (rxRsp, rxData) = self.readPacket()
                  if rxRsp == '-':
                     # If we receive - socket has closed, lets re-init
                     self.mylogger.logError('Tried to read data but socket seems to have closed.  Trying to re-initialize.')
                     self.state = self.States.BOTH_INIT


         # Check if the trouble timer counter is timing
         # We need to know if the trouble light has remained off
         # for a few seconds before we assume the trouble is cleared
         if self.troubleClearedTimer > 0:
            self.troubleClearedTimer -= 1
            if self.troubleClearedTimer == 0:
               self.troubleCode = 0
               self.sendTroubleEmail(u"Trouble Code Cleared.")

         if self.repeatAlarmTripped is True:
            #timeNow = time.time()
            if self.timeNow >= self.repeatAlarmTrippedNext:
               self.repeatAlarmTrippedNext = self.timeNow + 12
               self.speak('speakTextTripped')


         # If a minute has elapsed
         if self.timeNow >= self.minuteTracker:

            # Do we need to check for a new version?
            self.updater.checkVersionPoll()

            # Increment all zone changed timers
            self.minuteTracker += 60
            for zoneKey in self.zoneList.keys():
               zone = indigo.devices[self.zoneList[zoneKey]]
               tmr = zone.states[u"LastChangedTimer"] + 1
               zone.updateStateOnServer(key=u"LastChangedTimer", value=tmr)
               zone.updateStateOnServer(key=u"LastChangedShort", value=self.getShortTime(tmr))

            for zoneGroupDeviceId in self.zoneGroupList:
               zoneGroupDevice = indigo.devices[zoneGroupDeviceId]
               tmr = zoneGroupDevice.states[u"AnyMemberLastChangedTimer"] + 1
               zoneGroupDevice.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=tmr)
               zoneGroupDevice.updateStateOnServer(key=u"AnyMemberLastChangedShort", value=self.getShortTime(tmr))
               tmr = zoneGroupDevice.states[u"EntireGroupLastChangedTimer"] + 1
               zoneGroupDevice.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=tmr)
               zoneGroupDevice.updateStateOnServer(key=u"EntireGroupLastChangedShort", value=self.getShortTime(tmr))


      self.closePort()
      self.mylogger.log(3, u"Exiting Concurrent Thread")


   def stopConcurrentThread(self):
      self.mylogger.log(3, u"stopConcurrentThread called")
      self.shutdown = True
      self.mylogger.log(3, u"Exiting stopConcurrentThread")

Posted on
Wed Nov 22, 2017 4:10 pm
FlyingDiver offline
User avatar
Posts: 7213
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: python time module

Find this line:

Code: Select all
            time = datetime.now().strftime("%H:%M:%S")

Change it to:
Code: Select all
            timeNow = datetime.now().strftime("%H:%M:%S")


Then change "time" to "timeNow" in these lines:
Code: Select all
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Opened at %s" % (zone.name, time))
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Closed at %s" % (zone.name, time))
               indigo.variable.updateValue("DSC_Last_Motion_Active", value = "%s at %s" % (zone.name, time))


joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Wed Nov 22, 2017 4:26 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

Where my mod all over the place?

Renaming the variable didn't solve the problem
Attachments
Screen Shot.png
Screen Shot.png (66.63 KiB) Viewed 2620 times

Posted on
Wed Nov 22, 2017 4:29 pm
FlyingDiver offline
User avatar
Posts: 7213
Joined: Jun 07, 2014
Location: Southwest Florida, USA

Re: python time module

You did disable and enable the plugin after making the changes?

joe (aka FlyingDiver)
my plugins: http://forums.indigodomo.com/viewforum.php?f=177

Posted on
Wed Nov 22, 2017 4:48 pm
kidney offline
Posts: 240
Joined: Jul 15, 2007

Re: python time module

My bad had given you wrong code..... But your help allowed me to fix it!

Thanks.

Here is the code that is actually running now and fixed with your help!

Code: Select all
#! /usr/bin/env python
# -*- coding: utf-8 -*-
####################
# DSC Alarm Plugin
# Developed by Travis Cook and modified by Monstergerm
# www.frightideas.com

import re
import time
import indigo
from datetime import datetime
import serial
import indigoPluginUtils
import indigoPluginUpdateChecker

kSocketPort = 1514
kSocketBufferSize = 1024
kSocketTimeout = 1

kZoneStateOpen = 'open'
kZoneStateClosed = 'closed'
kZoneStateTripped = 'tripped'
kZoneBypassNo = 'nobypass'
kZoneBypassYes = 'bypassed'
kZoneGroupStateOpen = 'zoneOpen'
kZoneGroupStateClosed = 'allZonesClosed'
kZoneGroupStateTripped = 'zoneTripped'
kAlarmStateDisarmed = u'disarmed'
kAlarmStateExitDelay = u'exitDelay'
kAlarmStateFailedToArm = u'FailedToArm'
kAlarmStateArmed = u'armed'
kAlarmStateEntryDelay = u'entryDelay'
kAlarmStateTripped = u'tripped'
kKeypadStateChimeEnabled = u'enabled'
kKeypadStateChimeDisabled = u'disabled'
kAlarmArmedStateDisarmed = u'disarmed'
kAlarmArmedStateStay = u'stay'
kAlarmArmedStateAway = u'away'
kReadyStateTrue = u'ready'
kReadyStateFalse = u'notready'
kPanicStateNone = u'none'
kPanicStateFire = u'fire'
kPanicStateAmbulance = u'ambulance'
kPanicStatePanic = u'panic'
kPanicStateDuress = u'duress'

kPluginUpdateUrl = u'http://www.frightideas.com/hobbies/dscAlarm/dscAlarmVersionInfo.html'

kLedIndexList = ['None', 'Ready', 'Armed', 'Memory', 'Bypass', 'Trouble', 'Program', 'Fire', 'Backlight', 'AC']
kLedStateList = ['off', 'on', 'flashing']
kArmedModeList = ['Away', 'Stay', 'Away, No Delay', 'Stay, No Delay']
kPanicTypeList = ['None', 'Fire', 'Ambulance', 'Panic', 'Duress']
kMonthList = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']


kCmdNormal = 0
kCmdThermoSet = 1
kPingInterval = 301
kHoldRetryTimeMinutes = 3





# Note the "indigo" module is automatically imported and made available inside
# our global name space by the host process.

################################################################################
class Plugin(indigo.PluginBase):

   ########################################
   def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs):
      indigo.PluginBase.__init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs)

      self.mylogger = indigoPluginUtils.logger(self)
      self.updater = indigoPluginUpdateChecker.updateChecker(self, kPluginUpdateUrl)

      self.States = self.enum(STARTUP=1, HOLD=2, HOLD_RETRY=3, HOLD_RETRY_LOOP=4, BOTH_INIT=5, SO_CONNECT=6, ENABLE_TIME_BROADCAST=7, BOTH_PING=8, BOTH_POLL=9)
      self.state = self.States.STARTUP
      self.logLevel = 1
      self.shutdown = False
      self.configRead = False
      self.interfaceState = 0
      self.zoneList = {}
      self.tempList = {}
      self.zoneGroupList = {}
      self.trippedZoneList = []
      self.triggerList = []
      self.keypadList = {}
      self.createVariables = False
      self.port = None
      self.repeatAlarmTripped = False
      self.isPortOpen = False
      self.useSerial = False
      self.txCmdList = []
      self.closeTheseZonesList = []
      self.currentHoldRetryTime = kHoldRetryTimeMinutes
      self.ourVariableFolder = None
      self.configEmailUrgent = ""
      self.configEmailNotice = ""
      self.configEmailDisarm = ""
      self.configEmailUrgentSubject = ""
      self.configEmailNoticeSubject = ""
      self.configEmailDisarmSubject = ""
      self.userCode = ""
      self.userLabel = ""
      self.userLabelDict = {}
      self.configSpeakVariable = None
      self.configKeepTimeSynced = True
      self.troubleCode = 0
      self.troubleClearedTimer = 0
      if "DSC" not in indigo.variables.folders:
         indigo.variables.folder.create("DSC")
      if "DSC_RAW_Data" not in indigo.variables:
         indigo.variable.create("DSC_RAW_Data", value="", folder="DSC")
      if "DSC_Alarm_Text" not in indigo.variables:
         indigo.variable.create("DSC_Alarm_Text", value="", folder="DSC")
      if "DSC_Alarm_Memory" not in indigo.variables:
         indigo.variable.create("DSC_Alarm_Memory", value="no tripped zones", folder="DSC")
      if "DSC_Command" not in indigo.variables:
         indigo.variable.create("DSC_Command", value="#", folder="DSC")
      if "DSC_Last_User_Disarm" not in indigo.variables:
         indigo.variable.create("DSC_Last_User_Disarm", value="", folder="DSC")
      if "DSC_Last_User" not in indigo.variables:
         indigo.variable.create("DSC_Last_User", value="", folder="DSC")
      if "DSC_Last_Zone_Active" not in indigo.variables:
         indigo.variable.create("DSC_Last_Zone_Active", value="", folder="DSC")
      if "DSC_Last_Motion_Active" not in indigo.variables:
         indigo.variable.create("DSC_Last_Motion_Active", value="", folder="DSC")
      if "DSC_Temp_Outside" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Outside", value="", folder="DSC")
      if "DSC_Temp_Inside" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Inside", value="", folder="DSC")
      if "DSC_Temp_Cool_Setpoint" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Cool_Setpoint", value="", folder="DSC")
      if "DSC_Temp_Heat_Setpoint" not in indigo.variables:
         indigo.variable.create("DSC_Temp_Heat_Setpoint", value="", folder="DSC")




   def enum(self, **enums):
      return type('Enum', (), enums)

   def __del__(self):
      indigo.PluginBase.__del__(self)

   ########################################
   def startup(self):
      self.mylogger.log(4, u"startup called")
      self.configRead = self.getConfiguration(self.pluginPrefs)
      self.updater.checkVersionPoll()

   def shutdown(self):
      self.mylogger.log(4, u"shutdown called")


   ######################################################################################
   # Indigo Device Start/Stop
   ######################################################################################

   def deviceStartComm(self, dev):
      self.mylogger.log(4, u"<<-- entering deviceStartComm: %s (%d - %s)" % (dev.name, dev.id, dev.deviceTypeId))

      props = dev.pluginProps

      if dev.deviceTypeId == u'alarmZoneGroup':

         if dev.id not in self.zoneGroupList:
            self.zoneGroupList[dev.id] = props[u'devList']

         if dev.states[u'state'] == 0:
            dev.updateStateOnServer(key=u"state", value=kZoneGroupStateClosed)

      elif dev.deviceTypeId == u'alarmZone':
         if 'zoneNumber' not in props:
            return

         zone = int(props['zoneNumber'])
         if zone not in self.zoneList.keys():
            self.zoneList[zone] = dev.id
         else:
            self.mylogger.logError("Zone %s is already assigned to another device." % zone)

         # Check for new version zone states.
         # If they're not present tell Indigo to reread the Devices.xml file
         if 'LastChangedShort' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()

         # If state is invalid or not there, set to closed
         if dev.states['state'] == 0:
            dev.updateStateOnServer(key=u'state', value=kZoneStateClosed)

         # If plugin is v2 we won't have a bypass key so set it to default
         if 'bypass' not in dev.states or dev.states['bypass'] == 0:
            dev.stateListOrDisplayStateIdChanged()
            dev.updateStateOnServer(key=u'bypass', value=kZoneBypassNo)

         # If plugin is v2 we won't have a zonePartition key so set it to 1 by default
         if 'zonePartition' not in props:
            props.update({'zonePartition': '1'})
            dev.replacePluginPropsOnServer(props)

         dev.updateStateOnServer(key=u"LastChangedShort", value=self.getShortTime(dev.states[u"LastChangedTimer"]))


         # Check for new version properties to see if we need to refresh the device
         if 'occupancyGroup' not in props:
            self.mylogger.log(3, u"Adding occupancyGroup to device %s properties." % dev.name)
            props.update({"occupancyGroup": 0})
            dev.replacePluginPropsOnServer(props)

         # If the variable we used no longer exists then remove the varID
         if "var" in props:
            if props["var"] not in indigo.variables:
               props["var"] = None
               dev.replacePluginPropsOnServer(props)
         else:
            props["var"] = None
            dev.replacePluginPropsOnServer(props)


      elif dev.deviceTypeId == u'alarmKeypad':
         partition = int(dev.pluginProps['partitionNumber'])
         if partition not in self.keypadList.keys():
            self.keypadList[partition] = dev.id
            self.mylogger.log(3, u"Adding keypad: %s" % self.keypadList)
         else:
            self.mylogger.logError("Partition is already assigned to another device.")

         # If plugin is v2 we won't have a partitionName key so set it to default
         if 'partitionName' not in dev.pluginProps:
            props['partitionName'] = 'Default'
            dev.replacePluginPropsOnServer(props)

         dev.updateStateOnServer(key=u'state', value=kAlarmStateDisarmed)
         dev.updateStateOnServer(key=u'ReadyState', value=kReadyStateTrue)
         dev.updateStateOnServer(key=u'PanicState', value=kPanicStateNone)

         # Check for new keypad states.
         # If they're not present tell Indigo to reread the Devices.xml file
         if 'ArmedState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()
         elif 'ReadyState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()
         elif 'PanicState' not in dev.states:
            dev.stateListOrDisplayStateIdChanged()


      elif dev.deviceTypeId == u'alarmTemp':
         sensor = int(dev.pluginProps['sensorNumber'])
         if sensor not in self.tempList.keys():
            self.tempList[sensor] = dev

      self.mylogger.log(4, u"exiting deviceStartComm -->>")


   def deviceStopComm(self, dev):
      self.mylogger.log(4, u"<<-- entering deviceStopComm: %s (%d - %s)" % (dev.name, dev.id, dev.deviceTypeId))

      if dev.deviceTypeId == u'alarmZoneGroup':
         if dev.id in self.zoneGroupList:
            del self.zoneGroupList[dev.id]

      elif dev.deviceTypeId == u'alarmZone':
         if 'zoneNumber' in dev.pluginProps:
            zone = int(dev.pluginProps['zoneNumber'])
            if zone in self.zoneList.keys():
               del self.zoneList[zone]
            #self.mylogger.log(3, u"ZoneList is now: %s" % self.zoneList)

      elif dev.deviceTypeId == u'alarmKeypad':
         if 'partitionNumber' in dev.pluginProps:
            keyp = int(dev.pluginProps['partitionNumber'])
            if keyp in self.keypadList:
               del self.keypadList[keyp]

      elif dev.deviceTypeId == u'alarmTemp':
         if 'sensorNumber' in dev.pluginProps:
            tmp = int(dev.pluginProps['sensorNumber'])
            if tmp in self.tempList:
               del self.tempList[int(dev.pluginProps['sensorNumber'])]

      self.mylogger.log(4, u"exiting deviceStopComm -->>")


   #def deviceUpdated(self, origDev, newDev):
   #   self.mylogger.log(4, u"<<-- entering deviceUpdated: %s" % origDev.name)
   #   origDev.name = newDev.name
   #   self.mylogger.log(4, u"OrigDev now: %s" % origDev.name)
   #   self.DigiTemp.deviceStop(origDev)
   #   self.DigiTemp.deviceStart(newDev)

   ######################################################################################
   # Indigo Trigger Start/Stop
   ######################################################################################

   def triggerStartProcessing(self, trigger):
      self.mylogger.log(4, u"<<-- entering triggerStartProcessing: %s (%d)" % (trigger.name, trigger.id))
      self.triggerList.append(trigger.id)
      self.mylogger.log(4, u"exiting triggerStartProcessing -->>")

   def triggerStopProcessing(self, trigger):
      self.mylogger.log(4, u"<<-- entering triggerStopProcessing: %s (%d)" % (trigger.name, trigger.id))
      if trigger.id in self.triggerList:
         self.mylogger.log(4, u"TRIGGER FOUND")
         self.triggerList.remove(trigger.id)
      self.mylogger.log(4, u"exiting triggerStopProcessing -->>")

   #def triggerUpdated(self, origDev, newDev):
   #   self.mylogger.log(4, u"<<-- entering triggerUpdated: %s" % origDev.name)
   #   self.triggerStopProcessing(origDev)
   #   self.triggerStartProcessing(newDev)


   ######################################################################################
   # Indigo Trigger Firing
   ######################################################################################

   def triggerEvent(self, eventId):
      self.mylogger.log(4, u"<<-- entering triggerEvent: %s " % eventId)
      for trigId in self.triggerList:
         trigger = indigo.triggers[trigId]
         if trigger.pluginTypeId == eventId:
            indigo.trigger.execute(trigger)
      return


   ######################################################################################
   # Indigo Menu Action Methods
   ######################################################################################

   def checkForUpdates(self):
      self.mylogger.log(1, u"Manually checking for updates")
      self.updater.checkVersionNow()


   ######################################################################################
   # Indigo Action Methods
   ######################################################################################
   #These are partition specific commands, except global and panic alarms.

   def methodDisarmAlarm(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      #keyp = keyp[0][0] might return first character as string
      self.mylogger.log(1, u"Disarming Alarm. (Partition %s '%s')" % (keyp, keypname))
      tx = "".join(["040", keyp, self.pluginPrefs[u'code'], "0"*(6-len(self.pluginPrefs[u'code']))])
      self.txCmdList.append((kCmdNormal, tx))


   def methodArmStay(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      self.mylogger.log(1, u"Arming Alarm in Stay Mode. (Partition %s '%s')" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '031' + keyp))


   def methodArmAway(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keyp = dev.pluginProps["partitionNumber"]
      self.mylogger.log(1, u"Arming Alarm in Away Mode. (Partition %s '%s')" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '030' + keyp))


   def methodArmStayForce(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keyp = dev.pluginProps["partitionNumber"]
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition is Already Armed.")
         return
      if keyp !="1": #this sets all zones to nobypass if they are in partition 2-8, since those partitions do not report zone bypass status
         tx = "".join(["071", keyp, "*100#"])
         self.mylogger.log(3, u"We have partition 2-8 and will cancel all bypass")
         self.txCmdList.append((kCmdNormal, tx))
         self.sleep(3) #add delay if keybus buffer overruns
      for zoneNum in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneNum]]
         zonePartition = zone.pluginProps['zonePartition']
         if zone.states[u'state.open'] is True and zone.states[u'bypass'] == kZoneBypassNo and zonePartition == keyp:
            self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
            zoneNum = str(zoneNum).zfill(2)
            tx = "".join(["071", keyp, "*1", zoneNum, "#"])
            self.txCmdList.append((kCmdNormal, tx))
            self.sleep(2) #add delay if keybus buffer overruns
      self.mylogger.log(1, u"Arming Alarm in Forced Stay Mode. (Partition %s%s)" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '031' + keyp))


   def methodArmAwayForce(self, action, dev):
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keyp = dev.pluginProps["partitionNumber"]
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition is Already Armed.")
         return
      if keyp !="1": #this sets all zones to nobypass if they are in partition 2-8, since those partitions do not report zone bypass status
         tx = "".join(["071", keyp, "*100#"])
         self.mylogger.log(3, u"We have partition 2-8 and will cancel all bypass")
         self.txCmdList.append((kCmdNormal, tx))
         self.sleep(3) #add delay if keybus buffer overruns
      for zoneNum in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneNum]]
         zonePartition = zone.pluginProps['zonePartition']
         if zone.states[u'state.open'] is True and zone.states[u'bypass'] == kZoneBypassNo and zonePartition == keyp:
            self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
            zoneNum = str(zoneNum).zfill(2)
            tx = "".join(["071", keyp, "*1", zoneNum, "#"])
            self.txCmdList.append((kCmdNormal, tx))
            self.sleep(2) #add delay if keybus buffer overruns
      self.mylogger.log(1, u"Arming Alarm in Forced Away Mode. (Partition %s%s)" % (keyp, keypname))
      self.txCmdList.append((kCmdNormal, '030' + keyp))


   def methodArmGlobal(self, action):
      #this action arms all defined partitions in away mode.
      self.mylogger.log(1, u"Arming Alarm in Global Mode (All Partitions).")
      for i in range(1, 9):
         if i in self.keypadList.keys():
            key = str(i)
            self.txCmdList.append((kCmdNormal, '030' + key))
            time.sleep(1)


   def methodPanicAlarm(self, action):
      panicType = action.props[u'panicAlarmType']
      self.mylogger.log(1, u"Activating Panic Alarm! (%s)" % kPanicTypeList[int(panicType)])
      self.txCmdList.append((kCmdNormal, '060' + panicType))


   def methodSendKeypress070(self, action):
      self.mylogger.log(3, u"Received Send Keypress 070 Action")
      keys = action.props[u'keys']
      firstChar = True
      sendBreak = False
      for char in keys:
         if char == 'L':
            time.sleep(2)
            sendBreak = False

         if (firstChar is False):
            self.txCmdList.append((kCmdNormal, '070^'))

         if char != 'L':
            self.txCmdList.append((kCmdNormal, '070' + char))
            sendBreak = True

         firstChar = False
      if (sendBreak is True):
         self.txCmdList.append((kCmdNormal, '070^'))


   def methodSendKeypress071(self, action, dev):
       keypname = str(dev.pluginProps['partitionName'])
       keypname = " '" + keypname + "'"
       keyp = dev.pluginProps["partitionNumber"]
       keys = action.props[u'keys']
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
       self.mylogger.log(1, u"Received Send Keypress Action [%s]. (Partition %s%s)" % (keys, keyp, keypname))
       cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
      if len(keys) != len(cleanKeys) or "*8" in keys:
         self.mylogger.log(1, u"There are Invalid Keys in your Command.")
         return
      if len(keys) > 6:
         self.mylogger.log(1, u"The Key Command is too long.")
         return
       tx = "".join(["071", keyp, keys])
       self.txCmdList.append((kCmdNormal, tx))


   def methodSendKeypressVariable(self, action):
      keys = indigo.variables ["DSC_Command"]
      keys = keys.value
      self.mylogger.log(1, u"Received Send Keypress Action [%s]." % keys)
      cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if len(keys) != len(cleanKeys) or "*8" in keys:
         self.mylogger.log(1, u"There are Invalid Keys in your DSCcommand Variable.")
         return
      if len(keys) > 7:
         self.mylogger.log(1, u"The Key Command is too long.")
         return
      if keys[0] < "0" or keys[0] > "8":
         self.mylogger.log(1, u"The First Character in your DSCcommand Needs to be a Valid Partition (1-8).")
      else:
         self.txCmdList.append((kCmdNormal, '071' + keys))


   def methodBypassZone(self, action, dev):
      key = dev.pluginProps["zoneNumber"]
      keyp = dev.pluginProps["zonePartition"]
      zone = indigo.devices[self.zoneList[int(key)]]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Zone '%s' is Armed and Cannot be Bypassed." % zone.name)
         return
      self.mylogger.log(1, u"Received Zone Bypass Action for Zone '%s' in Partition %s%s." % (zone.name, keyp, keypname))
      key = str(key).zfill(2)
      tx = "".join(["071", keyp, "*1", key, "#"])
      self.txCmdList.append((kCmdNormal, tx))
      #This is a toggle action, i.e. already bypassed zones will turn to non-bypassed state.
      #Zones in partition 2-8 do not report bypass status, therefore there will be no zone state update.


   def methodBypassZoneCancel(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition %s%s is Armed and Cannot Accept Bypass Cancel." % (keyp, keypname))
         return
      self.mylogger.log(1, u"Received All Zones Bypass Cancel for Partition %s%s." % (keyp, keypname))
      tx = "".join(["071", keyp, "*100#"])
      self.txCmdList.append((kCmdNormal, tx))


   def methodBypassZoneRecall(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['state'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      if keypstate == kAlarmStateArmed:
         self.mylogger.log(1, u"The Selected Partition %s%s is Armed and Cannot Accept Bypass Recall." % (keyp, keypname))
         return
      self.mylogger.log(1, u"Received Zone(s) Bypass Recall for Partition %s%s." % (keyp, keypname))
      tx = "".join(["071", keyp, "*199#"])
      self.txCmdList.append((kCmdNormal, tx))


   def methodDoorChimeEnable(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['KeypadChime'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      self.mylogger.log(1, u"Received Keypad Chime Enable for Partition %s%s." % (keyp, keypname))
      if keypstate != kKeypadStateChimeEnabled:
         tx = "".join(["071", keyp, "*4"])
         self.txCmdList.append((kCmdNormal, tx))
         return


   def methodDoorChimeDisable(self, action, dev):
      keyp = dev.pluginProps["partitionNumber"]
      dev = indigo.devices[self.keypadList[int(keyp)]]
      keypname = str(dev.pluginProps['partitionName'])
      keypname = " '" + keypname + "'"
      keypstate = str(dev.states['KeypadChime'])
      if self.useSerial is True:
         self.mylogger.logError('This Action does not work with IT-100.')
         return
      self.mylogger.log(1, u"Received Keypad Chime Disable for Partition %s%s." % (keyp, keypname))
      if keypstate == kKeypadStateChimeEnabled:
         tx = "".join(["071", keyp, "*4"])
         self.txCmdList.append((kCmdNormal, tx))
         return


    # Queue a command to set DSC Thermostat Setpoints
   #
   def methodAdjustThermostat(self, action):
      self.mylogger.log(3, u"Device %s:" % action)
      self.txCmdList.append((kCmdThermoSet, action))


   # The command queued above calls this routine to create the packet
   #
   def setThermostat(self, action):
      #find this thermostat in our list to get the number
      for sensorNum in self.tempList.keys():
         if self.tempList[sensorNum].id == action.deviceId:
            break

      self.mylogger.log(3, u"SensorNum = %s" % sensorNum)

      #send 095 for thermostat in question, wait for 563 response
      #self.mylogger.log(3, u'095' + str(sensorNum))
      rx = self.sendPacket(u'095' + str(sensorNum), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error getting current thermostat setpoints, aborting adjustment.')
         return

      if (action.props[u'thermoAdjustmentType'] == u'+') or (action.props[u'thermoAdjustmentType'] == u'-'):
         sp = 0
      else:
         sp = int(action.props[u'thermoSetPoint'])

      # then 096TC+000 to inc cool,
      #      096Th-000 to dec heat
      #      096Th=### to set setpoint
      # wait for 563 response
      #self.mylogger.log(3, u'096%u%c%c%03u' % (sensorNum, action.props[u'thermoAdjustWhich'], action.props[u'thermoAdjustmentType'],sp) )
      rx = self.sendPacket(u'096%u%c%c%03u' % (sensorNum, action.props[u'thermoAdjustWhich'], action.props[u'thermoAdjustmentType'], sp), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error changing thermostat setpoints, aborting adjustment.')
         return

      # send 097T
      #send 097 for thermostat in question to save setting, wait for 563 response
      rx = self.sendPacket(u'097' + str(sensorNum), waitFor='563')
      if len(rx) == 0:
         self.mylogger.logError('Error saving thermostat setpoints, aborting adjustment.')
         return


   # Reset an Alarm Zone Group's timers to 0
   #
   def methodResetZoneGroupTimer(self, action):
      if action.deviceId in indigo.devices:
         zoneGrp = indigo.devices[action.deviceId]
         self.mylogger.log(3, u"Manual timer reset for alarm zone group \"%s\"" % zoneGrp.name)
         zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=0)
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=0)


   ######################################################################################
   # Indigo Pref UI Methods
   ######################################################################################

   # Validate the pluginConfig window after user hits OK
   # Returns False on failure, True on success
   #
   def validatePrefsConfigUi(self, valuesDict):
      self.mylogger.log(3, u"validating Prefs called")
      errorMsgDict = indigo.Dict()
      wasError = False

      if valuesDict[u'configInterface'] == u'serial':
         if len(valuesDict[u'serialPort']) == 0:
            errorMsgDict[u'serialPort'] = u"Select a valid serial port."
            wasError = True
      else:
         if len(valuesDict[u'TwoDS_Address']) == 0:
            errorMsgDict[u'TwoDS_Address'] = u"Enter a valid IP address or host name."
            wasError = True
         if len(valuesDict[u'TwoDS_Password']) == 0:
            errorMsgDict[u'TwoDS_Password'] = u"Enter the password for the 2DS."
            wasError = True

      if len(valuesDict[u'code']) > 6:
         errorMsgDict[u'code'] = u"The code must be 6 digits or less."
         wasError = True

      if len(valuesDict[u'code']) == 0:
         errorMsgDict[u'code'] = u"You must enter the alarm's arm/disarm code."
         wasError = True

      if len(valuesDict[u'emailUrgent']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'emailUrgent']):
            errorMsgDict[u'emailUrgent'] = u"Please enter a valid email address."
            wasError = True

      if len(valuesDict[u'updaterEmail']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'updaterEmail']):
            errorMsgDict[u'updaterEmail'] = u"Please enter a valid email address."
            wasError = True

      if len(valuesDict[u'EmailDisarm']) > 0:
         if not re.match(r"[^@]+@[^@]+\.[^@]+", valuesDict[u'EmailDisarm']):
            errorMsgDict[u'EmailDisarm'] = u"Please enter a valid email address."
            wasError = True

      userCodeList = valuesDict['userCode'].split(",")
      userLabelList = valuesDict['userLabel'].split(",")
      newuserCodeList = [s for s in userCodeList if s.isdigit()]   #transfers only numbers to newUserCodeList
      length = len(userCodeList)

      if newuserCodeList and (any(len(lst) != length for lst in [newuserCodeList])):
         errorMsgDict[u'userCode'] = u"Config Error: The user code contains invalid characters (2-digit numbers only)."
         wasError = True

      if any(len(lst) != length for lst in [userLabelList]):
         errorMsgDict[u'userCode'] = u"Config Error: The user code and label lists are not of the same length."
         errorMsgDict[u'userLabel'] = u"Config Error: The user code and label lists are not of the same length."
         wasError = True

      if len(userCodeList) != len(set(userCodeList)):
         errorMsgDict[u'userCode'] = u"Config Error: The user code list contains duplicate codes."
         wasError = True

      for key in newuserCodeList:
         if int(key) < 1 or int(key) > 40:
            errorMsgDict[u'userCode'] = u"Config Error: The user code is out of bound."
            wasError = True


      if wasError is True:
         return (False, valuesDict, errorMsgDict)

      # Tell DSC module to reread it's config
      self.configRead = False

      # User choices look good, so return True (client will then close the dialog window).
      return (True, valuesDict)

   def validateActionConfigUi(self, valuesDict, typeId, actionId):
      self.mylogger.log(3, u"validating Action Config called")
      if typeId == u'actionSendKeypress':
         keys = valuesDict[u'keys']
         cleanKeys = re.sub(r'[^a-e0-9LFAP<>=*#]+', '', keys)
         if len(keys) != len(cleanKeys) or "*8" in keys:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'keys'] = u"There are invalid keys in your keystring."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)


   def validateEventConfigUi(self, valuesDict, typeId, eventId):
      self.mylogger.log(3, u"validating Event Config called")
      #self.mylogger.log(3, u"Type: %s, Id: %s, Dict: %s" % (typeId, eventId, valuesDict))
      if typeId == u'userArmed' or typeId == u'userDisarmed':
         code = valuesDict[u'userCode']
         if len(code) != 2:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'userCode'] = u"The user code must be 2 digits in length."
            return (False, valuesDict, errorMsgDict)

         cleanCode = re.sub(r'[^0-9]+', '', code)
         if len(code) != len(cleanCode):
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'userCode'] = u"The code can only contain digits 0-9."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)

   def validateDeviceConfigUi(self, valuesDict, typeId, devId):
      self.mylogger.log(3, u"validating Device Config called")
      #self.mylogger.log(3, u"Type: %s, Id: %s, Dict: %s" % (typeId, devId, valuesDict))
      if typeId == u'alarmZone':
         zoneNum = int(valuesDict[u'zoneNumber'])
         if zoneNum in self.zoneList.keys() and devId != indigo.devices[self.zoneList[zoneNum]].id:
            #self.mylogger.log(3, u"ZONEID: %s" % self.DSC.zoneList[zone].id)
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'zoneNumber'] = u"This zone has already been assigned to a different device."
            return (False, valuesDict, errorMsgDict)
      if typeId == u'alarmKeypad':
         partitionNum = int(valuesDict[u'partitionNumber'])
         if partitionNum in self.keypadList.keys() and devId != indigo.devices[self.keypadList[partitionNum]].id:
            errorMsgDict = indigo.Dict()
            errorMsgDict[u'partitionNumber'] = u"This partition has already been assigned to a different device."
            return (False, valuesDict, errorMsgDict)
      return (True, valuesDict)


   def getZoneList(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []
      for i in range(1, 65):
         zoneName = str(i)
         if i in self.zoneList.keys():
            zoneDev = indigo.devices[self.zoneList[i]]
            zoneName = ''.join([str(i), ' - ', zoneDev.name])
         myArray.append((str(i), zoneName))
      return myArray


   def getZoneDevices(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []

      for dev in indigo.devices:
         try:
            if dev.deviceTypeId == 'alarmZone':
               myArray.append((dev.id, dev.name))
         except:
            pass
      return myArray

   def getKeypadList(self, filter="", valuesDict=None, typeId="", targetId=0):
      myArray = []
      for i in range(1, 9):
         keypadName = str(i)
         if i in self.keypadList.keys():
            keypDev = indigo.devices[self.keypadList[i]]
            keypadName = str(keypDev.pluginProps['partitionName'])
            keypadName = ''.join([str(i), ' - ', keypadName])
            #keypadName = ''.join([str(i), ' - ', keypDev.name])
         myArray.append((str(i), keypadName))
      return myArray




   ######################################################################################
   # Configuration Routines
   ######################################################################################

   # Reads the plugins config file into our own variables
   #
   def getConfiguration(self, valuesDict):

      # Tell our logging class to reread the config for level changes
      self.mylogger.readConfig()

      self.mylogger.log(3, u"getConfiguration start")

      try:

         # Get setting of Create Variables checkbox
         if valuesDict[u'createVariables'] is True:
            self.createVariables = True
         else:
            self.createVariables = False

         # If the variable folder doesn't exist, disable variables, we're done!
         if valuesDict[u'variableFolder'] not in indigo.variables.folders:
            self.createVariables = False

         self.useSerial = False
         if valuesDict[u'configInterface'] == 'serial':
            # using serial port interface IT-100 or similar
            self.useSerial = True

         self.configKeepTimeSynced = valuesDict.get(u'syncTime', True)

         self.configSpeakVariable = None
         if u'speakToVariableEnabled' in valuesDict:
            if valuesDict[u'speakToVariableEnabled'] is True:
               self.configSpeakVariable = int(valuesDict[u'speakToVariableId'])
               if self.configSpeakVariable not in indigo.variables:
                  self.mylogger.logError('Speak variable not found in variable list')
                  self.configSpeakVariable = None

         self.configEmailUrgent = valuesDict.get(u'emailUrgent', '')
         self.configEmailNotice = valuesDict.get(u'updaterEmail', '')
         self.configEmailDisarm = valuesDict.get(u'EmailDisarm', '')
         self.configEmailUrgentSubject = valuesDict.get(u'emailUrgentSubject', 'Alarm Tripped')
         self.configEmailNoticeSubject = valuesDict.get(u'updaterEmailSubject', 'Alarm Trouble')
         self.configEmailDisarmSubject = valuesDict.get(u'DisarmEmailSubject', 'Who Disarmed')

         self.userCodeList = valuesDict.get(u'userCode', '').split(",")
         self.userLabelList = valuesDict.get(u'userLabel', '').split(",")
         self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))

         self.mylogger.log(3, u"Configuration read successfully")

         return True

      except:
         self.mylogger.log(2, u"Error reading plugin configuration. (happens on very first launch)")

         return False


   ######################################################################################
   # Communication Routines
   ######################################################################################

   def calcChecksum(self, s):
      calcSum = 0
      for c in s:
         calcSum += ord(c)
      calcSum %= 256
      return calcSum

   def closePort(self):
      if self.port is None:
         return
      if self.port.isOpen() is True:
         self.port.close()
         self.port = None

   def openPort(self):
      self.closePort()
      if self.useSerial is False:
         adr = self.pluginPrefs[u'TwoDS_Address'] + ':4025'
         self.mylogger.log(1, u"Initializing communication at address: %s" % adr)
         try:
            self.port = serial.serial_for_url('socket://' + adr, baudrate=115200)
         except Exception, err:
            self.mylogger.logError('Error opening socket: %s' % (str(err)))
            return False
      else:
         self.mylogger.log(1, u"Initializing communication on port %s" % self.pluginPrefs[u'serialPort'])
         try:
            self.port = serial.Serial(self.pluginPrefs[u'serialPort'], 9600, writeTimeout=1)
         except Exception, err:
            self.mylogger.logError('Error opening serial port: %s' % (str(err)))
            return False

      if self.port.isOpen() is True:
         self.port.flushInput()
         self.port.timeout = 1
         self.mylogger.log(1, u"Communication established")
         return True

      return False


   def readPort(self):
      if self.port.isOpen() is False:
         self.state = self.States.BOTH_INIT
         return ""

      data = ""
      try:
         data = self.port.readline()
      except Exception, err:
         self.mylogger.logError('Connection RX Error: %s' % (str(err)))
         # Return with '-' signaling calling subs to abort so we can re-init.
         data = '-'
         #exit()
      except:
         self.mylogger.logError('Connection RX Problem, plugin quitting')
         exit()

      return data

### Needed for programming purpose pure raw DATA from the port it self oin it's raw nature.
###
      indigo.variable.updateValue("DSC_RAW_Data", value = data)
###
   
   def writePort(self, data):
      self.port.write(data)


   def sendPacketOnly(self, data):
      pkt = "%s%02X\r\n" % (data, self.calcChecksum(data))
      self.mylogger.log(4, u"TX: %s" % pkt)
      try:
         self.writePort(pkt.encode())
      except Exception, err:
         self.mylogger.logError('Connection TX Error: %s' % (str(err)))
         exit()
      except:
         self.mylogger.logError('Connection TX Problem, plugin quitting')
         exit()


   def sendPacket(self, tx, waitFor='500', rxTimeout=3, txRetries=3):
      retries = txRetries
      txCmd = tx[:3]

      while txRetries > 0:
         self.sendPacketOnly(tx)
         ourTimeout = time.time() + rxTimeout
         txRetries -= 1
         while time.time() < ourTimeout:
            if self.shutdown is True:
               return ''
            (rxCmd, rxData) = self.readPacket()

            # If rxCmd == - then the socket closed, return for re-init
            if rxCmd == '-':
               return '-'

            if rxCmd == '502':
               self.mylogger.logError('Received system error after sending command, aborting.')
               return ''

            # If rxCmd is not 0 length then we received a response
            if len(rxCmd) > 0:
               if waitFor == '500':
                  if (rxCmd == '500') and (rxData == txCmd):
                     return rxData
               elif (rxCmd == waitFor):
                  return rxData
         if txCmd != '000':
            self.mylogger.logError('Timed out after waiting for response to command %s for %u seconds, retrying.' % (tx, rxTimeout))
      self.mylogger.logError('Resent command %s %u times with no success, aborting.' % (tx, retries))
      return ''


   def readPacket(self):

      data = self.readPort()
      if len(data) == 0:
         return ('', '')
      elif data == '-':
         # socket has closed, return with signal to re-initialize
         return ('-', '')

      data = data.strip()

      m = re.search(r'^(...)(.*)(..)$', data)
      if not m:
         return ('', '')

      # Put this try in to try to catch exceptions when non-ascii characters
      # were received, not sure why they are being received.
      try:
         self.mylogger.log(4, u"RX: %s" % data)
         (cmd, dat, sum) = (m.group(1), m.group(2), int(m.group(3), 16))
      except:
         self.mylogger.logError(u'IT-100/Envisalink Error: Received a response with invalid characters')
         return ('', '')

      if sum != self.calcChecksum("".join([cmd, dat])):
         self.mylogger.logError("Checksum did not match on a received packet.")
         return ('', '')

      # Parse responses based on cmd value
      #
      
      if cmd == '500':
         self.mylogger.log(3, u"ACK for cmd %s." % dat)
         self.cmdAck = dat

      elif cmd == '501':
         self.mylogger.logError(u'IT-100/Envisalink Error: Received a command with a bad checksum')

      elif cmd == '502':
         errText = u'Unknown'

         if dat == '001':
            errText = u'Receive Buffer Overrun (a command is received while another is still being processed)'
         elif dat == '002':
            errText = u'Receive Buffer Overflow'
         elif dat == '003':
            errText = u'Transmit Buffer Overflow'

         elif dat == '010':
            errText = u'Keybus Transmit Buffer Overrun'
         elif dat == '011':
            errText = u'Keybus Transmit Time Timeout'
         elif dat == '012':
            errText = u'Keybus Transmit Mode Timeout'
         elif dat == '013':
            errText = u'Keybus Transmit Keystring Timeout'
            # this error is sometimes received after disarming a tripped partition with TPI disarm command 040
         elif dat == '014':
            errText = u'Keybus Interface Not Functioning (the TPI cannot communicate with the security system)'
         elif dat == '015':
            errText = u'Keybus Busy (Attempting to Disarm or Arm with user code)'
         elif dat == '016':
            errText = u'Keybus Busy – Lockout (The panel is currently in Keypad Lockout – too many disarm attempts)'
         elif dat == '017':
            errText = u'Keybus Busy – Installers Mode (Panel is in installers mode, most functions are unavailable)'
         elif dat == '018':
            errText = u'Keybus Busy – General Busy (The requested partition is busy)'

         elif dat == '020':
            errText = u'API Command Syntax Error'
         elif dat == '021':
            errText = u'API Command Partition Error (Requested Partition is out of bounds)'
         elif dat == '022':
            errText = u'API Command Not Supported'
         elif dat == '023':
            errText = u'API System Not Armed (sent in response to a disarm command)'
         elif dat == '024':
            errText = u'API System Not Ready to Arm (not secure, in delay, or already armed)'
            self.triggerEvent(u'eventFailToArm')
            self.speak('speakTextFailedToArm')
         elif dat == '025':
            errText = u'API Command Invalid Length'
         elif dat == '026':
            errText = u'API User Code not Required'
         elif dat == '027':
            errText = u'API Invalid Characters in Command'

         self.mylogger.logError(u"IT-100/Envisalink Error (%s): %s" % (dat, errText))

      elif cmd == '505':
         if dat == '3':
            self.mylogger.log(3, u'Received login request')

      elif cmd == '510':
         # Keypad LED State Updates for Partition 1 only
         leds = int(dat, 16)

         if leds & 1 > 0:
            self.updateKeypad(1, u'LEDReady', 'on')
         else:
            self.updateKeypad(1, u'LEDReady', 'off')

         if leds & 2 > 0:
            self.updateKeypad(1, u'LEDArmed', 'on')
         else:
            self.updateKeypad(1, u'LEDArmed', 'off')

         if leds & 16 > 0:
            self.updateKeypad(1, u'LEDTrouble', 'on')
         else:
            self.updateKeypad(1, u'LEDTrouble', 'off')

         if leds & 4 > 0:
            self.updateKeypad(1, u'LEDMemory', 'on')
         else:
            self.updateKeypad(1, u'LEDMemory', 'off')

         if leds & 8 > 0:
            self.updateKeypad(1, u'LEDBypass', 'on')
         else:
            self.updateKeypad(1, u'LEDBypass', 'off')

      elif cmd == '511':
         # Keypad LED Flashing State Update
         # Same as 510 above but means an LED is flashing
         # We don't use this right now
         pass

      elif cmd == '550':

         m = re.search(r'^(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$', dat)
         if m:
            tHour = int(m.group(1))
            tMin = int(m.group(2))
            dMonth = int(m.group(3)) - 1
            dMonthDay = int(m.group(4))
            dYear = int(m.group(5))

            # Check if we should sync time
            if self.configKeepTimeSynced is True:
               d = datetime.now()
               # Is the hour different or minute off by more than one minute?
               if (d.hour != tHour) or (abs(d.minute - tMin) > 1):
                  self.mylogger.log(1, u"Setting alarm panel time and date.")
                  self.txCmdList.append((kCmdNormal, u"010%s" % d.strftime("%H%M%m%d%y")))
               else:
                  self.mylogger.log(3, u"Alarm panel time is within 1 minute of actual time, no update necessary.")

            # If this is a 2DS/Envisalink interface then lets insert the time (updated every 4 minutes)
            if self.useSerial is False:
               tAmPm = u'a'
               if tHour >= 12:
                  tAmPm = u'p'

               if tHour > 12:
                  tHour -= 12
               elif tHour == 0:
                  tHour = 12
               self.updateKeypad(0, u'LCDLine1', u'  Date     Time ')
               self.updateKeypad(0, u'LCDLine2', u"%s %02u/%02u %2u:%02u%s" % (kMonthList[dMonth], dMonthDay, dYear, tHour, tMin, tAmPm))

      elif cmd == '560':
         self.mylogger.log(1, u"Telephone Ring Tone Has Been Detected.")
         for trig in self.triggerList:
            trigger = indigo.triggers[trig]
            if trigger.pluginTypeId == u'eventNoticeTelephone_Ring':
               indigo.trigger.execute(trigger.id)


      elif cmd == '561' or cmd == '562':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (sensor, temp) = (int(m.group(1)), int(m.group(2)))
            if cmd == '562':
               self.updateSensorTemp(sensor, 'outside', temp)
            else:
               self.updateSensorTemp(sensor, 'inside', temp)

      elif cmd == '563':
         m = re.search(r'^(.)(...)(...)$', dat)
         if m:
            (sensor, cool, heat) = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
            self.updateSensorTemp(sensor, 'cool', cool)
            self.updateSensorTemp(sensor, 'heat', heat)

      elif cmd == '601':
         # a zone goes into alarm
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.updateZoneState(zone, kZoneStateTripped)
            if (len(self.trippedZoneList) == 0):
               if "DSC_Alarm_Memory" in indigo.variables:
                  indigo.variable.updateValue("DSC_Alarm_Memory", value ="")
            if zone not in self.trippedZoneList:
               self.trippedZoneList.append(zone)
               self.sendZoneTrippedEmail()
               indigoVar =""
               for zoneNum in self.trippedZoneList:
                  zone = indigo.devices[self.zoneList[zoneNum]]
                  indigoVar += (zone.name + "; ")
               if "DSC_Alarm_Memory" in indigo.variables:
                  indigo.variable.updateValue("DSC_Alarm_Memory", indigoVar)
               self.triggerEvent(u'eventZoneTripped')

      elif cmd == '602':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            dev = indigo.devices[self.keypadList[partition]]
            zonedev = indigo.devices[self.zoneList[zone]]
            keyp = str(dev.pluginProps['partitionName'])
            self.mylogger.log(1, u"Zone '%s' Restored. (Partition %d '%s')" % (zonedev.name, partition, keyp))

      elif cmd == '603':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.mylogger.log(3, u"Zone Number %d Has a Tamper Condition." % zone)

      elif cmd == '604':
         m = re.search(r'^(.)(...)$', dat)
         if m:
            (partition, zone) = (int(m.group(1)), int(m.group(2)))
            self.mylogger.log(3, u"Zone Number %d Tamper Condition has been Restored." % zone)

      elif cmd == '605':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Has a Fault Condition." % zone)

      elif cmd == '606':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Fault Condition has been Restored." % zone)

      elif cmd == '609':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Open." % zone)
         self.updateZoneState(zone, kZoneStateOpen)
         if self.repeatAlarmTripped is True:
            if zone in self.closeTheseZonesList:
               self.closeTheseZonesList.remove(zone)

      elif cmd == '610':
         zone = int(dat)
         self.mylogger.log(3, u"Zone Number %d Closed." % zone)
         # Update the zone to closed ONLY if the alarm is not tripped
         # We want the tripped states to be preserved so someone looking
         # at their control page will see all the zones that have been
         # opened since the break in.
         if self.repeatAlarmTripped is False:
            self.updateZoneState(zone, kZoneStateClosed)
         else:
            self.closeTheseZonesList.append(zone)

      elif cmd == '616':
         #cmd is sent after a zone is bypassed or bypass is cancelled.
         #This dump can be forced with keypress command [1*1#] if partition is not armed. If partition is armed it will switch armed state from stay to away and vice versa.
         #Routine that identifies bypassed zones and updates zone status kZoneBypassNo or kZoneBypassYes via newState (unfortunately for partition 1 only)
         BypassHexDump = str(dat)       #this is the 16-digit hex string for bypassed zones. Partition 1 only!
         self.mylogger.log(3, u"Bypass Hex Dump (%s)" % BypassHexDump)
         BBDump = format(int(BypassHexDump, 16), '0>64b')
         BypassBinaryDump = "".join(["".join([m[i:i+1] for i in range(8-1,-1,-1)]) for m in [BBDump[i:i+8] for i in range(0,len(BBDump),8)]])
         #reordered in 8 bit words to get zones in 1-64 order
         self.mylogger.log(2, u"Bypass Binary Dump (%s)" % BypassBinaryDump)
         for i in range(64):
            if BypassBinaryDump[i-1] == '1':
               self.updateZoneBypass(i, kZoneBypassYes)
            elif BypassBinaryDump[i-1] == '0':
               self.updateZoneBypass(i, kZoneBypassNo)

      elif cmd == '620':
         self.mylogger.log(1, u"Duress Alarm Detected")
         self.sendDuressEmail(u"Duress Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateDuress)

      elif cmd == '621':
         self.mylogger.log(1, u"Fire Key Alarm Detected")
         self.sendPanicEmail(u"Fire Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateFire)

      elif cmd == '623':
         self.mylogger.log(1, u"Auxiliary/Medical Key Alarm Detected")
         self.sendPanicEmail(u"Ambulance/Medical Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStateAmbulance)

      elif cmd == '625':
         self.mylogger.log(1, u"Panic Key Alarm Detected")
         self.sendPanicEmail(u"Police/Panic Key Alarm Detected")
         # This updates all keypads (partitions)
         self.updateKeypad(0, u'PanicState', kPanicStatePanic)

      elif cmd == '631':
         self.mylogger.log(1, u"Auxiliary/Smoke Input Alarm Detected")

      elif cmd == '632':
         self.mylogger.log(1, u"Auxiliary/Smoke Input Alarm Restored")

      elif cmd == '650':
         self.mylogger.log(3, u"Partition %d Ready" % int(dat))
         partition = int(dat)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         # This reports ready state only for keypads i.e. partitions that are ready.

      elif cmd == '651':
         self.mylogger.log(3, u"Partition %d Not Ready" % int(dat))
         partition = int(dat)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')


      elif cmd == '652':
         if len(dat) == 1:
            partition = int(dat)
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            self.mylogger.log(3, u"Alarm Panel Armed. (Partition %d '%s')" % (partition, keyp))
            self.updateKeypad(partition, u'state', kAlarmStateArmed)
            self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
            self.updateKeypad(partition, u'LEDReady', 'off')
            self.updateKeypad(partition, u'LEDArmed', 'on')     # these update LEDs for partitions 1-8
            self.speak('speakTextArmed')
            self.trippedZoneList = []

         elif len(dat) == 2:
            m = re.search(r'^(.)(.)$', dat)
            if m:
               (partition, mode) = (int(m.group(1)), int(m.group(2)))
               dev = indigo.devices[self.keypadList[partition]]
               keyp = str(dev.pluginProps['partitionName'])
               self.mylogger.log(1, u"Alarm Panel Armed in %s Mode. (Partition %d '%s')" % (kArmedModeList[mode], partition, keyp))
               if (mode == 0) or (mode == 2):
                  armedEvent = u'armedAway'
                  self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateAway)
               else:
                  armedEvent = u'armedStay'
                  self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateStay)
                  self.updateKeypad(partition, u'LEDBypass', 'on')   #LED on since motion sensors are bypassed

               self.triggerEvent(armedEvent)
               for trig in self.triggerList:
                  trigger = indigo.triggers[trig]
                  if trigger.pluginTypeId == u'eventPartitionArmed':
                     if trigger.pluginProps[u'partitionNum'] == str(partition):
                        indigo.trigger.execute(trigger.id)

               self.updateKeypad(partition, u'state', kAlarmStateArmed)
               self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
               self.updateKeypad(partition, u'LEDReady', 'off')
               self.updateKeypad(partition, u'LEDArmed', 'on')     # these update LEDs for partitions 1-8
               self.speak('speakTextArmed')
               self.trippedZoneList = []


      elif cmd == '653':
         # Partition Ready - Forced Arming Enabled
         # We don't do anything with this now.
         pass

      elif cmd == '654':
         # partition is in alarm due to zone violations or panic & fire alarm
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         keyp = " '" + keyp + "'"
         self.mylogger.log(1, u"Alarm TRIPPED! (Partition %d%s)" % (partition, keyp))
         self.updateKeypad(int(dat), u'state', kAlarmStateTripped)
         self.updateKeypad(int(dat), u'LEDMemory', 'on')     # this updates LED for partitions 1-8
         self.triggerEvent(u'eventAlarmTripped')
         self.repeatAlarmTrippedNext = time.time()
         self.repeatAlarmTripped = True

      elif cmd == '655':
         # This command is send after user disarms an alarm that was not tripped (also see cmd 750, 751).
         # This is only disarm cmd sent if arming is cancelled during exit delay (which also cancels zone bypass)
         # If the alarm has been disarmed while it was tripped, update any zone states
         # that were closed during the break in.  We don't update them during the event
         # so that Indigo's zone states will represent a zone as tripped during the entire event.
         if self.repeatAlarmTripped is True:
            self.repeatAlarmTripped = False
            for zone in self.closeTheseZonesList:
               self.updateZoneState(zone, kZoneStateClosed)
            self.closeTheseZonesList = []

         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         keypstate = str(dev.states['state'])
         if partition == 1 and keypstate == kAlarmStateExitDelay:
            self.mylogger.log(1, u"Alarm Disarmed. (Partition %d '%s')" % (partition, keyp))

            #Only request bypassed zone list if we are not using IT-100
            if self.useSerial is False:
               self.sleep(1)    #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
               self.txCmdList.append((kCmdNormal, '071' + str(partition) + '*1#'))      #triggers cmd 616 only if partition was in exit delay and cmd 750 is not sent.
         self.trippedZoneList = []
         self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
         self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
         self.updateKeypad(partition, u'LEDArmed', 'off')
         self.updateKeypad(0, u'PanicState', kPanicStateNone)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'off')

         #self.triggerEvent(u'eventAlarmDisarmed') #use cmd 750 & 751 triggers
         #self.speak('speakTextDisarmed')  #use cmd 750 & 751 triggers


      elif cmd == '656':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Exit Delay. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateExitDelay)
         self.updateKeypad(partition, u'LEDArmed', 'on')
         #keyp = indigo.devices[self.keypadList[partition]]
         #LEDMemoryState = keyp.states["LEDMemory"]
         self.updateKeypad(partition, u'LEDMemory', 'off')
         if "DSC_Alarm_Memory" in indigo.variables:
            indigo.variable.updateValue("DSC_Alarm_Memory", value = "no tripped zones")
         self.speak('speakTextArming')

      elif cmd == '657':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Entry Delay. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateEntryDelay)
         self.speak('speakTextEntryDelay')

      elif cmd == '663':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Keypad Chime Enabled. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'KeypadChime', kKeypadStateChimeEnabled)

      elif cmd == '664':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Keypad Chime Disabled. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'KeypadChime', kKeypadStateChimeDisabled)

      elif cmd == '672':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Panel Failed to Arm. (Partition %d '%s')" % (partition, keyp))
         self.triggerEvent(u'eventFailToArm')
         self.speak('speakTextFailedToArm')

      elif cmd == '673':
         #sends busy reply for partitions that are not defined by the plugin
         partition = int(dat)
         self.mylogger.log(3, u"Partition %d Busy." % (partition))

      elif cmd == '700':
         m = re.search(r'^(.)..(..)$', dat)
         if m:
            (partition, user) = (int(m.group(1)), m.group(2))
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            #self.userCodeList = self.pluginPrefs[u'userCode'].split(",")
            #self.userLabelList = self.pluginPrefs[u'userLabel'].split(",")
            #self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))
            keyu = self.userLabelDict.get(user, "")
            if keyu:
               keyu = " '" + keyu +"'"
            self.mylogger.log(1, u"Alarm Panel Armed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            #No info on whether stay or away armed, but cmd 652 is also triggered with that info
            self.updateKeypad(partition, u'state', kAlarmStateArmed)
            self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
            self.updateKeypad(partition, u'LEDReady', 'off')
            self.updateKeypad(partition, u'LEDArmed', 'on')
            self.speak('speakTextArmed')
            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userArmed':
                  if trigger.pluginProps[u'userCode'] == user:
                     indigo.trigger.execute(trigger.id)

      elif cmd == '701':
         #special arming, e.g. IndigoTouch on iPhone
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(2, u"Alarm Panel Specially Armed. (Partition %d '%s')" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateArmed)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')
         self.updateKeypad(partition, u'LEDArmed', 'on')
         self.speak('speakTextArmed')


      elif cmd == '702':
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Panel Armed. (Partition %d '%s' with zone(s) bypass)" % (partition, keyp))
         self.updateKeypad(partition, u'state', kAlarmStateArmed)
         self.updateKeypad(partition, u'ReadyState', kReadyStateFalse)
         self.updateKeypad(partition, u'LEDReady', 'off')
         self.updateKeypad(partition, u'LEDArmed', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'on')
         self.speak('speakTextArmed')


      elif cmd == '750':
         # this command is send after user disarms an alarm that was tripping or not tripping (vs cmd 655).
         m = re.search(r'^(.)..(..)$', dat)
         if m:
            (partition, user) = (int(m.group(1)), m.group(2))
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            #self.userCodeList = self.pluginPrefs[u'userCode'].split(",")
            #self.userLabelList = self.pluginPrefs[u'userLabel'].split(",")
            #self.userLabelDict = dict(zip(self.userCodeList, self.userLabelList))
            keyu = self.userLabelDict.get(user, "")
            if keyu:
               keyu = " '" + keyu +"'"
            self.mylogger.log(1, u"Alarm Panel Disarmed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            self.sendEmailDisarm(u"Alarm Panel Disarmed by User %s%s. (Partition %d '%s')" % (user, keyu, partition, keyp))
            if "DSC_Last_User_Disarm" in indigo.variables:
               indigo.variable.updateValue("DSC_Last_User_Disarm", value = "User " + user + keyu)
               indigo.variable.updateValue("DSC_Last_User", value = keyu[2:-1])
               
            # self.trippedZoneList = []    #We do not want to delete list of tripped zones here
            self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
            self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
            self.updateKeypad(partition, u'LEDArmed', 'off')
            self.updateKeypad(0, u'PanicState', kPanicStateNone)
            self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
            self.updateKeypad(partition, u'LEDReady', 'on')
            self.updateKeypad(partition, u'LEDBypass', 'off')
            self.triggerEvent(u'eventAlarmDisarmed')
            self.speak('speakTextDisarmed')

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userDisarmed':
                  if trigger.pluginProps[u'userCode'] == user:
                     indigo.trigger.execute(trigger.id)

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'userDisarmedPartition':
                  if trigger.pluginProps[u'userCode'] == user and trigger.pluginProps[u'partitionNum'] == str(partition):
                     indigo.trigger.execute(trigger.id)

            for trig in self.triggerList:
               trigger = indigo.triggers[trig]
               if trigger.pluginTypeId == u'eventPartitionDisarmed':
                  if trigger.pluginProps[u'partitionNum'] == str(partition):
                     indigo.trigger.execute(trigger.id)

         # If the alarm has been disarmed while it was tripped, update any zone states
         # that were closed during the break in.  We don't update them during the event
         # so that Indigo's zone states will represent a zone as tripped during the entire event.
         if self.repeatAlarmTripped is True:
            self.repeatAlarmTripped = False
            for zone in self.closeTheseZonesList:
               self.updateZoneState(zone, kZoneStateClosed)
            self.closeTheseZonesList = []

         #Only request bypassed zone list if we are on partition 1 and not using IT-100
         if (partition == 1) and (self.useSerial is False):
            self.sleep(1) #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
            #self.txCmdList.append((kCmdNormal, '071' + str(partition) + '*1#'))      #triggers cmd 616
            self.txCmdList.append((kCmdNormal, '0711*1#'))      #triggers cmd 616


      elif cmd == '751':
         #special opening (triggered by keyswitch but not by Indigo Touch)
         partition = int(dat)
         dev = indigo.devices[self.keypadList[partition]]
         keyp = str(dev.pluginProps['partitionName'])
         self.mylogger.log(1, u"Alarm Disarmed by Special Opening (Partition %d '%s')" % (partition, keyp))
         # self.trippedZoneList = []    #We do not want to delete list of tripped zones here
         self.updateKeypad(partition, u'state', kAlarmStateDisarmed)
         self.updateKeypad(partition, u'ArmedState', kAlarmArmedStateDisarmed)
         self.updateKeypad(partition, u'LEDArmed', 'off')
         self.updateKeypad(0, u'PanicState', kPanicStateNone)
         self.updateKeypad(partition, u'ReadyState', kReadyStateTrue)
         self.updateKeypad(partition, u'LEDReady', 'on')
         self.updateKeypad(partition, u'LEDBypass', 'off')

         self.triggerEvent(u'eventAlarmDisarmed')
         self.speak('speakTextDisarmed')

         for trig in self.triggerList:
            trigger = indigo.triggers[trig]
            if trigger.pluginTypeId == u'eventPartitionDisarmed':
               if trigger.pluginProps[u'partitionNum'] == str(partition):
                  indigo.trigger.execute(trigger.id)

         #Only request bypassed zone list if we are not using IT-100
         if (partition == 1) and (self.useSerial is False):
            self.sleep(1) #add delay if keybus buffer overruns. Only send partition 1 cmd since other partitions don't work
            self.txCmdList.append((kCmdNormal, '0711*1#'))      #triggers cmd 616


      elif cmd == '800':
         self.mylogger.log(1, u"Alarm Panel Battery is low.")
         self.sendTroubleEmail(u"Alarm panel battery is low.")

      elif cmd == '801':
         self.mylogger.log(1, u"Alarm Panel Battery is now ok.")
         self.sendTroubleEmail(u"Alarm panel battery is now ok.")

      elif cmd == '802':
         self.mylogger.log(1, u"AC Power Lost.")
         self.sendTroubleEmail(u"AC Power Lost.")
         self.triggerEvent(u'eventNoticeAC_Trouble')

      elif cmd == '803':
         self.mylogger.log(1, u"AC Power Restored.")
         self.sendTroubleEmail(u"AC Power Restored.")
         self.triggerEvent(u'eventNoticeAC_Restore')

      elif cmd == '806':
         self.mylogger.log(1, u"An open circuit has been detected across the bell terminals.")
         self.sendTroubleEmail(u"An open circuit has been detected across the bell terminals.")

      elif cmd == '807':
         self.mylogger.log(1, u"The bell circuit has been restored.")
         self.sendTroubleEmail(u"The bell circuit has been restored.")

      elif cmd == '814':
         self.mylogger.log(1, u"FTC Trouble.")
         self.sendTroubleEmail(u"The panel has failed to communicate successfully to the monitoring station.")

      elif cmd == '840':
         partition = int(dat)
         if partition in self.keypadList:
            dev = indigo.devices[self.keypadList[partition]]
            keyp = str(dev.pluginProps['partitionName'])
            keyp = " '" + keyp + "'"
            self.mylogger.log(1, u"Trouble Status (LED ON). (Partition %d%s)" % (partition, keyp))
            self.updateKeypad(partition, u'LEDTrouble', 'on')   # this updates LED for partitions 1-8
            self.troubleClearedTimer = 0

      elif cmd == '841':
         #Sends Trouble off for all partitions, including undefined ones.
         partition = int(dat)
         self.mylogger.log(2, u"Trouble Status Restore (LED OFF). (Partition %d)" % (partition))
         self.updateKeypad(partition, u'LEDTrouble', 'off')  # this updates LED for partitions 1-8
         if self.troubleCode > 0:
            # If the trouble light goes off, set a 10 second timer.
            # If the light is still off after 10 seconds we'll clear our status
            # This is required because the panel turns the light off/on quickly
            # when the light is actually on.
            self.troubleClearedTimer = 10

      elif cmd == '849':
         self.mylogger.log(3, u"Received trouble code byte 0x%s" % dat)
         newCode = int(dat, 16)

         if newCode != self.troubleCode:
            self.troubleCode = newCode
            if self.troubleCode > 0:
               body = "Trouble Code Received:\n"
               if self.troubleCode & 1: body += "- Service is Required\n"
               if self.troubleCode & 2: body += "- AC Power Lost\n"
               if self.troubleCode & 4: body += "- Telephone Line Fault\n"
               if self.troubleCode & 8: body += "- Failure to Communicate\n"
               if self.troubleCode & 16: body += "- Sensor/Zone Fault\n"
               if self.troubleCode & 32: body += "- Sensor/Zone Tamper\n"
               if self.troubleCode & 64: body += "- Sensor/Zone Low Battery\n"
               if self.troubleCode & 128: body += "- Loss of Time\n"
               self.sendTroubleEmail(body)

      elif cmd == '851':
         self.mylogger.log(3, u"Partition Busy Restore. (Partition %d)" % int(dat))
      elif cmd == '896':
         self.mylogger.log(3, u"Keybus Fault")
      elif cmd == '897':
         self.mylogger.log(3, u"Keybus Fault Restore")
      elif cmd == '900':
         self.mylogger.logError(u"Code Required")

      elif cmd == '901':
         #this updates the virtual keypad
         #for char in dat:
         #   self.mylogger.log(3, u"LCD DEBUG: %d" % ord(char))
         m = re.search(r'^...(..)(.*)$', dat)
         if m:
            lcdText = re.sub(r'[^ a-zA-Z0-9_/\:-]+', ' ', m.group(2))
            half = len(lcdText)/2
            half1 = lcdText[:half]
            half2 = lcdText[half:]
            self.mylogger.log(3, u"LCD Update, Line 1:'%s' Line 2:'%s'" % (half1, half2))
            self.updateKeypad(0, u'LCDLine1', half1)
            self.updateKeypad(0, u'LCDLine2', half2)

      elif cmd == '903':
         m = re.search(r'^(.)(.)$', dat)
         if m:
            (ledName, ledState) = (kLedIndexList[int(m.group(1))], kLedStateList[int(m.group(2))])
            self.mylogger.log(3, u"LED '%s' is '%s'." % (ledName, ledState))

            if ledState == 'flashing':
               ledState = 'on'

            if ledName == 'Ready':
               self.updateKeypad(1, u'LEDReady', ledState)
            elif ledName == 'Armed':
               self.updateKeypad(1, u'LEDArmed', ledState)
            elif ledName == 'Trouble':
               self.updateKeypad(1, u'LEDTrouble', ledState)
            elif ledName == 'Bypass':
               self.updateKeypad(1, u'LEDBypass', ledState)
            elif ledName == 'Memory':
               self.updateKeypad(1, u'LEDMemory', ledState)


      elif cmd == '904':
         self.mylogger.log(3, u"Beep Status")

      elif cmd == '905':
         self.mylogger.log(3, u"Tone Status")

      elif cmd == '906':
         self.mylogger.log(3, u"Buzzer Status")

      elif cmd == '907':
         self.mylogger.log(3, u"Door Chime Status")

      elif cmd == '908':
         m = re.search(r'^(..)(..)(..)$', dat)
         if m:
            self.mylogger.log(3, u"DSC Software Version %s.%s" % (m.group(1), m.group(2)))
      else:
         #self.mylogger.log(3, u"RX: %s" % data)
         self.mylogger.log(3, u"Unrecognized command received (Cmd:%s Dat:%s Sum:%d)" % (cmd, dat, sum))

      return (cmd, dat)



   ######################################################################################
   # Indigo Device State Updating
   ######################################################################################

   # Updates temperature of DSC temperature sensor
   #
   def updateSensorTemp(self, sensorNum, key, temp):
      if temp > 127:
         temp = 127 - temp
      self.mylogger.log(3, u"Temp sensor %d %s temp now %d degrees." % (sensorNum, key, temp))
      if sensorNum in self.tempList.keys():
         if key == 'inside':
            self.tempList[sensorNum].updateStateOnServer(key=u"temperatureInside", value=temp)
            indigo.variable.updateValue("DSC_Temp_Inside", value = "%d" % temp )
         elif key == 'outside':
            self.tempList[sensorNum].updateStateOnServer(key=u"temperatureOutside", value=temp)
            indigo.variable.updateValue("DSC_Temp_Outside", value = "%d" % temp)
         elif key == 'cool':
            self.tempList[sensorNum].updateStateOnServer(key=u"setPointCool", value=temp)
            indigo.variable.updateValue("DSC_Temp_Cool_Setpoint", value = "%d" % temp)
         elif key == 'heat':
            self.tempList[sensorNum].updateStateOnServer(key=u"setPointHeat", value=temp)
            indigo.variable.updateValue("DSC_Temp_Heat_Setpoint", value = "%d" % temp)
   
         if self.tempList[sensorNum].pluginProps['zoneLogChanges'] == 1:
            self.mylogger.log(1, u"Temp sensor %d %s temp now %d degrees." % (sensorNum, key, temp))


   # Updates zone group
   #
   def updateZoneGroup(self, zoneGroupDevId):

      zoneGrp = indigo.devices[zoneGroupDevId]

      zoneGrp.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=0)

      newState = kZoneGroupStateClosed
      for zoneId in self.zoneGroupList[zoneGroupDevId]:
         zoneState = indigo.devices[int(zoneId)].states[u'state']
         if (zoneState != kZoneStateClosed) and (newState != kZoneGroupStateTripped):
            if zoneState == kZoneStateOpen:
               newState = kZoneGroupStateOpen
            elif zoneState == kZoneStateTripped:
               newState = kZoneGroupStateTripped

      if zoneGrp.states[u'state'] != newState:
         zoneGrp.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=0)
         zoneGrp.updateStateOnServer(key=u"state", value=newState)


   # Updates indigo variable instance var with new value varValue
   #
   def updateZoneState(self, zoneKey, newState):

      if zoneKey in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneKey]]
         zoneType = zone.pluginProps['zoneType']
         #zonePartition = zone.pluginProps['zonePartition']

         # If the new state is different from the old state
         # then lets update timers and set the new state
         if zone.states[u'state'] != newState:
            # This is a new state, update all states and timers
            zone.updateStateOnServer(key=u"LastChangedShort", value="0m")
            zone.updateStateOnServer(key=u"LastChangedTimer", value=0)
            zone.updateStateOnServer(key=u"state", value=newState)
            timeNow = datetime.now().strftime("%H:%M:%S")

            # Check if this zone is assigned to a zone group so we can update it
            for devId in self.zoneGroupList:
               #self.mylogger.log(3, u"Zone Group id: %s contains %s" % (zone.id,self.zoneGroupList[devId]))
               if str(zone.id) in self.zoneGroupList[devId]:
                  self.updateZoneGroup(devId)

            if 'var' in zone.pluginProps.keys():
               self.updateVariable(zone.pluginProps['var'], newState)

            if newState == kZoneStateTripped:
               self.mylogger.log(1, u"Alarm Zone '%s' TRIPPED!" % zone.name)

            if zone.pluginProps['zoneLogChanges'] == 1:
               if newState == kZoneStateOpen:
                  self.mylogger.log(1, u"Alarm Zone '%s' Opened." % zone.name)
#                  if zoneType != "zoneTypeMotion":
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Opened at %s" % (zone.name, timeNow))
               elif newState == kZoneStateClosed:
                  self.mylogger.log(1, u"Alarm Zone '%s' Closed." % zone.name)
#                  if zoneType != "zoneTypeMotion":
                  indigo.variable.updateValue("DSC_Last_Zone_Active", value = "%s Closed at %s" % (zone.name, timeNow))
                  
            if zoneType == "zoneTypeMotion":
               indigo.variable.updateValue("DSC_Last_Motion_Active", value = "%s at %s" % (zone.name, timeNow))
               

   def updateZoneBypass(self, zoneKey, newState):

      if zoneKey in self.zoneList.keys():
         zone = indigo.devices[self.zoneList[zoneKey]]
         #zoneType = zone.pluginProps['zoneType']

         # If the new bypass state is different from the old state
         # then lets set the new state
         if zone.states[u'bypass'] != newState:
            # This is a new bypass state, update all states
            zone.updateStateOnServer(key=u"bypass", value=newState)

            if 'var' in zone.pluginProps.keys():
               self.updateVariable(zone.pluginProps['var'], newState)

            #if zone.pluginProps['zoneLogChanges'] == 1:
            if newState == kZoneBypassYes:
               self.mylogger.log(1, u"Alarm Zone '%s' Bypassed." % zone.name)
            elif newState == kZoneBypassNo:
               self.mylogger.log(1, u"Alarm Zone '%s' Bypass Cancelled." % zone.name)


   def updateKeypad(self, partition, stateName, newState):

      self.mylogger.log(4, u"Updating state %s for keypad on partition %u to %s." % (stateName, partition, newState))

      # If we're updating the main keypad state, update the variable too
      if stateName == u'state':
         self.updateVariable(self.pluginPrefs[u'variableState'], newState)

      if partition == 0:
         for keyk in self.keypadList.keys():
            keyp = indigo.devices[self.keypadList[keyk]]
            keyp.updateStateOnServer(key=stateName, value=newState)
         return

      if partition in self.keypadList.keys():
         keyp = indigo.devices[self.keypadList[partition]]
         keyp.updateStateOnServer(key=stateName, value=newState)


   ######################################################################################
   # Misc
   ######################################################################################

   def sendZoneTrippedEmail(self):

      if (len(self.configEmailUrgent) == 0) or (len(self.trippedZoneList) == 0):
         return

      theBody = "The following zone(s) have been tripped:\n\n"

      for zoneNum in self.trippedZoneList:

         if zoneNum in self.closeTheseZonesList:
            stateNow = "closed"
         else:
            stateNow = "open"

         zone = indigo.devices[self.zoneList[zoneNum]]

         theBody += "%s (currently %s)\n" % (zone.name, stateNow)

      theBody += "\n--\nDSC Alarm Plugin\n\n"

      self.mylogger.log(1, u"Sending zone tripped email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         theBody = contentPrefix + "\n\n" + theBody

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=theBody)

########################################################################################
############# Kidney514 ##########  Sending email of who is disarming
########################################################################################

   def sendEmailDisarm(self, bodyText):

      if len(self.configEmailDisarm) == 0:
         return

      self.mylogger.log(1, u"Sending Disarmed by: email to %s." % self.configEmailDisarm)

      contentPrefix = self.pluginPrefs.get(u'EmailDisarmContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailDisarm, subject=self.configEmailDisarmSubject, body=bodyText)

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


   def sendTroubleEmail(self, bodyText):

      if len(self.configEmailNotice) == 0:
         return

      self.mylogger.log(1, u"Sending trouble email to %s." % self.configEmailNotice)

      contentPrefix = self.pluginPrefs.get(u'updaterEmailContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailNotice, subject=self.configEmailNoticeSubject, body=bodyText)


   def sendPanicEmail(self, bodyText):

      if len(self.configEmailUrgent) == 0:
         return

      self.mylogger.log(1, u"Sending panic alert email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=bodyText)
      #if no email address is specified the speak part does not trigger
      say = self.pluginPrefs['speakTextPanic'] + ' Keypad: ' + bodyText + '.'
      self.sayThis(say)



   def sendDuressEmail(self, bodyText):

      if len(self.configEmailUrgent) == 0:
         return

      self.mylogger.log(1, u"Sending duress alarm email to %s." % self.configEmailUrgent)

      contentPrefix = self.pluginPrefs.get(u'emailUrgentContent', '')
      if len(contentPrefix) > 0:
         bodyText = contentPrefix + "\n\n" + bodyText

      indigo.server.sendEmailTo(self.configEmailUrgent, subject=self.configEmailUrgentSubject, body=bodyText)
      #if no email address specified the speak part does not trigger
      say = self.pluginPrefs['speakTextPanic'] + ' Keypad: ' + bodyText + '.'
      self.sayThis(say)



   def sayThis(self, text):
      self.mylogger.log(3, u"SAY: %s" % text)
      #The default variable is DSC_Alarm_Text
      if self.configSpeakVariable is not None:
         if self.configSpeakVariable in indigo.variables:
            indigo.variable.updateValue(self.configSpeakVariable, value=text)
      else:
         indigo.server.speak(text)


   def speak(self, textId):
      self.mylogger.log(3, u"ID: %s" % textId)
      if self.pluginPrefs['speakingEnabled'] is False:
         return

      if len(self.pluginPrefs[textId]) == 0:
         return

      if textId == 'speakTextFailedToArm':
         zones = 0
         zoneText = ''
         for zoneNum in self.zoneList.keys():
            zone = indigo.devices[self.zoneList[zoneNum]]
            if zone.states[u'state.open'] is True:
               if zones > 0:
                  zoneText += ', '
               zoneText += zone.name.replace("Alarm_", "")
               zones += 1

         if zones == 0:
            say = self.pluginPrefs[textId]
         if zones == 1:
            say = self.pluginPrefs[textId] + '  The ' + zoneText + ' is open.'
         else:
            say = self.pluginPrefs[textId] + '  The following zones are open: ' + zoneText + '.'

         self.sayThis(say)

      elif textId == 'speakTextTripped':
         zones = 0
         zoneText = ''
         for zoneNum in self.trippedZoneList:
            zone = indigo.devices[self.zoneList[zoneNum]]
            if zones > 0:
               zoneText += ', '
            zoneText += zone.name.replace("Alarm_", "")
            zones += 1

         if zones == 0:
            return
            #if alarm is tripped due to panic, we want the panic alert to handle the talking
         if zones == 1:
            say = self.pluginPrefs[textId] + '  The ' + zoneText + ' has been tripped.'
         else:
            say = self.pluginPrefs[textId] + '  The following zones have been tripped: ' + zoneText + '.'

         self.sayThis(say)


      else:
         self.sayThis(self.pluginPrefs[textId])


   # Updates indigo variable instance var with new value varValue
   #
   def updateVariable(self, varID, varValue):

      if self.createVariables is False:
         return

      #self.mylogger.log(3, u"Variable: %s" % varID)

      if varID is None:
         return

      if varID in indigo.variables:
         indigo.variable.updateValue(varID, value=varValue)


  # Converts given time in minutes to a human format
   # 3m, 5h, 2d, etc.
   #
   def getShortTime(self, minutes):

      # If time is less than 100 min then show XXm
      if minutes < 100:
         return str(minutes) + 'm'
      # If it's less than 49 hours then show XXh
      elif minutes < 2881:
         return str(int(minutes / 60)) + 'h'
      # If it's less than 365 days then show XXd
      elif minutes < 525601:
         return str(int(minutes / 1440)) + 'd'
      # If it's anything more than 365 days then show XXmonths
      else:
         return str(int(minutes / 43800)) + 'months'

   ######################################################################################
   # Concurrent Thread
   ######################################################################################

   def runConcurrentThread(self):
      self.mylogger.log(3, u"runConcurrentThread called")
      self.minuteTracker = time.time() + 60
      self.nextUpdateCheckTime = 0

      # While Indigo hasn't told us to shutdown
      while self.shutdown is False:

         self.timeNow = time.time()

         if self.state == self.States.STARTUP:
            self.mylogger.log(3, u"STATE: Startup")

            if self.configRead is False:
               if self.getConfiguration(self.pluginPrefs) is True:
                  self.configRead = True

            if self.configRead is True:
               self.state = self.States.BOTH_INIT

            self.sleep(1)

         elif self.state == self.States.HOLD:
            if self.configRead is False:
               self.state = self.States.STARTUP
            self.sleep(1)

         elif self.state == self.States.HOLD_RETRY:
            self.mylogger.log(1, "Plugin will attempt to re-initialize again in %u minutes." % self.currentHoldRetryTime)
            self.nextRetryTime = self.timeNow + (kHoldRetryTimeMinutes*60)
            self.state = self.States.HOLD_RETRY_LOOP

         elif self.state == self.States.HOLD_RETRY_LOOP:
            if self.configRead is False:
               self.state = self.States.STARTUP
            if self.timeNow >= self.nextRetryTime:
               self.state = self.States.BOTH_INIT
            self.sleep(1)

         elif self.state == self.States.BOTH_INIT:
            if self.openPort() is True:
               if self.useSerial is False:
                  self.state = self.States.SO_CONNECT
                  self.sleep(1)

                  # Enable pinging every 5 minutes
                  self.nextPingTime = self.timeNow + kPingInterval
               else:
                  self.state = self.States.ENABLE_TIME_BROADCAST

            else:
               #self.mylogger.logError('Error opening port, will retry in %u minutes.' % self.currentHoldRetryTime)
               self.state = self.States.HOLD_RETRY

         elif self.state == self.States.SO_CONNECT:
            err = True

            # Read packet to clear the port of the 5053 login request
            self.readPacket()

            attemptLogin = True
            while attemptLogin is True:
               attemptLogin = False
               rx = self.sendPacket('005' + self.pluginPrefs[u'TwoDS_Password'], waitFor='505')
               if len(rx) == 0:
                  self.mylogger.logError('Timeout waiting for Envisalink to respond to login request.')
               else:
                  rx = int(rx)
                  if rx == 0:
                     self.mylogger.logError('Envisalink refused login request.')
                  elif rx == 1:
                     err = False
                     self.mylogger.log(1, "Connected to Envisalink.")
                  elif rx == 3:
                     # 2DS sent login request, retry (Happens when socket is first opened)
                     self.mylogger.log(3, u"Received login request, retrying login...")
                     attemptLogin = True
                  else:
                     self.mylogger.logError('Unknown response from Envisalink login request.')

            # This delay is required otherwise 2DS locks up
            self.sleep(1)

            if err is True:
               self.state = self.States.HOLD_RETRY
            else:
               self.state = self.States.ENABLE_TIME_BROADCAST


         elif self.state == self.States.ENABLE_TIME_BROADCAST:

            # Enable time broadcast
            self.mylogger.log(2, "Enabling Time Broadcast")
            rx = self.sendPacket('0561')
            if len(rx) > 0:
               self.mylogger.log(2, u"Time Broadcast enabled.")
               self.state = self.States.BOTH_PING
            else:
               self.mylogger.logError(u'Error enabling Time Broadcast.')
               self.state = self.States.HOLD_RETRY

         elif self.state == self.States.BOTH_PING:

            #Ping the panel to confirm we are in communication
            err = True
            self.mylogger.log(2, u"Pinging the panel to test communication...")
            rx = self.sendPacket('000')
            if len(rx) > 0:
               self.mylogger.log(2, u"Ping was successful.")
               err = False
            else:
               self.mylogger.logError('Error pinging panel, aborting.')

            if err is True:
               self.state = self.States.HOLD_RETRY
            else:
               #Request a full state update
               self.mylogger.log(2, "Requesting a full state update.")
               rx = self.sendPacket('001')
               if len(rx) == 0:
                  self.mylogger.logError('Error getting state update.')
                  self.state = self.States.HOLD_RETRY
               else:
                  self.mylogger.log(2, "State update request successful, initialization complete, starting normal operation.")
                  self.state = self.States.BOTH_POLL

         elif self.state == self.States.BOTH_POLL:
            if self.configRead is False:
               self.state = self.States.STARTUP
            else:

               if (self.useSerial is False) and (self.timeNow > self.nextPingTime):
                  #self.mylogger.log(1,"Pinging 2DS")
                  self.txCmdList.append((kCmdNormal, '000'))
                  self.nextPingTime = self.timeNow + kPingInterval

               if len(self.txCmdList) > 0:
                  (cmdType, data) = self.txCmdList[0]
                  if cmdType == kCmdNormal:
                     txRsp = self.sendPacket(data)
                     if txRsp == '-':
                        # If we receive - socket has closed, lets re-init
                        self.mylogger.logError('Tried to send data but socket seems to have closed.  Trying to re-initialize.')
                        self.state = self.States.BOTH_INIT
                     else:
                        # send was a success, remove command from queue
                        del self.txCmdList[0]

                  elif cmdType == kCmdThermoSet:
                     self.setThermostat(data)
                     del self.txCmdList[0]
               else:
                  (rxRsp, rxData) = self.readPacket()
                  if rxRsp == '-':
                     # If we receive - socket has closed, lets re-init
                     self.mylogger.logError('Tried to read data but socket seems to have closed.  Trying to re-initialize.')
                     self.state = self.States.BOTH_INIT


         # Check if the trouble timer counter is timing
         # We need to know if the trouble light has remained off
         # for a few seconds before we assume the trouble is cleared
         if self.troubleClearedTimer > 0:
            self.troubleClearedTimer -= 1
            if self.troubleClearedTimer == 0:
               self.troubleCode = 0
               self.sendTroubleEmail(u"Trouble Code Cleared.")

         if self.repeatAlarmTripped is True:
            #timeNow = time.time()
            if self.timeNow >= self.repeatAlarmTrippedNext:
               self.repeatAlarmTrippedNext = self.timeNow + 12
               self.speak('speakTextTripped')


         # If a minute has elapsed
         if self.timeNow >= self.minuteTracker:

            # Do we need to check for a new version?
            self.updater.checkVersionPoll()

            # Increment all zone changed timers
            self.minuteTracker += 60
            for zoneKey in self.zoneList.keys():
               zone = indigo.devices[self.zoneList[zoneKey]]
               tmr = zone.states[u"LastChangedTimer"] + 1
               zone.updateStateOnServer(key=u"LastChangedTimer", value=tmr)
               zone.updateStateOnServer(key=u"LastChangedShort", value=self.getShortTime(tmr))

            for zoneGroupDeviceId in self.zoneGroupList:
               zoneGroupDevice = indigo.devices[zoneGroupDeviceId]
               tmr = zoneGroupDevice.states[u"AnyMemberLastChangedTimer"] + 1
               zoneGroupDevice.updateStateOnServer(key=u"AnyMemberLastChangedTimer", value=tmr)
               tmr = zoneGroupDevice.states[u"EntireGroupLastChangedTimer"] + 1
               zoneGroupDevice.updateStateOnServer(key=u"EntireGroupLastChangedTimer", value=tmr)


      self.closePort()
      self.mylogger.log(3, u"Exiting Concurrent Thread")


   def stopConcurrentThread(self):
      self.mylogger.log(3, u"stopConcurrentThread called")
      self.shutdown = True
      self.mylogger.log(3, u"Exiting stopConcurrentThread")

Page 1 of 1

Who is online

Users browsing this forum: No registered users and 3 guests