Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
4040 lines (3379 sloc) 122 KB
' Gambas class file
' Description:
' CVisonic.class
' Support for Visonic PowerMax/PowerMaster Alarm
'
' Development Status:
' Done
' TODO:
' Build Visonic Commander, Arm/Disarm, X-10, Standard-Mode
' Links:
' http://www.domoticaforum.eu/viewtopic.php?f=68&t=6517
' Credits:
' Initial setup by Wouter Wolkers and Alexander Kuiper.
' Thanks to everyone who helped decode the data.
' DomotiGa - an open source home automation program.
' Copyright (C) Ron Klinkien, The Netherlands.
' Read file called COPYING for license details.
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Module/Class specific variables
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public PluginName As String = "Visonic"
Public PluginFriendlyName As String = "Visonic Security"
Public PluginVersion As String = "0.95"
Public PluginAuthor As String = "Wouter Wolkers, Alexander Kuiper"
Public PluginProtocols As String[]
Public PluginMaxInstances As Integer = 1
Public KeyName As String
Public LogLabel As String = "[Visonic] "
Public Instance As Integer
Public InterfaceId As Integer = 0
Public IsRunning As Boolean
Public ErrorText As String
Public ErrorWhere As String
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Queue structure
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Struct VisonicQueueEntry
Msg As String
Command As Byte[]
Receive As Byte
ReceiveCnt As Integer
ReceiveCntFixed As Integer ' Need to store it extra, because with a re-queue it can get lost
ReceiveRetries As Integer
Options As String
End Struct
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Private variables
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private $bEnabled As Boolean
Private $sSerPort As String
Private $sTCPHost As String
Private $iTCPPort As Integer
Private $sInterface As String
Private $bDebug As Boolean
Private $iMasterCode As Integer = &H0000 ' Not Known
Private $iDisarmArmCode As Integer = &H0000 ' Not Known
Private $iDownloadCode As Integer
Private $bSendInit As Boolean = True
Private $bAutoSyncTime As Boolean
Private $bForceStandardMode As Boolean
Private $iMotionTimeout As Integer
Private $iSensorArmStatus As Integer
Private $tPowerLinkKeepAlive As New Timer As "tPowerLinkKeepAlive"
Public hMySocket As CSocket
Public hMySerial As CSerialPort
' Receive command buffer
Private $bReceiveHeaderFound As Boolean = False ' When 0D char is detected
Private $tReceiveLast0DChar As Date ' When last 0D char is received
Private $sReceiveBuffer As New Byte[] ' Complete buffer of received data, including pre, crc and postamble
Private $sReceiveData As New Byte[] ' buffer without pre, crc and postamble (complete command, with valid crc)
Private $dReceiveLastPacket As Date ' When last 'ReceiveData' packet is received
Private $bReceiveLength As Byte = 0 ' Expected data length, 0=unknown length
Private $cSendLastCommand As VisonicQueueEntry
Private $cSendQueue As New Object[]
Const $iPowerLinkTimeOut As Integer = 60000 ' 60 seconds
Const $iSendDelay As Integer = 500 ' 500 msec delay between last received message and the new message
Const $iSendTimeout As Integer = 1000 ' 1000 msec wait on a response
Const $iReceiveCompletePacketTimeout As Integer = 3000 ' 3000msec to wait on a complete packet
Private $bDownloadMode As Boolean
Private $bInitialized As Boolean
Private $iPanelType As Integer
Private $iModelType As Byte
Private $bPowerMaster As Boolean
Private $tQueueDelay As New Timer As "tQueueDelay"
Private $bQueueDelay As Boolean
Private $tQueueTimeout As New Timer As "tQueueTimeout"
Private $oTripTimers As New Object[]
Public tTripTimer As CTimerGeneric
Private $cConfig As New Collection
Private $cMemoryMap As New Collection
Private $dB00304FirstPacket As Date = 0 ' First B0 03 04 packet
Private $dB00304LastPacket As Date = 0 ' last B0 03 04 packet
Private $bB00304ZoneNumber As Byte ' Zone number
Private $bB00304ZoneInfo As Byte[] ' Zone info
'########################################################
' PowerMax/Master send messages
'########################################################
' ### ACK messages, depending on the type, we need to ACK differently ###
Private VMSG_ACK1 As Byte[] = [&H02] 'NONE
Private VMSG_ACK2 As Byte[] = [&H02, &H43] 'NONE
' ### Init, restore and enroll messages ###
Private VMSG_INIT As Byte[] = [&HAB, &H0A, &H00, &H01, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'NONE
Private VMSG_RESTORE As Byte[] = [&HAB, &H06, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'NONE
Private VMSG_ENROLL As Byte[] = [&HAB, &H0A, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'NONE - DownloadCode: 4 & 5
Private VMSG_ARMDISARM As Byte[] = [&HA1, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'NONE - MasterCode: 4 & 5
Private VMSG_STATUS As Byte[] = [&HA2, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'A5
Private VMSG_EVENTLOG As Byte[] = [&HA0, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &H43] 'A0 - MasterCode: 4 & 5
' #### PowerMaster message ###
Private VMSG_PMASTER_STAT1 As Byte[] = [&HB0, &H01, &H04, &H06, &H02, &HFF, &H08, &H03, &H00, &H00, &H43] 'B0 STAT1
'Private VMSG_PMASTER_STAT2 As Byte[] = [&HB0, &H01, &H05, &H06, &H02, &HFF, &H08, &H03, &H00, &H00, &H43] 'B0 STAT2
'Private VMSG_PMASTER_STAT3 As Byte[] = [&HB0, &H01, &H07, &H06, &H02, &HFF, &H08, &H03, &H00, &H00, &H43] 'B0 STAT3
' ### Start/stop download information ###
Private VMSG_DL_START As Byte[] = [&H24, &H00, &H00, &H01, &H00, &H00, &H00, &H00, &H00, &H00, &H00] '&H3C - DownloadCode: 3 & 4
Private VMSG_DL_GET As Byte[] = [&H0A] '&H33
Private VMSG_DL_EXIT As Byte[] = [&H0F] 'NONE
' ### PowerMax download/config items (some apply to PowerMaster too) ###
' ---
' Pos. 0 1 2 3 4 5 6 7 8 9 A
' E.g. 3E 00 04 20 00 B0 00 00 00 00 00
' 1=Index, 2=Page, 3=Low Length, 4=High Length, 5=Always B0?
' ---
' PowerMaster30:
' Pos. 0 1 2 3 4 5 6 7 8 9 A
' E.g. 3E FF FF 42 1F B0 05 48 01 00 00
' 1=FF 2=FF 3=Low Length, 4=High Length, 5=Always B0, 6=Index, 7=Page
' ---
Private VMSG_DL_PANELFW As Byte[] = [&H3E, &H00, &H04, &H20, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_ZONESTR As Byte[] = [&H3E, &H00, &H19, &H00, &H02, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_SERIAL As Byte[] = [&H3E, &H30, &H04, &H08, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
'Private VMSG_DL_EVENTLOG As Byte[] = [&H3E, &HDF, &H04, &H28, &H03, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_TIME As Byte[] = [&H3E, &HF8, &H00, &H20, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private vMSG_DL_COMMDEF As Byte[] = [&H3E, &H01, &H01, &H1E, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_USERPINCODES As Byte[] = [&H3E, &HFA, &H01, &H10, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_OTHERPINCODES As Byte[] = [&H3E, &H0A, &H02, &H0A, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_PHONENRS As Byte[] = [&H3E, &H36, &H01, &H20, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_PGMX10 As Byte[] = [&H3E, &H14, &H02, &HD5, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_PARTITIONS As Byte[] = [&H3E, &H00, &H03, &HF0, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_ZONES As Byte[] = [&H3E, &H00, &H09, &H78, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_KEYFOBS As Byte[] = [&H3E, &H78, &H09, &H40, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_2WKEYPADS As Byte[] = [&H3E, &H00, &H0A, &H08, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_1WKEYPADS As Byte[] = [&H3E, &H20, &H0A, &H40, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_SIRENS As Byte[] = [&H3E, &H60, &H0A, &H08, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_X10NAMES As Byte[] = [&H3E, &H30, &H0B, &H10, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_ZONENAMES As Byte[] = [&H3E, &H40, &H0B, &H1E, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
'Private VMSG_DL_ZONECUSTOM As Byte[] = [&H3E, &HA0, &H1A, &H50, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
' ### PowerMaster download/config items ###
Private VMSG_DL_MASTER_SIRENKEYPADSZONE As Byte[] = [&H3E, &HE2, &HB6, &H10, &H04, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER_USERPINCODES As Byte[] = [&H3E, &H98, &H0A, &H60, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER_SIRENS As Byte[] = [&H3E, &HE2, &HB6, &H50, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER_KEYPADS As Byte[] = [&H3E, &H32, &HB7, &H40, &H01, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER_ZONENAMES As Byte[] = [&H3E, &H60, &H09, &H40, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER_ZONES As Byte[] = [&H3E, &H72, &HB8, &H80, &H02, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
Private VMSG_DL_MASTER10_EVENTLOG As Byte[] = [&H3E, &HFF, &HFF, &HD2, &H07, &HB0, &H05, &H48, &H01, &H00, &H00] '&H3F
Private VMSG_DL_MASTER30_EVENTLOG As Byte[] = [&H3E, &HFF, &HFF, &H42, &H1F, &HB0, &H05, &H48, &H01, &H00, &H00] '&H3F
Private VMSG_SET_DATETIME As Byte[] = [&H46, &HF8, &H00, &H00, &H00, &H00, &H00, &H00, &H00, &HFF, &HFF] '&H46
'########################################################
' PowerMax/Master definitions for partitions, events, keyfobs, etc
'########################################################
' 0=PowerMax, 1=PowerMax+, 2=PowerMax Pro, 3=PowerMax Complete, 4=PowerMax Pro Part
' 5=PowerMax Complete Part, 6=PowerMax Express, 7=PowerMaster10, 8=PowerMaster30
Private VCFG_PARTITIONS As Byte[] = [1, 1, 1, 1, 3, 3, 1, 3, 3]
'Private VCFG_EVENTS As Integer[] = [250, 250, 250, 250, 250, 250, 250, 250, 1000]
Private VCFG_KEYFOBS As Byte[] = [8, 8, 8, 8, 8, 8, 8, 8, 32]
Private VCFG_1WKEYPADS As Byte[] = [8, 8, 8, 8, 8, 8, 8, 0, 0]
Private VCFG_2WKEYPADS As Byte[] = [2, 2, 2, 2, 2, 2, 2, 8, 32]
Private VCFG_SIRENS As Byte[] = [2, 2, 2, 2, 2, 2, 2, 4, 8]
Private VCFG_USERCODES As Byte[] = [8, 8, 8, 8, 8, 8, 8, 8, 48]
'Private VCFG_PROXTAGS As Byte[] = [0, 0, 8, 0, 8, 8, 0, 8, 32]
Private VCFG_WIRELESS As Byte[] = [28, 28, 28, 28, 28, 28, 28, 29, 62]
Private VCFG_WIRED As Byte[] = [2, 2, 2, 2, 2, 2, 1, 1, 2]
Private VCFG_ZONECUSTOM As Byte[] = [0, 5, 5, 5, 5, 5, 5, 5, 5]
Private VINFO_ZONETYPE As String[] = ["Non-Alarm", "Emergency", "Flood", "Gas", "Delay 1", "Delay 2", "Interior-Follow", "Perimeter", "Perimeter-Follow", "24 Hours Silent", "24 Hours Audible", "Fire", "Interior", "Home Delay", "Temperature", "Outdoor", "16"]
Private VINFO_CHIME As String[] = ["Chime Off", "Melody Chime", "Zone Name Chime"]
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Mandatory Sub for EACH Module/Interface to initialize:
' - The cPluginList[x].Settings are copied into local variables
' - Port/Connection will be started (any errors caught)
' - Any other code per interface
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub StartPlugin(cPl As CPluginEntry)
Dim rResult As Result
Dim sZone As String
Dim iZone As Integer
Dim sName As String
KeyName = cPl.KeyName
Instance = cPl.Instance
If Instance <> 1 Then LogLabel = Replace(LogLabel, "] ", "#" & Instance & "] ")
' Copy configuration items locally
$bEnabled = cPl.Settings["enabled"]
$sInterface = cPl.Settings["type"]
$sTCPHost = cPl.Settings["tcphost"]
$iTCPPort = cPl.Settings["tcpport"]
$sSerPort = cPl.Settings["serialport"]
$bAutoSyncTime = cPl.Settings["autosynctime"]
$bForceStandardMode = cPl.Settings["forcestandardmode"]
$iMotionTimeout = cPl.Settings["motiontimeout"]
$iSensorArmStatus = cPl.Settings["sensorarmstatus"]
'$iMasterCode = cPl.Settings["mastercode"]
Try $iDownloadCode = cPl.Settings["downloadcode"]
If Error Then $iDownloadCode = 5650
$bDebug = cPl.Settings["debug"]
' First test if it is in an allowed range
If $iDownloadCode >= 1000 Or $iDownloadCode <= 9999 Then
' Convert the value to hex, because it is stored in that way in Visonic
$iDownloadCode = Val("&H" & $iDownloadCode)
Else
' Assume the download code as '5650'
$iDownloadCode = &H5650 ' VP
Endif
' Retrieve our Interface Id, we don't have to retrieve it more then once
InterfaceId = Devices.FindInterface("Visonic Interface")
If InterfaceId = 0 Then
ErrorText = "Required InterfaceId for 'Visonic Interface' can't be retrieved from the database"
WriteLog("ERROR: " & ErrorText)
IsRunning = False
Return
Endif
$bDownloadMode = False
$bInitialized = False
$iPanelType = -1
$iModelType = 0
$bPowerMaster = False
$cSendLastCommand = Null
' When we force standard mode, we will retrieve the zones from the devices
' and we don't try to download the configuration from the panel
If $bForceStandardMode Then
' Set our panel to PowerMax
$iPanelType = 0
rResult = Devices.FindDevicesForInterface(InterfaceId, Instance)
If rResult And If rResult.Available Then
For Each rResult
sZone = rResult!address
If Len(sZone) = 3 And If InStr(sZone, "Z") = 1 Then
Try iZone = Val(Mid(sZone, 2, 2))
If Error Then
WriteLog("ERROR: Device id '" & rResult!id & "' has an invalid Visonic address")
Continue
Endif
If iZone < 1 Or If iZone > 30 Then
WriteLog("ERROR: Device id '" & rResult!id & "' has an invalid Visonic address")
Continue
Endif
If Not $cConfig.Exist("zoneinfo") Then
$cConfig["zoneinfo"] = New Collection
Endif
If Not $cConfig["zoneinfo"].Exist(iZone) Then
sName = Devices.FindNameForDeviceType(rResult!devicetype_id)
Select sName
Case "Visonic PIR"
$cConfig["zoneinfo"][iZone] = New Collection
$cConfig["zoneinfo"][iZone]["sensortype"] = "Motion"
Case "Visonic Door/Window Contact"
$cConfig["zoneinfo"][iZone] = New Collection
$cConfig["zoneinfo"][iZone]["sensortype"] = "Magnet"
Case "Visonic Wired"
$cConfig["zoneinfo"][iZone] = New Collection
$cConfig["zoneinfo"][iZone]["sensortype"] = "Wired"
End Select
Else
WriteLog("ERROR: Device id '" & rResult!id & "' has a duplicate Visonic address")
Continue
Endif
Else
WriteLog("ERROR: Device id '" & rResult!id & "' has an invalid Visonic address")
Endif
Next
Endif
Endif
' Connect/Initialize connection
If $sInterface = "tcp" Then
ConnectTCP()
Else
ConnectSerial()
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Mandatory sub for EACH Class to stop
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub StopPlugin()
Try Disconnect()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Easy helper for WriteLog
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub WriteLog(sLog As String)
Main.WriteLog(LogLabel & sLog)
If $bDebug Then Main.WriteDebugLog(LogLabel & sLog)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Easy helper for WriteDebugLog
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub WriteDebugLog(sLog As String)
If $bDebug Then Main.WriteDebugLog(LogLabel & sLog)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' connect to the tcp host:port
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ConnectTCP()
Dim iReconnectRetry As Integer = 0
' try to close the connection
Try hMySocket.Close
Try iReconnectRetry = hMySocket.ReconnectRetry
' get a new one - but also pass on our previous reconnect counter
hMySocket = New CSocket(iReconnectRetry) As "MySocket"
hMySocket.DataType = &HFF + gb.Byte
hMySocket.Connect($sTCPHost, $iTCPPort)
' Write to main logfile we are trying to connect
WriteLog(PluginFriendlyName & " TCP interface connecting to " & $sTCPHost & ":" & $iTCPPort)
Catch ' some errors
WriteLog("ERROR: " & PluginFriendlyName & " TCP interface FAILED to connect to " & $sTCPHost & ":" & $iTCPPort)
WriteLog("ERROR: " & Error.Text)
IsRunning = False
ErrorText = Error.Text
ErrorWhere = Error.Where
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Open serial port and start keepalive timer
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ConnectSerial()
Dim iReconnectRetry As Integer = 0
' try to close the port
Try hMySerial.Close
Try iReconnectRetry = hMySerial.ReconnectRetry
' get a new one - but store the retry counter between new instances
hMySerial = New CSerialPort(iReconnectRetry) As "MySerial"
With hMySerial
.DataType = &HFF + gb.Byte ' Byte[]
.PortName = $sSerPort
.Speed = 9600
.Parity = 0
.DataBits = 8
.StopBits = 1
.FlowControl = 0
.Open()
End With
' Write to main logfile we connected successfully
WriteLog(PluginFriendlyName & " serial interface connected to port " & $sSerPort)
IsRunning = True
Run()
Catch ' some errors
WriteLog("ERROR: " & PluginFriendlyName & " serial interface FAILED to connect to port " & $sSerPort)
WriteLog("ERROR: " & Error.Text)
IsRunning = False
ErrorText = Error.Text
ErrorWhere = Error.Where
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Close port and stop timers
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Disconnect()
' Try to close the connection
Try hMySocket.Close
Try hMySerial.Close
' Stop possible timers
StopTimers()
WriteLog(PluginFriendlyName & IIf($sInterface = "tcp", " TCP connection closed.", " Port closed."))
Finally
IsRunning = False
ErrorText = ""
ErrorWhere = ""
Catch ' some errors
WriteLog("ERROR: " & Error.Text)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Stop all timers
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub StopTimers()
$tPowerLinkKeepAlive.Stop
$tQueueDelay.Stop
$tQueueTimeout.Stop
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' peer closed tcp socket
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Closed()
WriteLog("ERROR: TCP socket closed by peer.")
IsRunning = False
ErrorText = "TCP socket closed by peer"
StopTimers()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' error while connected/connecting to tcp host
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Error(sMsg As String)
WriteLog("ERROR: " & sMsg)
IsRunning = False
ErrorText = sMsg
StopTimers()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' host ip address found
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Found()
Log.Plugin_DNS_Found(LogLabel, $sTCPHost)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' received data from the tcp port
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Read(vVar As Variant)
Dim bData As Byte
If vVar Then
For Each bData In vVar
ProcessReceivedChar(bData)
Next
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' tcp socket is connected
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Ready()
WriteLog("TCP interface connected.")
IsRunning = True
Run()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Called when we should reconnect to the tcp host
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySocket_Reconnect()
ConnectTCP()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Handle errors
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySerial_Error(sMsg As String)
WriteLog("ERROR: " & sMsg)
StopTimers()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' got data back from Alarm panel and parse it when it's complete
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySerial_Read(vVar As Variant)
Dim bData As Byte
If vVar Then
For Each bData In vVar
ProcessReceivedChar(bData)
Next
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Handle reconnect(s)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub MySerial_Reconnect()
' ReConnect/Initialize connection
ConnectSerial()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' When SerialPort is connected, start the initialization
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Run()
$bDownloadMode = False
$bInitialized = False
$iPanelType = -1
$iModelType = 0
$bPowerMaster = False
$cSendLastCommand = Null
' Define 60 seconds timer and start it for PowerLink communication
$tPowerLinkKeepAlive.Stop
$tPowerLinkKeepAlive = New Timer As "tPowerLinkKeepAlive"
$tPowerLinkKeepAlive.Delay = $iPowerLinkTimeOut
$tPowerLinkKeepAlive.Start
' Initialize last timestamp, also add delay to trigger it directly
$dReceiveLastPacket = DateAdd(Now(), gb.Millisecond, $iSendDelay)
' Reset the receive buffer
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
' Only force the init when requested
If $bSendInit Then
SendMsg_INIT()
Endif
' Send the download command, this should initiate the communication
' Only skip it, if we force standard mode
If Not $bForceStandardMode Then
SendMsg_DL_START()
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Start timer for PowerLink connection, we will try to re-establish if it goes away
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub tPowerLinkKeepAlive_Timer()
' TBD: Don't do this forever, stop after a certain number of failures
' TBD: Needs to be tested more - it doesn't seem to work? Maybe we need to close/open the serial port?
If $bDebug Then WriteDebugLog("PowerLink Timer Expired (" & CInt($iPowerLinkTimeOut / 1000) & " seconds)")
SendMsg_RESTORE()
' Restart timer
$tPowerLinkKeepAlive.Stop
$tPowerLinkKeepAlive = New Timer As "tPowerLinkKeepAlive"
$tPowerLinkKeepAlive.Delay = $iPowerLinkTimeOut
$tPowerLinkKeepAlive.Start
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Check the CRC of the received data
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub CheckCRC(bData As Byte[]) As Boolean
Dim bChecksum As Byte
' Anything shorter then pre, some data, checksum and postamble isn't possible
If bData.Count < 3 Then Return False
' Copy checksum
bChecksum = bData.Copy(bData.Count - 2, 1)[0]
' Re-copy data, remove pre, checksum and postamble
bData = bData.Copy(1, bData.Count - 3)
' Ignore the CRC of a MsgType=&HF1
If bData.Length >= 1 And If bData[0] = &HF1 Then Return True
' Check if calculation and checksum match
If CalculateCRC(bData) = bChecksum Then
Return True
Else
Return False
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Calculate the CRC
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub CalculateCRC(bData As Byte[]) As Byte
Dim bByte As Byte
Dim sCount As Short
Dim sChecksum As Short
' Count all byte values together
sCount = 0
For Each bByte In bData
sCount += bByte
Next
' Do a modulus on the total count
sChecksum = sCount Mod 255
' Additional safety if we go over 255, shouldn't ever happen
If sChecksum > 255 Then
sChecksum = sChecksum Mod 255
Endif
' Substract from 0xFF if the checksum isn't 0
' Checksum = 0xFF is never possible
If sChecksum <> 0 Then
sChecksum = &HFF - sChecksum
Endif
Return CByte(sChecksum)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Start timer for messages queued
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub StartTimer(iDelay As Integer)
' When we have an invalid delay, reset to maximum
If iDelay < 0 Or If iDelay > $iSendDelay Then
iDelay = $iSendDelay
Endif
' Only start timer if it isn't running yet
'If Not $tQueueDelay.Enabled Then
$bQueueDelay = False
$tQueueDelay.Stop
$tQueueDelay = New Timer As "tQueueDelay"
$tQueueDelay.Delay = iDelay
$tQueueDelay.Start
'Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Timer expired, start processing the queue (can restart the timer)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub tQueueDelay_Timer()
$tQueueDelay.Stop
' Timer expired, kick off the processing of the queue
If $cSendLastCommand And If $bQueueDelay Then
ProcessQueue(True)
Else
ProcessQueue()
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Process the queue, restart timer or send command. Also possible to resend the last message
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ProcessQueue(Optional bResend As Boolean = False)
Dim iMSec As Integer
Dim aStr As New String[]
Dim iCode As Integer
Dim bAddToQueue As Boolean
' Resend the request, but reset the receive counter to the original value
If bResend Then
If $cSendLastCommand Then
' Compare if we already have the same command in the buffer, then pop it first
bAddToQueue = True
If $cSendQueue.Count >= 1 Then
If $cSendQueue[0].Msg = $cSendLastCommand.Msg And If $cSendQueue[0].Command.ToString() = $cSendLastCommand.Command.ToString() And If $cSendQueue[0].Receive = $cSendLastCommand.Receive Then
bAddToQueue = False
Endif
Endif
If bAddToQueue Then
$cSendLastCommand.ReceiveCnt = $cSendLastCommand.ReceiveCntFixed
$cSendQueue.Add($cSendLastCommand, 0)
If $bDebug Then WriteDebugLog("Resending Request '" & $cSendLastCommand.Msg & "' " & ByteToHex($cSendLastCommand.Command.Copy()) & ". Queue Count=" & $cSendQueue.Count)
Endif
Else
WriteDebugLog("ERROR: Resend is requested, but the last request is NULL?")
Endif
Endif
If $cSendQueue.Count = 0 Then
$cSendLastCommand = Null
Return
Endif
' Check the timer again - possible something is received
iMSec = DateDiff($dReceiveLastPacket, Now(), gb.Millisecond)
' Check if the difference is at least 500+ msec
If iMSec < $iSendDelay Then
' Start timer again for fast sending the command
StartTimer($iSendDelay - iMSec)
Return
Endif
' Doublecheck if we got something in the queue
If $cSendQueue.Count >= 1 Then
' Send first queued command and remove it
$cSendLastCommand = $cSendQueue.Extract(0, 1)[0]
' Options are set, do our magic on the outgoing packet
If $cSendLastCommand.Options Then
aStr = Split($cSendLastCommand.Options, ":")
If aStr.Count >= 1 Then
Select aStr[0]
Case "PIN"
' The Pin has to be 3 fields
If aStr.Count = 3 Then
iCode = 0
Select aStr[1]
Case "MasterCode"
iCode = $iMasterCode
Case "DownloadCode"
iCode = $iDownloadCode
Case "DisarmArmCode"
iCode = $iDisarmArmCode
Default
WriteLog("ERROR: Unknown Code defined (" & aStr[1] & ")")
End Select
If iCode <> 0 Then
$cSendLastCommand.Command[CInt(aStr[2])] = Lsr(iCode, 8) And &HFF
$cSendLastCommand.Command[CInt(aStr[2]) + 1] = iCode And &HFF
Else
WriteLog("ERROR: The pincode is zero (" & ByteToHex($cSendLastCommand.Command.Copy()) & ")")
Endif
Else
WriteLog("ERROR: Invalid Option '" & $cSendLastCommand.Options & "', Expected: 3, Received: " & aStr.Count)
Endif
Default
WriteLog("ERROR: Unknown Option '" & $cSendLastCommand.Options & "'")
End Select
Else
WriteLog("ERROR: Invalid Option '" & $cSendLastCommand.Options & "'")
Endif
Endif
' Now send it to the PowerMax/Master
SendCommand($cSendLastCommand.Command.Copy())
If $bDebug Then WriteDebugLog($cSendLastCommand.Msg)
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Add a command to the send queue
' The queue is needed to prevent sending messages to quickly
' normally it requires 500msec between messages
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub QueueCommand(sData As Byte[], sMsg As String, bResponse As Byte, Optional sOptions As String = "")
Dim iCnt As Integer
Dim cEntry As New VisonicQueueEntry
' Copy the msg, command and response into our queue entry
cEntry.Msg = sMsg
cEntry.Command = sData.Copy()
cEntry.Receive = bResponse
cEntry.Options = sOptions
' Log some usefull information in debug mode
If $bDebug Then
WriteDebugLog("Queued Command (" & cEntry.Msg & ")")
Endif
' Download commands, can have answers split across multiple message
' Determine the number of messages we should get
If sData[0] = &H3E Then
iCnt = CInt(sData[4]) * &H100 + sData[3]
cEntry.ReceiveCnt = Floor(iCnt / &HB0) + 1
Else
cEntry.ReceiveCnt = 1
Endif
cEntry.ReceiveCntFixed = cEntry.ReceiveCnt
' Add it to the queue
$cSendQueue.Add(cEntry)
' Kick off queue handling if required
If $cSendQueue.Count >= 1 Then
ProcessQueue()
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Clear the queue, preventing any retry causing issues
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ClearQueue()
$cSendQueue.Clear()
$cSendLastCommand = Null
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' send a command to the alarm panel
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendCommand(sData As Byte[])
Dim bData As Byte
Dim sStr As String
' Lets update the timestamp
$dReceiveLastPacket = Now()
If InStr($sInterface, "tcp") Then
If hMySocket.Status <> Net.Active Then
WriteDebugLog("ERROR: Not connected anymore")
Return
Endif
Else
If hMySerial.Status <> Net.Active Then
WriteDebugLog("ERROR: Not connected anymore")
Return
Endif
Endif
' First add preamble, crc and postamble
bData = CalculateCRC(sData)
sData.Add(&H0D, 0)
sData.Add(bData)
sData.Add(&H0A)
If $bDebug Then
sStr = ""
For Each bData In sData
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("> " & sStr)
Endif
' Write the complete string - not byte-for-byte
If InStr($sInterface, "tcp") Then
hMySocket.Write(sData)
Else
hMySerial.Write(sData)
Endif
Catch
WriteDebugLog("ERROR: while trying to send: '" & ERROR.Text & "' at " & ERROR.Where)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Parse byte for byte, and detect pre and postamble characters
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ProcessReceivedChar(bTemp As Byte)
Dim sStr As String
Dim bData As Byte
' Check if the prevous character isn't long ago
If $bReceiveHeaderFound Then
If DateDiff($tReceiveLast0DChar, Now, gb.Millisecond) >= $iReceiveCompletePacketTimeout Then
If $bDebug Then WriteDebugLog("< " & Util.ByteToHex($sReceiveBuffer) & " (TIMEOUT ERROR: " & DateDiff($tReceiveLast0DChar, Now, gb.Millisecond) & " milliseconds)")
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
Endif
' If we only have 1 byte (preamble), check if next byte is a valid type
If $sReceiveBuffer.Length = 1 Then
' Depending on the msgtype, we assign a length or reject the current data
Select bTemp
Case &H02, &H06, &H08, &H0B ' dynamic length, between 4-5 normally
$bReceiveLength = 0
Case &H25, &H33, &H3C
$bReceiveLength = 14
Case &H3F
$bReceiveLength = 0
Case &HA0, &HA3, &HA5, &HA6, &HA7, &HAB
$bReceiveLength = 15
Case &HB0 ' dynamic length
$bReceiveLength = 0
Case &F1
$bReceiveLength = 9
Default ' invalid msgtype received
If $bDebug Then
sStr = ""
For Each bData In $sReceiveBuffer
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("< " & sStr & " (ERROR: Invalid MsgType=" & Hex$(bTemp, 2) & ")")
Endif
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
$bReceiveLength = 0
Return
End Select
Endif
' A packet can never be longer &HC0/192 bytes
If $sReceiveBuffer.Length >= &HC0 Then
If $bDebug Then
sStr = ""
For Each bData In $sReceiveBuffer
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("< " & sStr & " (ERROR: Message too long (>192 bytes)")
Endif
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
$bReceiveLength = 0
Return
Endif
Else
$tReceiveLast0DChar = Now()
Endif
' We detected a preamble, set the HeaderFound to True. It doesn't matter if
' we find more 0x0D in the data, because it Is already set To True anyway
If bTemp = &H0D Then $bReceiveHeaderFound = True
' Add the received Byte to the Array
If $bReceiveHeaderFound Then $sReceiveBuffer.Add(bTemp)
' We detected a postamble and a preamble has been found
If $bReceiveHeaderFound Then
If $bReceiveLength = 0 Then
' &HB0 and &H3F have a dynamic length, if the buffer contains the length, determine it
If $sReceiveBuffer.Length >= 5 Then
If $sReceiveBuffer[1] = &H3F Then $bReceiveLength = $sReceiveBuffer[4] + 7
If $sReceiveBuffer[1] = &HB0 Then $bReceiveLength = $sReceiveBuffer[4] + 8
Endif
Endif
If bTemp = &H0A Then
' Check the Crc, if it doesn't match - the 0x0A isn't the postamble!
If CheckCRC($sReceiveBuffer.Copy()) Then
ProcessReceivedPacket($sReceiveBuffer.Copy())
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
$bReceiveLength = 0
Else
If $bReceiveLength > 0 Then
' If length match, we got a CRC error
If $sReceiveBuffer.Length >= $bReceiveLength Then
If $bDebug Then
sStr = ""
For Each bData In $sReceiveBuffer
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("< " & sStr & " (CRC ERROR)" & IIf($sReceiveBuffer.Length > $bReceiveLength, " (INVALID LENGTH)", ""))
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
$bReceiveLength = 0
' Just send an ACK anyway
SendMsg_ACK(False)
Endif
Endif
Endif
Endif
Else
' Check if we don't go over the expected length
If $bReceiveLength > 0 Then
' If length match, we got a CRC error
If $sReceiveBuffer.Length >= $bReceiveLength Then
If $bDebug Then
sStr = ""
For Each bData In $sReceiveBuffer
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("< " & sStr & " (CRC ERROR)" & IIf($sReceiveBuffer.Length > $bReceiveLength, " (INVALID LENGTH)", ""))
' reset for next data
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
$bReceiveLength = 0
Endif
Endif
Endif
Endif
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Process a complete valid PowerMax/Master packet
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ProcessReceivedPacket(sData As Byte[])
Dim bData As Byte
Dim bAck As Boolean = False
Dim bResponse As Boolean = False
Dim sStr As String
' Also set it timestamp to now
$dReceiveLastPacket = Now()
' Dump the received raw data to the debug logfile
If $bDebug Then
sStr = ""
For Each bData In sData
sStr &= IIf(sStr, " ", "") & Hex$(bData, 2)
Next
WriteDebugLog("< " & sStr)
Endif
' Remove preample, checksum and postamble and place it in a global variable
$sReceiveData = sData.Copy(1, sData.Count - 3)
' Handle the response now
Select Case $sReceiveData[0]
Case &H02 ' ACK
Handle_MsgType02()
Case &H06 ' Timeout
Handle_MsgType06()
Case &H08 ' Access denied
bAck = True
Handle_MsgType08()
Case &H0B ' Stop
bAck = True
bResponse = True
If $bDebug Then WriteDebugLog("Stopped")
Case &H25 ' Download retry
bAck = True
bResponse = True
Handle_MsgType25()
Case &H33 ' Settings send after a MSGV_START
bAck = True
bResponse = True
Handle_MsgType33()
Case &H3C ' Messsage when start the download
bAck = True
bResponse = True
Handle_MsgType3C()
Case &H3F ' Download information
bAck = True
bResponse = True
Handle_MsgType3F()
Case &HA0 ' Event log
bAck = True
bResponse = True
Handle_MsgTypeA0()
Case &HA5 ' General Event description
bAck = True
bResponse = True
Handle_MsgTypeA5()
Case &HA7 ' Panel Status Change
bAck = True
bResponse = True
Handle_MsgTypeA7()
Case &HAB ' PowerLink
bAck = True
bResponse = True
Handle_MsgTypeAB()
Case &HB0 ' PowerMaster
bAck = True
bResponse = True
' Strangly we can get B0 messages in download mode, ignore them
If Not $bDownloadMode Then
Handle_MsgTypeB0()
Endif
Default ' Anything else ...
bAck = True
If $bDebug Then WriteDebugLog("Unknown message type: " & Hex$($sReceiveData[0], 2))
End Select
' We received a complete packet, check if it is the expected packet
If bResponse And If $cSendLastCommand Then
' The expected receive command has to be not zero
If $cSendLastCommand.Receive <> &H00 Then
' Log expect and received responses for debug purposes
If $bDebug Then WriteDebugLog("Received: " & Hex$($sReceiveData[0], 2) & ", Expected: " & Hex$($cSendLastCommand.Receive, 2) & ", Count=" & $cSendLastCommand.ReceiveCnt & ", Retries=" & $cSendLastCommand.ReceiveRetries)
' &H33 can be any number of messages, but we get a &0B when it is finished
If $cSendLastCommand.Receive = &H33 And If $sReceiveData[0] = &H0B Then
$cSendLastCommand = Null
ProcessQueue()
Else
If $cSendLastCommand.Receive = $sReceiveData[0] Then
' We don't know the number of &H33s, don't distract receive count
If $sReceiveData[0] <> &H33 Then
' Yes, matching command
$cSendLastCommand.ReceiveCnt -= 1
' Send next message if our expected count is zero
If $cSendLastCommand.ReceiveCnt <= 0 Then
$cSendLastCommand = Null
ProcessQueue()
Endif
Endif
Else
' No match, but no panic. Lets wait 1 more message before resending
If $cSendLastCommand.ReceiveRetries = 0 Then
$cSendLastCommand.ReceiveRetries += 1
Else
' Kick off the queue with resend of the last message, but only if timer didn't expire
If Not $tQueueDelay.Enabled Then ProcessQueue(True)
Endif
Endif
Endif
Else
' We don't expect anything, so kick off queue handling
$cSendLastCommand = Null
ProcessQueue()
Endif
Endif
Finally
If bAck Then SendMsg_ACK()
Catch ' some errors
WriteDebugLog("ERROR: " & ERROR.Text & " at " & ERROR.Where)
$sReceiveBuffer.Clear()
$bReceiveHeaderFound = False
'***************************
'TODO - not complete anymore
'***************************
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' The PowerMax/Master responded to the DL_START, now we can retrieve more information from the unit
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub PowerLinkEnrolled()
If $bDebug Then WriteDebugLog("PowerLink is Enrolled, retrieving PowerMax/Master information")
' Request the panel FW - Only in debug mode
SendMsg_DL_PANELFW()
' Request serial & type (not always sent by default) - Only in debug mode
SendMsg_DL_SERIAL()
' Read the names of the zones
SendMsg_DL_ZONESTR()
' Retrieve extra info if this is a PowerMaster
If $bPowerMaster Then
SendMsg_DL_MASTER_SIRENKEYPADSZONE()
' Only request eventlog in debug mode
If $bDebug Then
Select $iPanelType
Case &H07 ' PowerMaster10
SendMsg_DL_MASTER10_EVENTLOG()
Case &H08 ' PowerMaster30
SendMsg_DL_MASTER30_EVENTLOG()
End Select
Endif
Endif
' Request all other relevant information
SendMsg_DL_GET()
' We are done, exit download mode
SendMsg_DL_EXIT()
' auto-sync date/time
SendMsg_SetDateTime()
' Lets request the eventlogs
If $bDebug And If Not $bPowerMaster Then SendMsg_EVENTLOG()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Process Settings
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ProcessSettings()
Dim sData As New Byte[]
Dim sDataMaster As New Byte[]
Dim sZoneNames As New Byte[]
Dim sDataX10 As New Byte[]
Dim sPartition As New Byte[]
Dim sVar As New Byte[]
Dim iCnt As Integer
Dim jCnt As Integer
Dim iZoneCnt As Integer
Dim iCustomCnt As Integer
Dim iUserCnt As Integer
Dim iPartitionCnt As Integer
Dim iSirenCnt As Integer
Dim iKeypad1Cnt As Integer
Dim iKeypad2Cnt As Integer
Dim iKeyfobCnt As Integer
Dim bEnrolled As Boolean
Dim bEnabled As Boolean
Dim bLog As Boolean
Dim bX10ZoneType As Byte
Dim iEventLog As Integer
Dim sEventLog As New Byte[]
Dim sEventDate As String
Dim iEventType As Integer
Dim sEventType As String
Dim iEventZone As Integer
Dim sEventZone As String
' Dump MemoryMap first to logfile. We can have standard and extended ones
If $bDebug Then
If $cMemoryMap.Count Then
For Each $cMemoryMap[0]
WriteDebugLog("MemoryMap Standard " & Hex$(CInt($cMemoryMap[0].Key), 2) & ": " & ByteToHex($cMemoryMap[0][$cMemoryMap[0].Key], True))
Next
For Each $cMemoryMap[1]
WriteDebugLog("MemoryMap Extended " & Hex$(CInt($cMemoryMap[1].Key), 2) & ": " & ByteToHex($cMemoryMap[1][$cMemoryMap[1].Key], True))
Next
Endif
Endif
' Re-initialize the configuration
$cConfig = New Collection
' Only continue if we have a valid paneltype (>=0)
If $iPanelType = -1 Then
WriteDebugLog("ERROR: Can't process settings, the PanelType is unknown - maybe communication/authorization issue?")
Return
Endif
' Only continue if we have a known paneltype (<=8)
If $iPanelType > 8 Then
WriteDebugLog("ERROR: Can't process settings, the PanelType=" & $iPanelType & " is a too new type")
Return
Endif
' Try to create the PowerMax/Master panel device, to show the Arm/Disarm of the panel
Devices.Find(Instance, "Panel", InterfaceId, IIf($bPowerMaster, "Visonic PowerMaster", "Visonic PowerMax"))
' ------------------------------------
' Read serialnumber and paneltype info
' ------------------------------------
sData = ReadMemoryMap(VMSG_DL_SERIAL)
If sData = Null Then
WriteDebugLog("ERROR: Can't read the PowerMax/Master MemoryMap, communication with the unit went wrong?")
Return
Endif
If sData[7] <> $iPanelType Then
WriteDebugLog("ERROR: Initial received PanelType is different then read from MemoryMap. PanelType=" & $iPanelType & ", Read=" & sData[7])
Return
Endif
If $bDebug Then WriteDebugLog("Reading panel information")
$cConfig["panel"] = New Collection
' Convert serial into readable ascii and save
$cConfig["panel"]["serial"] = ByteToHex(sData.Copy(0, 6))
' ----------------------------------
' Now it looks to be safe to determine panel options/dimensions
' ----------------------------------
iZoneCnt = VCFG_WIRELESS[$iPanelType] + VCFG_WIRED[$iPanelType]
iCustomCnt = VCFG_ZONECUSTOM[$iPanelType]
iUserCnt = VCFG_USERCODES[$iPanelType]
iPartitionCnt = VCFG_PARTITIONS[$iPanelType]
iSirenCnt = VCFG_SIRENS[$iPanelType]
iKeypad1Cnt = VCFG_1WKEYPADS[$iPanelType]
iKeypad2Cnt = VCFG_2WKEYPADS[$iPanelType]
iKeyfobCnt = VCFG_KEYFOBS[$iPanelType]
' ----------------------------------
' Read panel eprom and software info
' ----------------------------------
sData = ReadMemoryMap(VMSG_DL_PANELFW)
$cConfig["panel"]["eprom"] = ByteToString(sData.Copy(0, 16))
$cConfig["panel"]["software"] = ByteToString(sData.Copy(16, 16))
' ------------------------------------------------------------
' Read panel date/time (doesn't get updated after initial read
' ------------------------------------------------------------
sData = ReadMemoryMap(VMSG_DL_TIME)
$cConfig["panel"]["datetime"] = Format$(sData[3], "00") & "/" & Format$(sData[4], "00") & "/" & CStr(CInt(sData[5]) + 2000) & " " & Format$(sData[2], "00") & ":" & Format$(sData[1], "00") & ":" & Format$(sData[0], "00")
If $bDebug Then
WriteDebugLog("Panel Info:")
WriteDebugLog(" PanelType: " & DisplayPanelType())
WriteDebugLog(" Model: " & DisplayPanelModel())
WriteDebugLog(" Serial: " & $cConfig["panel"]["serial"])
WriteDebugLog(" Eprom: " & $cConfig["panel"]["eprom"])
WriteDebugLog(" Software: " & $cConfig["panel"]["software"])
WriteDebugLog(" Date/Time: " & $cConfig["panel"]["datetime"])
Endif
' ---------------------------------------------------
' Determine the zone names, including the custom ones
' ---------------------------------------------------
$cConfig["zonename"] = New Collection
sData = ReadMemoryMap(VMSG_DL_ZONESTR)
For iCnt = 1 To (26 + iCustomCnt)
sVar = sData.Copy((iCnt - 1) * &H10, &H10)
If sVar[0] <> &HFF Then
$cConfig["zonename"][iCnt] = ByteToString(sVar)
Endif
Next
If $bDebug Then
WriteDebugLog("Zone Names/Aliases:")
For Each $cConfig["zonename"]
WriteDebugLog(" " & Format$($cConfig["zonename"].Key, "00") & ": " & $cConfig["zonename"][$cConfig["zonename"].Key])
Next
Endif
' -------------------------
' Retrieve telephonenumbers
' -------------------------
$cConfig["phonenr"] = New Collection
sData = ReadMemoryMap(VMSG_DL_PHONENRS)
For iCnt = 1 To 4
For jCnt = 0 To 7
If sData[8 * (iCnt - 1) + jCnt] <> &HFF Then
$cConfig["phonenr"][iCnt] &= Chr$(sData[8 * (iCnt - 1) + jCnt])
Endif
Next
Next
If $bDebug Then
bLog = False
WriteDebugLog("Phonenumbers:")
For Each $cConfig["phonenr"]
bLog = True
WriteDebugLog(" " & $cConfig["phonenr"].Key & ": " & $cConfig["phonenr"][$cConfig["phonenr"].Key])
Next
If Not bLog Then WriteDebugLog(" None")
Endif
' --------------
' Alarm settings
' --------------
$cConfig["alarm"] = New Collection
sData = ReadMemoryMap(VMSG_DL_COMMDEF)
$cConfig["alarm"]["entrydelay1"] = sData[0]
$cConfig["alarm"]["entrydelay2"] = sData[1]
$cConfig["alarm"]["exitdelay"] = sData[2]
$cConfig["alarm"]["belltime"] = sData[3]
$cConfig["alarm"]["silentpanic"] = (sData[25] And &H10 = &H10)
$cConfig["alarm"]["quickarm"] = (sData[26] And &H08 = &H08)
$cConfig["alarm"]["bypassoff"] = (sData[27] And &HC0 = &H00)
$cConfig["alarm"]["forceddisablecode"] = ByteToHex(sData.Copy(16, 2))
$iDisarmArmCode = CInt(sData[16]) * 256 + sData[17]
If $bDebug Then
WriteDebugLog("Alarm:")
WriteDebugLog(" Entry Delay 1: " & $cConfig["alarm"]["entrydelay1"] & " seconds")
WriteDebugLog(" Entry Delay 2: " & $cConfig["alarm"]["entrydelay2"] & " seconds")
WriteDebugLog(" Exit Delay: " & $cConfig["alarm"]["exitdelay"] & " seconds")
WriteDebugLog(" Bell Time: " & $cConfig["alarm"]["belltime"] & " seconds")
WriteDebugLog(" Silent Panic: " & IIf($cConfig["alarm"]["silentpanic"], "True", "False"))
WriteDebugLog(" Quick Arm: " & IIf($cConfig["alarm"]["quickarm"], "True", "False"))
WriteDebugLog(" Bypass Off: " & IIf($cConfig["alarm"]["belltime"], "True", "False"))
WriteDebugLog(" Forced Disarm Code: " & $cConfig["alarm"]["forceddisablecode"])
Endif
' -----------------
' Retrieve pincodes
' -----------------
$cConfig["pincode"] = New Collection
' If it is a PowerMaster, the usercodes are somewhere else in memory
If $bPowerMaster Then
sData = ReadMemoryMap(VMSG_DL_MASTER_USERPINCODES)
Else
sData = ReadMemoryMap(VMSG_DL_USERPINCODES)
Endif
For iCnt = 1 To iUserCnt
$cConfig["pincode"][iCnt] = ByteToHex(sData.Copy(2 * (iCnt - 1), 2))
Next
' Retrieve the installer and powerlink pincodes - they are known/visible
sData = ReadMemoryMap(VMSG_DL_OTHERPINCODES)
$cConfig["pincode"]["installer"] = ByteToHex(sData.Copy(0, 2))
$cConfig["pincode"]["masterinstaller"] = ByteToHex(sData.Copy(2, 2))
$cConfig["pincode"]["powerlink"] = ByteToHex(sData.Copy(8, 2))
' Copy the first pin into the masterpin
If $cConfig["pincode"].Exist("1") Then
Try $iMasterCode = Val("&H" & $cConfig["pincode"]["1"])
If Error Then
$iMasterCode = &H0000
WriteDebugLog("ERROR: Unable to retrieve the master pincode (" & $cConfig["pincode"]["1"] & ")")
Endif
Else
' No masterpin code ...
$iMasterCode = &H0000
Endif
If $bDebug Then
WriteDebugLog("Pincodes:")
For iCnt = 1 To iUserCnt
WriteDebugLog(" " & Format$(iCnt, "00") & ": " & $cConfig["pincode"][iCnt])
Next
WriteDebugLog(" Installer: " & $cConfig["pincode"]["installer"])
WriteDebugLog(" Master Installer: " & $cConfig["pincode"]["masterinstaller"])
WriteDebugLog(" PowerLink: " & $cConfig["pincode"]["powerlink"])
Endif
' -----------------------------------
' Retrieve if partitioning is enabled
' -----------------------------------
$cConfig["partition"] = New Collection
' If our panel supports multiple partitions, try to figure out if it is enabled or not
' PowerMax without partitions will not give the &H0300 back when requested
If iPartitionCnt > 1 Then
sPartition = ReadMemoryMap(VMSG_DL_PARTITIONS)
If sPartition <> Null Then
If sPartition[0] = 0 Then iPartitionCnt = 1
Endif
Endif
$cConfig["partition"]["count"] = iPartitionCnt
WriteDebugLog("Partitions: " & iPartitionCnt)
' ----------------------------------
' Retrieve detailed zone information
' ----------------------------------
$cConfig["zoneinfo"] = New Collection
sData = ReadMemoryMap(VMSG_DL_ZONES) ' setting
If $bPowerMaster Then
sZoneNames = ReadMemoryMap(VMSG_DL_MASTER_ZONENAMES)
sDataMaster = ReadMemoryMap(VMSG_DL_MASTER_ZONES) ' settingMr
Else
sZoneNames = ReadMemoryMap(VMSG_DL_ZONENAMES)
Endif
If (ByteToHex(sData.Copy(0, iZoneCnt)) <> String$(iZoneCnt, "00")) And If (ByteToHex(sZonenames) <> String(sZoneNames.Count, "00")) Then
For iCnt = 1 To iZoneCnt
If $bPowerMaster Then
bEnrolled = (ByteToHex(sDataMaster.Copy(iCnt * 10 - 6, 5)) <> String$(5, "00"))
Else
bEnrolled = (ByteToHex(sData.Copy(iCnt * 4 - 4, 3)) <> String$(3, "00"))
Endif
If bEnrolled Then
' Create a new collection for this node
$cConfig["zoneinfo"][iCnt] = New Collection
$cConfig["zoneinfo"][iCnt]["tripstatus"] = -1
$cConfig["zoneinfo"][iCnt]["batterystatus"] = -1
If $cConfig["zonename"].Exist(sZoneNames[iCnt - 1] + 1) Then
$cConfig["zoneinfo"][iCnt]["zonename"] = $cConfig["zonename"][sZoneNames[iCnt - 1] + 1]
Else
$cConfig["zoneinfo"][iCnt]["zonename"] = "Unknown"
Endif
If $bPowerMaster Then
$cConfig["zoneinfo"][iCnt]["zonetype"] = sData[iCnt - 1]
$cConfig["zoneinfo"][iCnt]["sensorid"] = sDataMaster[iCnt * 10 - 5]
Select $cConfig["zoneinfo"][iCnt]["sensorid"]
Case &H01
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Motion"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "Next PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic PIR"
Case &H04
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Camera"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "Next CAM PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Camera"
Case &H15
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Smoke"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "SMD-427 PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Smoke Detector"
Case &H16
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Smoke"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "SMD-426 PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Smoke Detector"
Case &H1A
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Temperature"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "TMD-560 PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Temperature"
Case &H2A
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Magnet"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "MC-302 PG2"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Door/Window Contact"
Case &HFE
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Wired"
$cConfig["zoneinfo"][iCnt]["sensorname"] = "Wired"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Wired"
Default
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Unknown"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Unknown"
End Select
Else
$cConfig["zoneinfo"][iCnt]["zonetype"] = sData[iCnt * 4 - 1]
$cConfig["zoneinfo"][iCnt]["sensorid"] = sData[iCnt * 4 - 2]
Select ($cConfig["zoneinfo"][iCnt]["sensorid"] And &H0F)
Case &H3, &H4, &HC
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Motion"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic PIR"
Case &H5, &H6, &H7
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Magnet"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Door/Window Contact"
Case &HA
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Smoke"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Smoke Detector"
Case &HB
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Gas"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Gas Detector"
Case &HF
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Wired"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Wired"
Default
$cConfig["zoneinfo"][iCnt]["sensortype"] = "Unknown"
$cConfig["zoneinfo"][iCnt]["autocreate"] = "Visonic Unknown"
End Select
Endif
$cConfig["zoneinfo"][iCnt]["zonechime"] = Lsr($cConfig["zoneinfo"][iCnt]["zonetype"], 4) And &H03
Try $cConfig["zoneinfo"][iCnt]["zonechimename"] = VINFO_CHIME[$cConfig["zoneinfo"][iCnt]["zonechime"]]
If Error Then Try $cConfig["zoneinfo"][iCnt]["zonechimename"] = "Unknown"
$cConfig["zoneinfo"][iCnt]["zonetype"] = $cConfig["zoneinfo"][iCnt]["zonetype"] And &H0F
Try $cConfig["zoneinfo"][iCnt]["zonetypename"] = VINFO_ZONETYPE[$cConfig["zoneinfo"][iCnt]["zonetype"]]
If Error Then $cConfig["zoneinfo"][iCnt]["zonetypename"] = "Unknown"
If $cConfig["partition"]["count"] > 1 Then
For jCnt = 1 To $cConfig["partition"]["count"]
If (sPartition[&H11 + iCnt - 1] And CInt(2 ^ (jCnt - 1))) > 0 Then
If $cConfig["zoneinfo"][iCnt].Exist("partition") Then
$cConfig["zoneinfo"][iCnt]["partition"] &= ", " & CStr(jCnt)
Else
$cConfig["zoneinfo"][iCnt]["partition"] = CStr(jCnt)
Endif
Endif
Next
Else
$cConfig["zoneinfo"][iCnt]["partition"] = CStr(1)
Endif
Endif
Next
' All information gathered, now print it in debug
If $bDebug Then WriteDebugLog("ZoneInfo:")
For iCnt = 1 To iZoneCnt
If $cConfig["zoneinfo"].Exist(iCnt) Then
If $bDebug Then
WriteDebugLog(" Zone " & Format$(iCnt, "00") & ": Enrolled")
WriteDebugLog(" ZoneName: " & $cConfig["zoneinfo"][iCnt]["zonename"] & " (id=" & sZoneNames[iCnt] & ")")
WriteDebugLog(" ZoneType: " & $cConfig["zoneinfo"][iCnt]["zonetypename"] & " (id=" & $cConfig["zoneinfo"][iCnt]["zonetype"] & ")")
WriteDebugLog(" Sensor: " & $cConfig["zoneinfo"][iCnt]["sensortype"] & IIf($cConfig["zoneinfo"][iCnt]["sensorname"], " (" & $cConfig["zoneinfo"][iCnt]["sensorname"] & ")", "") & " (id=" & $cConfig["zoneinfo"][iCnt]["sensorid"] & ")")
Endif
' Try to find it, it will autocreate the device if needed
Devices.Find(Instance, "Z" & Format$(iCnt, "00"), InterfaceId, $cConfig["zoneinfo"][iCnt]["autocreate"])
Else
If $bDebug Then WriteDebugLog(" Zone " & Format$(iCnt, "00") & ": Not Enrolled")
Endif
Next
Endif
' ------------------------------------------------------
' Retrieve PGM & X10, X10 - Only applicable for PowerMax
' ------------------------------------------------------
' TODO: if the X10 is a Switch or Dimmer
$cConfig["PGMX10"] = New Collection
sData = ReadMemoryMap(VMSG_DL_PGMX10)
sDataX10 = ReadMemoryMap(VMSG_DL_X10NAMES)
For iCnt = 1 To 16
bX10ZoneType = &H0F
bEnabled = False
For jCnt = 0 To 9
bEnabled = bEnabled Or (ByteToHex(sData.Copy(5 + iCnt + jCnt * &H10)) <> "00")
Next
If iCnt > 1 Then bX10ZoneType = sDataX10[iCnt - 1]
If bEnabled Or bX10ZoneType <> &H0F Then
' Create a new collection for this node
$cConfig["PGMX10"][iCnt] = New Collection
$cConfig["PGMX10"][iCnt]["enabled"] = bEnabled
If iCnt > 1 Then
Try $cConfig["PGMX10"][iCnt]["zonename"] = $cConfig["zonename"][sZoneNames[iCnt - 1] + 1]
If Error Then
$cConfig["PGMX10"][iCnt]["zonename"] = "Unknown"
Endif
Else
$cConfig["PGMX10"][iCnt]["zonename"] = "PGM"
Endif
Endif
Next
' All information gathered, now print it in debug
' If $bDebug Then
' WriteDebugLog("PGM-X10:")
' For iCnt = 1 To 16
' If $cConfig["PGMX10"].Exist(iCnt) Then
' WriteDebugLog(" N " & Format$(iCnt, "00") & ": " & IIf($cConfig["PGMX10"][iCnt]["enabled"], "Enabled", "Disabled") & " (" & IIf(iCnt = 1, "PGM", "X10") & ")")
' WriteDebugLog(" ZoneName: " & $cConfig["PGMX10"][iCnt]["zonename"] & " (id=" & sZoneNames[iCnt] & ")")
' Endif
' Next
' Endif
' ------------------------------------------------------
' Retrieve keypads and siren information
' ------------------------------------------------------
$cConfig["siren"] = New Collection
' PowerMaster has only 1 type of keypad. PowerMax has 2 types of keypads, 1 and 2-way communication
If $bPowerMaster Then
$cConfig["keypad2"] = New Collection
sData = ReadMemoryMap(VMSG_DL_MASTER_KEYPADS)
For iCnt = 1 To iKeypad2Cnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 10 - 6, 5)) <> String$(5, "00"))
If bEnrolled Then
$cConfig["keypad2"][iCnt] = New Collection
$cConfig["keypad2"][iCnt]["enrolled"] = True
Endif
Next
sData = ReadMemoryMap(VMSG_DL_MASTER_SIRENS)
For iCnt = 1 To iSirenCnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 10 - 6, 5)) <> String$(5, "00"))
If bEnrolled Then
$cConfig["siren"][iCnt] = New Collection
$cConfig["siren"][iCnt]["enrolled"] = True
Endif
Next
Else
$cConfig["keyfob"] = New Collection
$cConfig["keypad1"] = New Collection
$cConfig["keypad2"] = New Collection
sData = ReadMemoryMap(VMSG_DL_KEYFOBS)
For iCnt = 1 To iKeyfobCnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 4 - 4, 2)) <> String$(2, "00"))
If bEnrolled Then
$cConfig["keyfob"][iCnt] = New Collection
$cConfig["keyfob"][iCnt]["enrolled"] = True
Endif
Next
sData = ReadMemoryMap(VMSG_DL_1WKEYPADS)
For iCnt = 1 To iKeypad1Cnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 4 - 4, 2)) <> String$(2, "00"))
If bEnrolled Then
$cConfig["keypad1"][iCnt] = New Collection
$cConfig["keypad1"][iCnt]["enrolled"] = True
Endif
Next
sData = ReadMemoryMap(VMSG_DL_2WKEYPADS)
For iCnt = 1 To iKeypad2Cnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 4 - 4, 3)) <> String$(3, "00"))
If bEnrolled Then
$cConfig["keypad2"][iCnt] = New Collection
$cConfig["keypad2"][iCnt]["enrolled"] = True
Endif
Next
sData = ReadMemoryMap(VMSG_DL_SIRENS)
For iCnt = 1 To iSirenCnt
bEnrolled = (ByteToHex(sData.Copy(iCnt * 4 - 4, 3)) <> String$(3, "00"))
If bEnrolled Then
$cConfig["siren"][iCnt] = New Collection
$cConfig["siren"][iCnt]["enrolled"] = True
Endif
Next
Endif
' All information gathered, now print it in debug
If $bDebug Then
bLog = False
If $cConfig.Exist("keyfob") Then
bLog = False
WriteDebugLog("Keyfob:")
For iCnt = 1 To iKeyfobCnt
If $cConfig["keyfob"].Exist(iCnt) Then
bLog = True
WriteDebugLog(" " & Format$(iCnt, "00") & ": Enrolled")
Endif
Next
If Not bLog Then WriteDebugLog(" None")
Endif
bLog = False
If $cConfig.Exist("keypad1") Then
bLog = False
WriteDebugLog("1-Way Keypad:")
For iCnt = 1 To iKeypad1Cnt
If $cConfig["keypad1"].Exist(iCnt) Then
bLog = True
WriteDebugLog(" " & Format$(iCnt, "00") & ": Enrolled")
Endif
Next
If Not bLog Then WriteDebugLog(" None")
Endif
bLog = False
WriteDebugLog("2-Way Keypad:")
For iCnt = 1 To iKeypad2Cnt
If $cConfig["keypad2"].Exist(iCnt) Then
bLog = True
WriteDebugLog(" " & Format$(iCnt, "00") & ": Enrolled")
Endif
Next
If Not bLog Then WriteDebugLog(" None")
bLog = False
WriteDebugLog("Siren:")
For iCnt = 1 To iSirenCnt
If $cConfig["siren"].Exist(iCnt) Then
bLog = True
WriteDebugLog(" " & Format$(iCnt, "00") & ": Enrolled")
Endif
Next
If Not bLog Then WriteDebugLog(" None")
Endif
' ------------------------------------------------------
' Retrieve EventLogs for PowerMaster10 or 30
' ------------------------------------------------------
' Currently only reporting, information isn't stored
If $bDebug Then
sData.Clear()
Select $iPanelType
Case &H07
sData = ReadMemoryMap(VMSG_DL_MASTER10_EVENTLOG)
Case &H08
sData = ReadMemoryMap(VMSG_DL_MASTER30_EVENTLOG)
End Select
WriteDebugLog("EventLog:")
If sData.Count Then
' Number of events is in Byte 2 and 1
iEventLog = CInt(sData[2]) * &H100 + sData[1]
WriteDebugLog(" Event Count: " & iEventLog)
' Remove first 3 bytes, then the eventlog are accessible
sData.Extract(0, 3)
For iCnt = 1 To iEventLog
' Format: 00 D5 71 6E 38 00 00 60
sEventLog = sData.Extract(0, 8)
' The timestamp is in Unixtime format
sEventDate = Util.TimeSinceEpoch(Hex$(sEventLog[4], 2) & Hex$(sEventLog[3], 2) & Hex$(sEventLog[2], 2) & Hex$(sEventLog[1], 2), False, True)
iEventZone = sEventLog[6]
iEventType = sEventLog[7]
Try sEventZone = $cConfig["zoneinfo"][iEventZone]["zonename"]
If Error Then sEventZone = ""
sEventType = DisplayLogEvent(iEventType)
WriteDebugLog(" " & sEventDate & " " & sEventType & IIf(iEventZone <> 0 And iEventZone <= 64, ", Zone: " & iEventZone & IIf(sEventZone, " (" & sEventZone & ")", ""), ""))
Next
Else
WriteDebugLog(" No EventLog found for panel")
Endif
Endif
' --- DONE with ReadSettings ---
Finally
' --------------------------------
' Finally done with panel settings
' --------------------------------
' Now send a restore message, this will give the current status
SendMsg_RESTORE()
Catch
WriteLog("ERROR: ProcessSettings '" & Error.Text & "' at '" & Error.Where & "'")
End
'########################################################
' Send PowerMax/Master messages
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Send an ACK upon receive a message, an ACK is never queued
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_ACK(Optional bUseType As Boolean = True)
If bUseType Then
' Depending on the msgtype and/or last byte, we will be sending type-1 or 2 ACK
If $sReceiveData[0] >= &H80 Or If ($sReceiveData[0] < &H10 And $sReceiveData[$sReceiveData.Count - 1] = &H43) Then
SendCommand(VMSG_ACK2.Copy())
Else
SendCommand(VMSG_ACK1.Copy())
Endif
Else
' If no buffer is available, always send type-1 ACK
SendCommand(VMSG_ACK1.Copy())
Endif
If $bDebug Then WriteDebugLog("Acknowledgement")
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Initialize the PowerMax/Master
' Normally if no communication has been estabilished before
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_INIT()
QueueCommand(VMSG_INIT.Copy(), "Initializing PowerMax/Master PowerLink Connection", &H00)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Restore the connection
' Normally if the keepalive timer expired after 60 seconds
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_RESTORE()
QueueCommand(VMSG_RESTORE.Copy(), "Restore PowerMax/Master Connection", &H00)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Auto enroll the PowerMax/Master unit
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_ENROLL()
' Remove anything else from the queue, we need to restart
ClearQueue()
' Send the request, the pin will be added when the command is send
QueueCommand(VMSG_ENROLL.Copy(), "Auto-Enroll of the PowerMax/Master", &H00, "PIN:DownloadCode:4")
' We are doing an auto-enrollment, most likely the download failed. Lets restart the download stage.
If $bDownloadMode Then
If $bDebug Then WriteDebugLog("Resetting download mode to 'Off'")
$bDownloadMode = False
Endif
SendMsg_DL_START()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Start download mode
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_START()
If Not $bDownloadMode Then
$bDownloadMode = True
QueueCommand(VMSG_DL_START.Copy(), "Start Download Mode", &H3C, "PIN:DownloadCode:3")
Else
If $bDebug Then WriteDebugLog("Already in Download Mode?")
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Get al(most) all relevant parameter during the startup
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_GET()
QueueCommand(VMSG_DL_GET.Copy(), "DL: Retrieving Revelant Settings", &H33)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Stop download mode
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_EXIT(Optional bSend As Boolean = False)
If $bDownloadMode Or If bSend Then
QueueCommand(VMSG_DL_EXIT.Copy(), "Exit Download Mode", &H00)
Else
If $bDebug Then WriteDebugLog("Not in Download Mode?")
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Request the panel FW
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_PANELFW()
QueueCommand(VMSG_DL_PANELFW.Copy(), "DL: Panel Firmware", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Request serial & type (not always sent by default)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_SERIAL()
QueueCommand(VMSG_DL_SERIAL.Copy(), "DL: Serialnumber", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Read the names of the zones
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_ZONESTR()
QueueCommand(VMSG_DL_ZONESTR.Copy(), "DL: Zonenames", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Retrieve extra info if this is a PowerMaster
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_MASTER_SIRENKEYPADSZONE()
QueueCommand(VMSG_DL_MASTER_SIRENKEYPADSZONE.Copy(), "DL: PowerMaster Siren/Keypad/Zones", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Retrieve extra eventlog if this is a PowerMaster10
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_MASTER10_EVENTLOG()
QueueCommand(VMSG_DL_MASTER10_EVENTLOG.Copy(), "DL: PowerMaster10 EventLog", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Retrieve extra eventlog if this is a PowerMaster30
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_DL_MASTER30_EVENTLOG()
QueueCommand(VMSG_DL_MASTER30_EVENTLOG.Copy(), "DL: PowerMaster30 EventLog", &H3F)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Request status update
' Only done when we are not in PowerLink mode
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_STATUS()
QueueCommand(VMSG_STATUS.Copy(), "Request Status (None PowerLink Mode)", &HA5)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Request the event logs
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_EVENTLOG()
' The MasterCode is used, and it is on position 4 and 5
QueueCommand(VMSG_EVENTLOG.Copy(), "Request Event Logs", &HA0, "PIN:MasterCode:4")
End
'########################################################
' Handle MemoryMap reads and writes
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Write the 3C and 3F information into our own memory map structure. This contains all the
' information of the PowerMax/Master and will later be processed in ReadSettings
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub WriteMemoryMap(iPage As Integer, iIndex As Integer, sData As Byte[])
Dim dPage As New Byte[]
Dim iFree As Integer
Dim iPMType As Integer
If iPage = &HFF And If iIndex = &HFF Then
iPMType = 1
Else
iPMType = 0
Endif
' Overrule page/index/data if we have an extended message
If iPMType = 1 Then
iPage = sData[1]
iIndex = sData[0]
' Remove page/index and other 2 bytes, unknown usage
sData = sData.Copy(4, sData.Count - 4)
Endif
If $bDebug Then WriteDebugLog("Received Settings " & IIf(iPMType = 0, "Standard", "Extended") & " Page=" & Hex$(iPage, 2) & ", Index=" & Hex$(iIndex, 2) & ", Length=" & sData.Count)
' Create standard/extended placeholders
If Not $cMemoryMap.Count Then
$cMemoryMap[0] = New Collection
$cMemoryMap[1] = New Collection
Endif
' Repeat until we put all the data into the MemoryMap
While sData.Count > 0
If $cMemoryMap[iPMType].Exist(iPage) Then
' Place Byte[] data in a local variable
dPage = $cMemoryMap[iPMType][iPage]
Else
' Create empty page with 255 values
dPage = New Byte[]
dPage.Resize(&H100)
dPage.Fill(&HFF, &H00, &H100)
$cMemoryMap[iPMType][iPage] = dPage
Endif
'WriteDebugLog("### BEGIN ### " & ByteToHex(dPage, True))
' Check if the data fits in the current page
If iIndex + sData.Count <= &H100 Then
' It fits, just copy all in and we are done
dPage.Extract(iIndex, sData.Count)
dPage.Insert(sData, iIndex)
sData.Clear()
Else
' It spans multiple pages, take care of it
iFree = &H100 - iIndex
dPage.Extract(iIndex, iFree)
dPage.Insert(sData.Extract(0, iFree), iIndex)
' Set it to the next page and index to 0
iPage += 1
iIndex = 0
Endif
'WriteDebugLog("### END ### " & ByteToHex(dPage, True))
Wend
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Read data from the memory map and return the relevant bytes
' We will return Null if if the request page/index/length can't be returned
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ReadMemoryMap(aMsg As Byte[]) As Byte[]
Dim aResult As New Byte[]
Dim dPage As New Byte[]
Dim iPage As Integer
Dim iIndex As Integer
Dim iLength As Integer
Dim iTemp As Integer
Dim iPMType As Integer
' The aMsg is in the regular download format, ignore the SubType and only use page, index and length
' NOTE: Length can be more then &HFF bytes, in such we got multiple 3F responses with a "real" download
iPage = aMsg[2]
iIndex = aMsg[1]
iLength = CInt(aMsg[4]) * &H100 + aMsg[3]
If iPage = &HFF And If iIndex = &HFF Then
iPMType = 1
Else
iPMType = 0
Endif
' We got a PowerMaster extended message, overrule page/index/length
If iPMType = 1 Then
iPage = aMsg[7]
iIndex = aMsg[6]
Endif
If Not $cMemoryMap.Count Then
$cMemoryMap[0] = New Collection
$cMemoryMap[1] = New Collection
Endif
While iLength > 0
If Not $cMemoryMap[iPMType].Exist(iPage) Then Return Null
dPage = $cMemoryMap[iPMType][iPage]
If iIndex + iLength <= &H100 Then
aResult.Insert(dPage.Copy(iIndex, iLength))
iLength = 0
Else
' It spans multiple pages, take care of it
iTemp = &H100 - iIndex
aResult.Insert(dPage.Copy(iIndex, iTemp))
' Set it to the next page and index to 0
iPage += 1
iIndex = 0
iLength -= iTemp
Endif
Wend
If aResult.Count >= 1
Return aResult
Else
Return Null
Endif
End
'########################################################
' Handle MsgType section
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=02 - ACK
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType02()
If $bDebug Then WriteDebugLog("Acknowledgement")
' Check if the last message is Download Exit, if this is the case, process the settings
If $cSendLastCommand Then
If $cSendLastCommand.Command[0] = &H0F Then
$bDownloadMode = False
' We received a download exit message, restart timer
$tPowerLinkKeepAlive.Stop
$tPowerLinkKeepAlive = New Timer As "tPowerLinkKeepAlive"
$tPowerLinkKeepAlive.Delay = $iPowerLinkTimeOut
$tPowerLinkKeepAlive.Start
ProcessSettings()
Else If $cSendLastCommand.Command.Count >= 3 And If $cSendLastCommand.Command[0] = &HAB And If $cSendLastCommand.Command[1] = &H0A And If $cSendLastCommand.Command[2] = &H00 Then
' The queue timer
$bQueueDelay = False
Try $tQueueDelay.Stop
$tQueueDelay = New Timer As "tQueueDelay"
$tQueueDelay.Delay = $iSendDelay
$tQueueDelay.Start
Endif
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=06 - Time out
' Timeout message from the PM, most likely we are/were in download mode
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType06()
If $bDebug Then WriteDebugLog("Timeout Received")
SendMsg_DL_EXIT(True)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=08 - Access denied
' Access denied, a wrong pin supplied
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType08()
If $cSendLastCommand Then
If $cSendLastCommand.Command[0] = &H24 Then
If $bDebug Then WriteDebugLog("Access Denied (will try to enroll)")
SendMsg_ENROLL()
Else
If $bDebug Then WriteDebugLog("Access Denied (will not try to enroll), previous packet: " & Hex$($cSendLastCommand.Command[0], 2))
Endif
Else
If $bDebug Then WriteDebugLog("Access Denied")
Endif
' If LastMsgType And &HF0 = &HA0 Then
' Wrong pin enroll, eventlog, arm, bypass
' Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=25 - Download retry
' Unit is not ready to enter download mode
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType25()
Dim iDelay As Integer
' Format: <MsgType> <?> <?> <delay in sec>
iDelay = $sReceiveData[3]
If $bDebug Then WriteDebugLog("Download Retry, have to wait " & iDelay & " seconds")
' Restart the queue timer
$bQueueDelay = True
$tQueueDelay.Stop
$tQueueDelay = New Timer As "tQueueDelay"
$tQueueDelay.Delay = iDelay * 1000
$tQueueDelay.Start
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=33 - Settings
' Message send after a VMSG_START. We will store the information in an internal array/collection
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType33()
Dim iPage As Integer
Dim iIndex As Integer
If $sReceiveData.Count <> 11 Then
If $bDebug Then WriteDebugLog("ERROR: MSGTYPE=0x33 Expected=11, Received=" & $sReceiveData.Count)
Return
Endif
' Format is: <MsgType> <index> <page> <data 8x bytes>
' Extract Page and Index information
iIndex = $sReceiveData[1]
iPage = $sReceiveData[2]
' Write to memory map structure, but remove the first 3 bytes from the data
WriteMemoryMap(iPage, iIndex, $sReceiveData.Copy(3, $sReceiveData.Count - 3))
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=3C - Panel information
' Direct message after we do a download start. Contains the paneltype information
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType3C()
' The panel information is in 5 & 6
' 6=PanelType e.g. PowerMax, PowerMaster
' 5=Sub model type of the panel - just informational
$iPanelType = $sReceiveData[6]
$iModelType = $sReceiveData[5]
$bPowerMaster = ($iPanelType >= 7)
If $bDebug Then WriteDebugLog("PanelType=" & DisplayPanelType() & ", Model=" & DisplayPanelModel())
' We got a first response, now we can continue enrollment the PowerMax/Master PowerLink
PowerLinkEnrolled()
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=3F - Download information
' Multiple 3F can follow eachother, if we request more then &HFF bytes
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgType3F()
Dim iPage As Integer
Dim iIndex As Integer
Dim iLength As Integer
' Format is normally: <MsgType> <index> <page> <length> <data ...>
' If the <index> <page> = FF, then it is an additional PowerMaster MemoryMap
iIndex = $sReceiveData[1]
iPage = $sReceiveData[2]
iLength = $sReceiveData[3]
' Check length and data-length
If iLength <> ($sReceiveData.Count - 4) Then
If $bDebug Then WriteDebugLog("ERROR: Type=3F has an invalid length, Received: " & $sReceiveData.Count - 4 & ", Expected: " & iLength)
Endif
' Write to memory map structure, but remove the first 4 bytes (3F/index/page/length) from the data
WriteMemoryMap(iPage, iIndex, $sReceiveData.Copy(4, $sReceiveData.Count - 4))
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=A0 - Event Log
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgTypeA0()
Dim iSec, iMin, iHour, iDay, iMonth, iYear As Integer
Dim iEventZone As Integer
Dim iLogEvent As Integer
Dim sEventZone As String
Dim sLogEvent As String
Dim sDate As String
Dim dDate As Date
' Check for the first entry, it only contains the number of events
If $sReceiveData[2] = &H01 Then
If $bDebug Then WriteDebugLog("Eventlog received")
$cSendLastCommand.ReceiveCnt = $sReceiveData[1] - 1
$cSendLastCommand.ReceiveCntFixed = $sReceiveData[1] - 1
Else
If $bPowerMaster Then
' Set sec, min, etc to 1-Jan-2000 00:00:00
iSec = 0
iMin = 0
iHour = 0
iDay = 1
iMonth = 1
iYear = 2000
' timestamp looks unixtime
sDate = "&H" & Hex$($sReceiveData[7], 2) & Hex$($sReceiveData[6], 2) & Hex$($sReceiveData[5], 2) & Hex$($sReceiveData[4], 2)
Try dDate = DateAdd(CDate("1/1/1970"), gb.Second, Val(sDate))
If Not Error Then
iSec = Second(dDate)
iMin = Minute(dDate)
iHour = Hour(dDate)
iDay = Day(dDate)
iMonth = Month(dDate)
iYear = Year(dDate)
Endif
Else
iSec = $sReceiveData[3]
iMin = $sReceiveData[4]
iHour = $sReceiveData[5]
iDay = $sReceiveData[6]
iMonth = $sReceiveData[7]
iYear = CInt($sReceiveData[8]) + 2000
Endif
iEventZone = $sReceiveData[9]
iLogEvent = $sReceiveData[10]
Try sEventZone = $cConfig["zonenname"][iEventZone]
If Error Then sEventZone = ""
sLogEvent = DisplayLogEvent(iLogEvent)
If $bDebug Then
sDate = CStr(iYear) & "/" & Format$(iMonth, "00") & "/" & Format$(iDay, "00") & " " & Format$(iHour, "00") & ":" & Format$(iMin, "00") & ":" & Format$(iSec, "00")
WriteDebugLog(" " & sDate & " " & sLogEvent & IIf(iEventZone <> 0 And iEventZone <= 64, ", Zone: " & iEventZone & IIf(sEventZone, " (" & sEventZone & ")", ""), ""))
Endif
Endif
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' A5 = Handle Status Messages
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgTypeA5()
Dim sLog As String
Dim sArm As String
Dim iDeviceId As Integer
Dim iCnt As Integer
Dim bByte As Byte
Dim bBit As Byte
' Format: <msgtype> <total rows> <row> <data 8x bytes>
Select Case $sReceiveData[2]
Case &H01 ' Log Event Print
If $bDebug Then WriteDebugLog("Log Event Print")
Case &H02 ' Status message zones
If $bDebug Then
WriteDebugLog("Status and Battery Message")
WriteDebugLog("Status Zones 01-08: " & DisplayZoneBin($sReceiveData[3]))
WriteDebugLog("Status Zones 09-16: " & DisplayZoneBin($sReceiveData[4]))
WriteDebugLog("Status Zones 17-24: " & DisplayZoneBin($sReceiveData[5]))
WriteDebugLog("Status Zones 25-30: " & DisplayZoneBin($sReceiveData[6]))
WriteDebugLog("Battery Zones 01-08: " & DisplayZoneBin($sReceiveData[7]))
WriteDebugLog("Battery Zones 09-16: " & DisplayZoneBin($sReceiveData[8]))
WriteDebugLog("Battery Zones 17-24: " & DisplayZoneBin($sReceiveData[9]))
WriteDebugLog("Battery Zones 25-30: " & DisplayZoneBin($sReceiveData[10]))
Endif
If $cConfig And If Not $cConfig.Exist("zoneinfo") Then
WriteDebugLog("Zone status message received, but we did or could not download this info from the panel")
Endif
' Send an "On" when the zone status is 1. This doesn't work for PowerMaster PIRs, but seems to work for magnets
For iCnt = 1 To 30
bByte = (iCnt - 1) / 8
bBit = (iCnt - 1) Mod 8
' Only do it, when the zone information is known
If $cConfig And If $cConfig.Exist("zoneinfo") And If $cConfig["zoneinfo"].Exist(iCnt) And If $cConfig["zoneinfo"][iCnt]["sensortype"] Then
' Only process magnets if we are a PowerMaster
If $bPowerMaster Then
If $cConfig["zoneinfo"][iCnt]["sensortype"] <> "Magnet" Then Continue
Endif
' Check the zone status
If ($sReceiveData[3 + bByte] And CInt(2 ^ bBit)) <> $cConfig["zoneinfo"][iCnt]["tripstatus"] Then
' Try to find it, it will autocreate the device if needed
iDeviceId = Devices.Find(Instance, "Z" & Format$(iCnt, "00"), InterfaceId, $cConfig["zoneinfo"][iCnt]["autocreate"])
If ($sReceiveData[3 + bByte] And CInt(2 ^ bBit)) = 0 Then
If iDeviceId Then Devices.ValueUpdate(iDeviceId, 1, "Off")
Else
If iDeviceId Then
Devices.ValueUpdate(iDeviceId, 1, "On")
Endif
Endif
$cConfig["zoneinfo"][iCnt]["tripstatus"] = ($sReceiveData[3 + bByte] And CInt(2 ^ bBit))
Endif
' Check the battery status
If ($sReceiveData[7 + bByte] And CInt(2 ^ bBit)) <> $cConfig["zoneinfo"][iCnt]["batterystatus"] Then
' Try to find it, it will autocreate the device if needed
iDeviceId = Devices.Find(Instance, "Z" & Format$(iCnt, "00"), InterfaceId, $cConfig["zoneinfo"][iCnt]["autocreate"])
If ($sReceiveData[7 + bByte] And CInt(2 ^ bBit)) = 0 Then
If iDeviceId Then Devices.Battery(iDeviceId, "Low")
Else
If iDeviceId Then Devices.Battery(iDeviceId, "OK")
Endif
' Store latest battery status, we don't want to generate too many valueupdates
$cConfig["zoneinfo"][iCnt]["batterystatus"] = ($sReceiveData[7 + bByte] And CInt(2 ^ bBit))
Endif
Else
'WriteLog("ERROR: Zone information for zone '" & iCnt & "' is unknown, possible the Plugin couldn't retrieve the information from the panel?")
Endif
Next
Case &H03 ' Tamper event
If $bDebug Then WriteDebugLog("Tamper Event")
Case &H04 ' Zone event
Select Case $sReceiveData[3] ' System Status
Case &H00
sLog = "Disarmed"
sArm = "Disarmed"
Case &H01
sLog = "Exit Delay, Arming Home"
sArm = "Disarmed"
Case &H02
sLog = "Exit Delay, Arming Away"
sArm = "Disarmed"
Case &H03
sLog = "Entry Delay"
sArm = "Armed"
Case &H04
sLog = "Armed Home"
sArm = "Armed"
Case &H05
sLog = "Armed Away"
sArm = "Armed"
Case &H06
sLog = "User Test"
sArm = "Disarmed"
Case &H07
sLog = "Downloading"
sArm = "Disarmed"
Case &H08
sLog = "Programming"
sArm = "Disarmed"
Case &H09
sLog = "Installer"
sArm = "Disarmed"
Case &H0A
sLog = "Home Bypass"
sArm = "Armed"
Case &H0B
sLog = "Away Bypass"
sArm = "Armed"
Case &H0C
sLog = "Ready"
sArm = "Disarmed"
Case &H0D
sLog = "Not Ready"
sArm = "Disarmed"
Case &H10
sLog = "Disarm"
sArm = "Disarmed"
Case &H11
sLog = "Exit Delay"
sArm = "Disarmed"
Case &H12
sLog = "Exit Delay"
sArm = "Disarmed"
Case &H13
sLog = "Entry Delay"
sArm = "Armed"
Case &H14
sLog = "Armed Home Instant"
sArm = "Armed"
Case &H15
sLog = "Armed Away Instant"
sArm = "Armed"
Default
sLog = "Unknown (" & Hex$($sReceiveData[3], 2) & ")"
sArm = "Disarmed"
End Select
If $bDebug Then WriteDebugLog("Zone Event " & sLog)
iDeviceId = Devices.Find(Instance, "Panel", InterfaceId, IIf($bPowerMaster, "Visonic PowerMaster", "Visonic PowerMax"))
If iDeviceId Then
Devices.ValueUpdate(iDeviceId, 1, sArm)
Devices.ValueUpdate(iDeviceId, 2, sLog)
Endif
If ($sReceiveData[4] And 1) Then
If $bDebug Then WriteDebugLog("Bit 0 set, Ready")
Endif
If ($sReceiveData[4] And 2) Then
If $bDebug Then WriteDebugLog("Bit 1 set, Alert in Memory")
Endif
If ($sReceiveData[4] And 4) Then
If $bDebug Then WriteDebugLog("Bit 2 set, Trouble!")
Endif
If ($sReceiveData[4] And 8) Then
If $bDebug Then WriteDebugLog("Bit 3 set, Bypass")
Endif
If ($sReceiveData[4] And 16) Then
If $bDebug Then WriteDebugLog("Bit 4 set, Last 10 seconds of entry/exit")
Endif
If ($sReceiveData[4] And 32) Then
Select Case $sReceiveData[6]
Case &H00 ' None
sLog = "No Zone Event"
Case &H01 ' Tamper Alarm
sLog = "Tamper Alarm"
Case &H02 ' Tamper Restore
sLog = "Tamper Alarm Restored"
Case &H03 ' Open
sLog = "Zone Open"
Case &H04 ' Closed
sLog = "Zone Closed"
Case &H05 ' Violated (Motion)
sLog = "Zone Violation, Motion Detected"
Case &H06 ' Panic Alarm
sLog = "Zone Panic Alarm"
Case &H07 ' RF Jamming
sLog = "RF Jamming Detected"
Case &H08 ' Tamper Open
sLog = "Zone Tamper Alarm Open"
Case &H09 ' Communication Failure
sLog = "Zone Communication Failure"
Case &H0A ' Line Failure
sLog = "Zone Line Failure"
Case &H0B ' Fuse
sLog = "Zone Fuse"
Case &H0C ' Not Active
sLog = "Zone Not Active"
Case &H0D ' Low Battery
sLog = "Zone Low Battery"
Case &H0E ' AC Failure
sLog = "Zone AC Failure"
Case &H0F ' Fire Alarm
sLog = "Zone Fire Alarm"
Case &H10 ' Emergency
sLog = "Zone Emergency"
Case &H11 ' Siren Tamper
sLog = "Zone Siren Tamper"
Case &H12 ' Siren Tamper Restore
sLog = "Zone Siren Tamper Restored"
Case &H13 ' Siren Low Battery
sLog = "Zone Siren Low Battery"
Case &H14 ' Siren AC Fail
sLog = "Zone Siren AC Failure"
Default
sLog = "Unknown (" & Hex$($sReceiveData[6]) & ")"
End Select
If $bDebug Then WriteDebugLog("Bit 5 set, Zone Event")
If $bDebug Then WriteDebugLog("Zone: " & $sReceiveData[5] & ", " & sLog)
Endif
If ($sReceiveData[4] And 64) Then
If $bDebug Then WriteDebugLog("Bit 6 set, Status Changed")
Endif
If ($sReceiveData[4] And 128) Then
If $bDebug Then WriteDebugLog("Bit 7 set, Alarm Event")
Endif
' Trigger a zone event will only work for PowerMax and not for PowerMaster
If Not $bPowerMaster Then
'If $sReceiveData[6] = 3 Or If $sReceiveData[6] = 4 Or If $sReceiveData[6] = 5 Then
If $sReceiveData[6] = 5 Then
If $cConfig And If $cConfig.Exist("zoneinfo") And If $cConfig["zoneinfo"].Exist($sReceiveData[5]) And If $cConfig["zoneinfo"][$sReceiveData[5]]["sensortype"] Then
' Try to find it, it will autocreate the device if needed
iDeviceId = Devices.Find(Instance, "Z" & Format$($sReceiveData[5], "00"), InterfaceId, $cConfig["zoneinfo"][$sReceiveData[5]]["autocreate"])
If iDeviceId Then Devices.ValueUpdate(iDeviceId, 1, "On")
If $cConfig["zoneinfo"][$sReceiveData[5]]["sensortype"] = "Motion" Then
EnableTripTimer("Z" & Format$($sReceiveData[5], "00"))
Endif
Else
WriteLog("ERROR: Zone information for zone '" & $sReceiveData[5] & "' is unknown, possible the Plugin couldn't retrieve the information from the panel?")
Endif
Endif
Endif
Case &H06 ' Status message enrolled/bypassed
If $bDebug Then
WriteDebugLog("Enrollment and Bypass Message")
WriteDebugLog("Enrolled Zones 01-08: " & DisplayZoneBin($sReceiveData[3]))
WriteDebugLog("Enrolled Zones 09-16: " & DisplayZoneBin($sReceiveData[4]))
WriteDebugLog("Enrolled Zones 17-24: " & DisplayZoneBin($sReceiveData[5]))
WriteDebugLog("Enrolled Zones 25-30: " & DisplayZoneBin($sReceiveData[6]))
WriteDebugLog("Bypassed Zones 01-08: " & DisplayZoneBin($sReceiveData[7]))
WriteDebugLog("Bypassed Zones 09-16: " & DisplayZoneBin($sReceiveData[8]))
WriteDebugLog("Bypassed Zones 17-24: " & DisplayZoneBin($sReceiveData[9]))
WriteDebugLog("Bypassed Zones 25-30: " & DisplayZoneBin($sReceiveData[10]))
Endif
Default
If $bDebug Then WriteDebugLog("Unknown A5 event: " & Hex$($sReceiveData[2], 2))
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=A7 - Panel Status Change
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgTypeA7()
If $bDebug Then WriteDebugLog("Panel Status Change")
If $bDebug Then WriteDebugLog("Zone/User: " & DisplayZoneUser($sReceiveData[3]))
If $bDebug Then WriteDebugLog("Log Event: " & DisplayLogEvent($sReceiveData[4]))
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' MsgType=AB - PowerLink message
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgTypeAB()
Select Case $sReceiveData[1]
Case &H03 ' PowerLink keep-alive
If $bDebug Then WriteDebugLog("PowerLink Keep-Alive (" & ByteToString($sReceiveData.Copy(5, 4)) & ")")
$bDownloadMode = False
' We received a keep-alive, restart timer
$tPowerLinkKeepAlive.Stop
$tPowerLinkKeepAlive = New Timer As "tPowerLinkKeepAlive"
$tPowerLinkKeepAlive.Delay = $iPowerLinkTimeOut
$tPowerLinkKeepAlive.Start
Case &H05 ' Phone message
Select $sReceiveData[3]
Case &H01
If $bDebug Then WriteDebugLog("PowerLink Phone: Calling User")
Case &H02
If $bDebug Then WriteDebugLog("PowerLink Phone: User Acknowledged")
Default
If $bDebug Then WriteDebugLog("PowerLink Phone: Unknown Action " & Hex$($sReceiveData[3], 2))
End Select
Case &H0A ' PowerLink most likely wants to auto-enroll
If $sReceiveData[3] = &H01 Then
' Send the auto-enroll message with the new download pin/code
SendMsg_ENROLL()
' Restart the timer
$tPowerLinkKeepAlive.Stop
$tPowerLinkKeepAlive = New Timer As "tPowerLinkKeepAlive"
$tPowerLinkKeepAlive.Delay = $iPowerLinkTimeOut
$tPowerLinkKeepAlive.Start
Endif
Default
If $bDebug Then WriteDebugLog("Unknown AB Message: " & Hex$($sReceiveData[1], 2))
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' B0 = Handle PowerMaster message
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub Handle_MsgTypeB0()
Dim iDiff As Integer
Dim iCnt As Integer
Dim iDeviceId As Integer
If $bDebug Then WriteDebugLog("PowerMaster Message")
' Set our panel to PowerMaster10 if we run in forced mode
If $bForceStandardMode Then
$iPanelType = 7
$bPowerMaster = True
Endif
' Format: <Type> <SubType> <Length> <Data>
' B0 03 39 06 - Movement
' B0 03 39 - Normally send when movement is detect
If $sReceiveData[1] = &H03 And If $sReceiveData[2] = &H39 Then
' Send a new B0 status message, to retrieve zone information
QueueCommand(VMSG_PMASTER_STAT1, "PowerMaster Msg1", &HB0)
Endif
' B0 03 04 - Zone information
If $sReceiveData[1] = &H03 And If $sReceiveData[2] = &H04 Then
'
' Determine difference between now and last B0 03 04 packet
Try iDiff = DateDiff($dB00304LastPacket, Now(), gb.Second)
If Error Or If iDiff > 30 Then
' Reset the information
$bB00304ZoneNumber = 0
$dB00304FirstPacket = Now()
' Copy the zone information
$bB00304ZoneInfo = $sReceiveData.Copy(8, $sReceiveData[7])
Else
' Only continue if we didn't notice a zone change
If Not $bB00304ZoneNumber Then
' Determine different of first packet and now, has to be within 5 seconds
Try iDiff = DateDiff($dB00304FirstPacket, Now(), gb.Second)
If Error Or If iDiff <= 5 Then
For iCnt = 1 To $sReceiveData[7]
If $sReceiveData[7 + iCnt] <> $bB00304ZoneInfo[iCnt - 1] Then
' Check if the zone exists and it has to be a PIR
If $cConfig.Exist("zoneinfo") And If $cConfig["zoneinfo"].Exist(iCnt) And If $cConfig["zoneinfo"][iCnt]["sensortype"] = "Motion" Then
$bB00304ZoneNumber = iCnt
iDeviceId = Devices.Find(Instance, "Z" & Format$(iCnt, "00"), InterfaceId, $cConfig["zoneinfo"][iCnt]["autocreate"])
If iDeviceId Then
Devices.ValueUpdate(iDeviceId, 1, "On")
EnableTripTimer("Z" & Format$(iCnt, "00"))
Endif
If $bDebug Then WriteDebugLog("PowerMaster PIR zone " & Format$($bB00304ZoneNumber, "00") & ", Motion detected")
Break
Endif
Endif
Next
Endif
Endif
Endif
' Set last packet timestamp
$dB00304LastPacket = Now()
Endif
' B0 03 18 - Open/Close information
If $sReceiveData[1] = &H03 And If $sReceiveData[2] = &H18 Then
Endif
End
'########################################################
' Trip timers functions
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Enable or restart the trip timer
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub EnableTripTimer(sZone As String)
Dim oObject As Object
' First try to find running timers and remove it
For Each oObject In $oTripTimers
If oObject.Tag = sZone Then
$oTripTimers.Remove($oTripTimers.Find(oObject))
Break
Endif
Next
' Start the timer (again)
tTripTimer = New CTimerGeneric As "tTripTimer"
$oTripTimers.Add(tTripTimer)
tTripTimer.Delay = $iMotionTimeout * 1000
tTripTimer.Start
tTripTimer.Tag = sZone
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Trip timer expired, reset the zone to Off
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Public Sub tTripTimer_Timer()
Dim iDeviceId As Integer
Dim sZone As String
Dim iZone As Integer
' Retrieve the zone information, stop and remove the timer
sZone = Last.Tag
Try iZone = CInt(Mid$(sZone, 2))
Last.Stop
$oTripTimers.Remove($oTripTimers.Find(Last))
If $cConfig And If $cConfig["zoneinfo"].Exist(iZone) And If $cConfig["zoneinfo"][iZone]["sensortype"] Then
If $bDebug Then WriteDebugLog("Timer expired for zone " & Format$(iZone, "00") & ", resetting to 'Off'")
' Try to find it, it will autocreate the device if needed
iDeviceId = Devices.Find(Instance, sZone, InterfaceId, $cConfig["zoneinfo"][iZone]["autocreate"])
If iDeviceId Then Devices.ValueUpdate(iDeviceId, 1, "Off")
Else
WriteLog("ERROR: Zone information for zone '" & iZone & "' is unknown, possible the Plugin couldn't retrieve the information from the panel?")
Endif
End
'########################################################
' Display functions
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Convert an array of bytes to a string, also remove space at the end of the string
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ByteToString(aStr As Byte[]) As String
Dim bVal As Byte
Dim sStr As String
For Each bVal In aStr
' If the value is &HFF, most likely we hit end of the string and we should stop processing
If bVal = &HFF Then Return RTrim$(sStr)
' Append byte as character to string
' TODO: None-Ascii characters
sStr &= Chr$(bVal)
Next
Return RTrim$(sStr)
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Convert an array of bytes to a hex string
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub ByteToHex(aStr As Byte[], Optional bSpace As Boolean = False) As String
Dim bVal As Byte
Dim sStr As String
For Each bVal In aStr
' Append byte as character to string
If bSpace And If sStr Then sStr &= " "
sStr &= Hex$(bVal, 2)
Next
Return sStr
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Short PanelType name
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayPanelType() As String
Select $iPanelType
Case &H00
Return "PowerMax"
Case &H01
Return "PowerMax+"
Case &H02
Return "PowerMax Pro"
Case &H03
Return "PowerMax Complete"
Case &H04
Return "PowerMax Pro Part"
Case &H05
Return "PowerMax Complete Part"
Case &H06
Return "PowerMax Express"
Case &H07
Return "PowerMaster10"
Case &H08
Return "PowerMaster30"
Default
Return "Unknown"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display the panels model name
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayPanelModel() As String
Select ((CInt($iPanelType) * &H100) + $iModelType)
Case &H0000
Return "PowerMax"
Case &H0001
Return "PowerMax LT"
Case &H0004
Return "PowerMax A"
Case &H0005
Return "PowerMax"
Case &H0006
Return "PowerMax LT"
Case &H0009
Return "PowerMax B"
Case &H000A
Return "PowerMax A"
Case &H000B
Return "PowerMax"
Case &H000C
Return "PowerMax LT"
Case &H000F
Return "PowerMax B"
Case &H0014
Return "PowerMax A"
Case &H0015
Return "PowerMax"
Case &H0016
Return "PowerMax"
Case &H0017
Return "PowerArt"
Case &H0018
Return "PowerMax SC"
Case &H0019
Return "PowerMax SK"
Case &H001A
Return "PowerMax SV"
Case &H001B
Return "PowerMax T"
Case &H001E
Return "PowerMax WSS"
Case &H001F
Return "PowerMax Smith"
Case &H0100
Return "PowerMax+"
Case &H0103
Return "PowerMax+ UK (3)"
Case &H0104
Return "PowerMax+ JP"
Case &H0106
Return "PowerMax+ CTA"
Case &H0108
Return "PowerMax+"
Case &H010a
Return "PowerMax+ SH"
Case &H010b
Return "PowerMax+ CF"
Case &H0112
Return "PowerMax+ WSS"
Case &H0113
Return "PowerMax+ 2INST"
Case &H0114
Return "PowerMax+ HL"
Case &H0115
Return "PowerMax+ UK"
Case &H0116
Return "PowerMax+ 2INST3"
Case &H0118
Return "PowerMax+ CF"
Case &H0119
Return "PowerMax+ 2INST"
Case &H011A
Return "PowerMax+"
Case &H011C
Return "PowerMax+ WSS"
Case &H011D
Return "PowerMax+ UK"
Case &H0120
Return "PowerMax+ 2INST33"
Case &H0121
Return "PowerMax+"
Case &H0122
Return "PowerMax+ CF"
Case &H0124
Return "PowerMax+ UK"
Case &H0127
Return "PowerMax+ 2INST_MONITOR"
Case &H0128
Return "PowerMax+ KeyOnOff"
Case &H0129
Return "PowerMax+ 2INST_MONITOR"
Case &H012A
Return "PowerMax+ 2INST_MONITOR42"
Case &H012B
Return "PowerMax+ 2INST33"
Case &H012C
Return "PowerMax+ One Inst_1_44_0"
Case &H012D
Return "PowerMax+ CF_1_45_0"
Case &H012E
Return "PowerMax+ SA_1_46"
Case &H012F
Return "PowerMax+ UK_1_47"
Case &H0130
Return "PowerMax+ SA UK_1_48"
Case &H0132
Return "PowerMax+ KeyOnOff 1_50"
Case &H0201
Return "PowerMax Pro"
Case &H0202
Return "PowerMax Pro-Nuon"
Case &H0204
Return "PowerMax Pro-PortugalTelecom"
Case &H020a
Return "PowerMax Pro-PortugalTelecom2"
Case &H020c
Return "PowerMax HW-V9 Pro"
Case &H020d
Return "PowerMax ProSms"
Case &H0214
Return "PowerMax Pro-PortugalTelecom_4_5_02"
Case &H0216
Return "PowerMax HW-V9_4_5_02 Pro"
Case &H0217
Return "PowerMax ProSms_4_5_02"
Case &H0218
Return "PowerMax UK_DD243_4_5_02 Pro M"
Case &H021B
Return "PowerMax Pro-Part2__2_27"
Case &H0223
Return "PowerMax Pro Bell-Canada"
Case &H0301
Return "PowerMax Complete"
Case &H0302
Return "PowerMax Complete_NV"
Case &H0303
Return "PowerMax Complete-PortugalTelecom"
Case &H0307
Return "PowerMax Complete_1_0_07"
Case &H0308
Return "PowerMax Complete_NV_1_0_07"
Case &H030A
Return "PowerMax Complete_UK_DD243_1_1_03"
Case &H030B
Return "PowerMax Complete_COUNTERFORCE_1_0_06"
Case &H0401
Return "PowerMax Pro-Part"
Case &H0402
Return "PowerMax Pro-Part CellAdaptor"
Case &H0405
Return "PowerMax Pro-Part_5_0_08"
Case &H0406
Return "PowerMax Pro-Part CellAdaptor_5_2_04"
Case &H0407
Return "PowerMax Pro-Part KeyOnOff_5_0_08"
Case &H0408
Return "PowerMax UK Pro-Part_5_0_08"
Case &H0409
Return "PowerMax SectorUK Pro-Part_5_0_08"
Case &H040A
Return "PowerMax Pro-Part CP1 4_10"
Case &H040C
Return "PowerMax Pro-Part_Cell_key_4_12"
Case &H040D
Return "PowerMax Pro-Part UK 4_13"
Case &H040E
Return "PowerMax SectorUK Pro-Part_4_14"
Case &H040F
Return "PowerMax Pro-Part UK 4_15"
Case &H0410
Return "PowerMax Pro-Part CP1 4_16"
Case &H0411
Return "PowerMax NUON key 4_17"
Case &H0433
Return "PowerMax Pro-Part2__4_51"
Case &H0434
Return "PowerMax UK Pro-Part2__4_52"
Case &H0436
Return "PowerMax Pro-Part2__4_54"
Case &H0437
Return "PowerMax Pro-Part2__4_55 (CP_01)"
Case &H0438
Return "PowerMax Pro-Part2__4_56"
Case &H0439
Return "PowerMax Pro-Part2__4_57 (NUON)"
Case &H043a
Return "PowerMax Pro 4_58"
Case &H043c
Return "PowerMax Pro 4_60"
Case &H043e
Return "PowerMax Pro-Part2__4_62"
Case &H0440
Return "PowerMax Pro-Part2__4_64"
Case &H0442
Return "PowerMax 4_66"
Case &H0443
Return "PowerMax Pro 4_67"
Case &H0444
Return "PowerMax Pro 4_68"
Case &H0445
Return "PowerMax Pro 4_69"
Case &H0446
Return "PowerMax Pro-Part2__4_70"
Case &H0447
Return "PowerMax 4_71"
Case &H0449
Return "PowerMax 4_73"
Case &H044b
Return "PowerMax Pro-Part2__4_75"
Case &H0451
Return "PowerMax Pro 4_81"
Case &H0452
Return "PowerMax Pro 4_82"
Case &H0454
Return "PowerMax 4_84"
Case &H0455
Return "PowerMax 4_85"
Case &H0456
Return "PowerMax 4_86"
Case &H0503
Return "PowerMax UK Complete partition 1_5_00"
Case &H050a
Return "PowerMax Complete partition GPRS"
Case &H050b
Return "PowerMax Complete partition NV GPRS"
Case &H050c
Return "PowerMax Complete partition GPRS NO-BBA"
Case &H050d
Return "PowerMax Complete partition NV GPRS NO-BBA"
Case &H050e
Return "PowerMax Complete part. GPRS NO-BBA UK_5_14"
Case &H0511
Return "PowerMax Pro-Part CP1 GPRS 5_17"
Case &H0512
Return "PowerMax Complete part. BBA UK_5_18"
Case &H0533
Return "PowerMax Complete part2 5_51"
Case &H0534
Return "PowerMax Complete part2 5_52 (UK)"
Case &H0536
Return "PowerMax Complete 5_54 (GR)"
Case &H0537
Return "PowerMax Complete 5_55"
Case &H053A
Return "PowerMax Complete 5_58 (PT)"
Case &H053B
Return "PowerMax Complete part2 5_59 (NV)"
Case &H053C
Return "PowerMax Complete 5_60"
Case &H053E
Return "PowerMax Complete 5_62"
Case &H053F
Return "PowerMax Complete part2 5_63"
Case &H0540
Return "PowerMax Complete 5_64"
Case &H0541
Return "PowerMax Complete 5_65"
Case &H0543
Return "PowerMax Complete 5_67"
Case &H0544
Return "PowerMax Complete 5_68"
Case &H0545
Return "PowerMax Complete 5_69"
Case &H0546
Return "PowerMax Complete 5_70"
Case &H0547
Return "PowerMax Complete 5_71"
Case &H0549
Return "PowerMax Complete 5_73"
Case &H054B
Return "PowerMax Complete 5_75"
Case &H054F
Return "PowerMax Complete 5_79"
Case &H0601
Return "PowerMax Express"
Case &H0603
Return "PowerMax Express CP 01"
Case &H0605
Return "PowerMax Express OEM 6_5"
Case &H0607
Return "PowerMax Express BBA 6_7"
Case &H0608
Return "PowerMax Express CP 01 BBA 6_8"
Case &H0609
Return "PowerMax Express OEM1 BBA 6_9"
Case &H060B
Return "PowerMax Express BBA 6_11"
Case &H0633
Return "PowerMax Express 6_51"
Case &H063B
Return "PowerMax Express 6_59"
Case &H063D
Return "PowerMax Express 6_61"
Case &H063E
Return "PowerMax Express 6_62 (UK)"
Case &H0645
Return "PowerMax Express 6_69"
Case &H0647
Return "PowerMax Express 6_71"
Case &H0648
Return "PowerMax Express 6_72"
Case &H0649
Return "PowerMax Express 6_73"
Case &H064A
Return "PowerMax Activa 6_74"
Case &H064C
Return "PowerMax Express 6_76"
Case &H064D
Return "PowerMax Express 6_77"
Case &H064E
Return "PowerMax Express 6_78"
Case &H064F
Return "PowerMax Secure 6_79"
Case &H0650
Return "PowerMax Express 6_80"
Case &H0650
Return "PowerMax Express part2 M 6_80"
Case &H0651
Return "PowerMax Express 6_81"
Case &H0652
Return "PowerMax Express 6_82"
Case &H0653
Return "PowerMax Express 6_83"
Case &H0654
Return "PowerMax 6_84"
Case &H0655
Return "PowerMax 6_85"
Case &H0658
Return "PowerMax 6_88"
Case &H0659
Return "PowerMax 6_89"
Case &H065A
Return "PowerMax 6_90"
Case &H065B
Return "PowerMax 6_91"
Case &H0701
Return "PowerMax PowerCode-G 7_1"
Case &H0702
Return "PowerMax PowerCode-G 7_2"
Case &H0704
Return "PowerMaster10 7_4"
Case &H0705
Return "PowerMaster10 7_05"
Case &H0707
Return "PowerMaster10 7_07"
Case &H070C
Return "PowerMaster10 7_12"
Case &H070F
Return "PowerMaster10 7_15"
Case &H0710
Return "PowerMaster10 7_16"
Case &H0711
Return "PowerMaster10 7_17"
Case &H0712
Return "PowerMaster10 7_18"
Case &H0713
Return "PowerMaster10 7_19"
Case &H0735
Return "PowerMaster10 7_53"
Case &H0802
Return "PowerMax Complete PowerCode-G 8_2"
Case &H0803
Return "PowerMaster30 8_3"
Case &H080F
Return "PowerMaster30 8_15"
Case &H0810
Return "PowerMaster30 8_16"
Case &H0812
Return "PowerMaster30 8_18"
Case &H0813
Return "PowerMaster30 8_19"
Case &H0815
Return "PowerMaster30 8_21"
Default
Return "Unknown (" & Hex$((CInt($iPanelType) * &H100) + $iModelType, 4) & ")"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Zone/User
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayZoneUser(bType As Byte) As String
Select bType
Case &H00
Return "System"
Case &H01
Return "Zone 1"
Case &H02
Return "Zone 2"
Case &H03
Return "Zone 3"
Case &H04
Return "Zone 4"
Case &H05
Return "Zone 5"
Case &H06
Return "Zone 6"
Case &H07
Return "Zone 7"
Case &H08
Return "Zone 8"
Case &H09
Return "Zone 9"
Case &H0A
Return "Zone 10"
Case &H0B
Return "Zone 11"
Case &H0C
Return "Zone 12"
Case &H0D
Return "Zone 13"
Case &H0E
Return "Zone 14"
Case &H0F
Return "Zone 15"
Case &H10
Return "Zone 16"
Case &H11
Return "Zone 17"
Case &H12
Return "Zone 18"
Case &H13
Return "Zone 19"
Case &H14
Return "Zone 20"
Case &H15
Return "Zone 21"
Case &H16
Return "Zone 22"
Case &H17
Return "Zone 23"
Case &H18
Return "Zone 24"
Case &H19
Return "Zone 25"
Case &H1A
Return "Zone 26"
Case &H1B
Return "Zone 27"
Case &H1C
Return "Zone 28"
Case &H1D
Return "Zone 29"
Case &H1E
Return "Zone 30"
Case &H1F
Return "Keyfob1"
Case &H20
Return "Keyfob2"
Case &H21
Return "Keyfob3"
Case &H22
Return "Keyfob4"
Case &H23
Return "Keyfob5"
Case &H24
Return "Keyfob6"
Case &H25
Return "Keyfob7"
Case &H26
Return "Keyfob8"
Case &H27
Return "User1"
Case &H28
Return "User2"
Case &H29
Return "User3"
Case &H2A
Return "User4"
Case &H2B
Return "User5"
Case &H2C
Return "User6"
Case &H2D
Return "User7"
Case &H2E
Return "User8"
Case &H2F
Return "Wireless Commander1"
Case &H30
Return "Wireless Commander2"
Case &H31
Return "Wireless Commander3"
Case &H32
Return "Wireless Commander4"
Case &H33
Return "Wireless Commander5"
Case &H34
Return "Wireless Commander6"
Case &H35
Return "Wireless Commander7"
Case &H36
Return "Wireless Commander8"
Case &H37
Return "Wireless Siren1"
Case &H38
Return "Wireless Siren2"
Case &H39
Return "2Way Wireless Keypad1"
Case &H3A
Return "2Way Wireless Keypad2"
Case &H3B
Return "2Way Wireless Keypad3"
Case &H3C
Return "2Way Wireless Keypad4"
Case &H3D
Return "X10-1"
Case &H3E
Return "X10-2"
Case &H3F
Return "X10-3"
Case &H40
Return "X10-4"
Case &H41
Return "X10-5"
Case &H42
Return "X10-6"
Case &H43
Return "X10-7"
Case &H44
Return "X10-8"
Case &H45
Return "X10-9"
Case &H46
Return "X10-10"
Case &H47
Return "X10-11"
Case &H48
Return "X10-12"
Case &H49
Return "X10-13"
Case &H4A
Return "X10-14"
Case &H4B
Return "X10-15"
Case &H4C
Return "PGM"
Case &H4D
Return "GSM"
Case &H4E
Return "Powerlink"
Case &H4F
Return "Proxy Tag1"
Case &H50
Return "Proxy Tag2"
Case &H51
Return "Proxy Tag3"
Case &H52
Return "Proxy Tag4"
Case &H53
Return "Proxy Tag5"
Case &H54
Return "Proxy Tag6"
Case &H55
Return "Proxy Tag7"
Case &H56
Return "Proxy Tag8"
Default
Return "Unknown (" & Hex$(bType, 2) & ")"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Log Event
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayLogEvent(bType As Byte) As String
Select bType
Case &H00
Return "None"
Case &H01
Return "Interior Alarm"
Case &H02
Return "Perimeter Alarm"
Case &H03
Return "Delay Alarm"
Case &H04
Return "24h Silent Alarm"
Case &H05
Return "24h Audible Alarm"
Case &H06
Return "Tamper"
Case &H07
Return "Control Panel Tamper"
Case &H08
Return "Tamper Alarm"
Case &H09
Return "Tamper Alarm"
Case &H0A
Return "Communication Loss"
Case &H0B
Return "Panic From Keyfob"
Case &H0C
Return "Panic From Control Panel"
Case &H0D
Return "Duress"
Case &H0E
Return "Confirm Alarm"
Case &H0F
Return "General Trouble"
Case &H10
Return "General Trouble Restore"
Case &H11
Return "Interior Restore"
Case &H12
Return "Perimeter Restore"
Case &H13
Return "Delay Restore"
Case &H14
Return "24h Silent Restore"
Case &H15
Return "24h Audible Restore"
Case &H16
Return "Tamper Restore"
Case &H17
Return "Control Panel Tamper Restore"
Case &H18
Return "Tamper Restore"
Case &H19
Return "Tamper Restore"
Case &H1A
Return "Communication Restore"
Case &H1B
Return "Cancel Alarm"
Case &H1C
Return "General Restore"
Case &H1D
Return "Trouble Restore"
Case &H1E
Return "Not used"
Case &H1F
Return "Recent Close"
Case &H20
Return "Fire"
Case &H21
Return "Fire Restore"
Case &H22
Return "No Active"
Case &H23
Return "Emergency"
Case &H24
Return "No used"
Case &H25
Return "Disarm Latchkey"
Case &H26
Return "Panic Restore"
Case &H27
Return "Supervision (Inactive)"
Case &H28
Return "Supervision Restore (Active)"
Case &H29
Return "Low Battery"
Case &H2A
Return "Low Battery Restore"
Case &H2B
Return "AC Fail"
Case &H2C
Return "AC Restore"
Case &H2D
Return "Control Panel Low Battery"
Case &H2E
Return "Control Panel Low Battery Restore"
Case &H2F
Return "RF Jamming"
Case &H30
Return "RF Jamming Restore"
Case &H31
Return "Communications Failure"
Case &H32
Return "Communications Restore"
Case &H33
Return "Telephone Line Failure"
Case &H34
Return "Telephone Line Restore"
Case &H35
Return "Auto Test"
Case &H36
Return "Fuse Failure"
Case &H37
Return "Fuse Restore"
Case &H38
Return "Keyfob Low Battery"
Case &H39
Return "Keyfob Low Battery Restore"
Case &H3A
Return "Engineer Reset"
Case &H3B
Return "Battery Disconnect"
Case &H3C
Return "1-Way Keypad Low Battery"
Case &H3D
Return "1-Way Keypad Low Battery Restore"
Case &H3E
Return "1-Way Keypad Inactive"
Case &H3F
Return "1-Way Keypad Restore Active"
Case &H40
Return "Low Battery"
Case &H41
Return "Clean Me"
Case &H42
Return "Fire Trouble"
Case &H43
Return "Low Battery"
Case &H44
Return "Battery Restore"
Case &H45
Return "AC Fail"
Case &H46
Return "AC Restore"
Case &H47
Return "Supervision (Inactive)"
Case &H48
Return "Supervision Restore (Active)"
Case &H49
Return "Gas Alert"
Case &H4A
Return "Gas Alert Restore"
Case &H4B
Return "Gas Trouble"
Case &H4C
Return "Gas Trouble Restore"
Case &H4D
Return "Flood Alert"
Case &H4E
Return "Flood Alert Restore"
Case &H4F
Return "X-10 Trouble"
Case &H50
Return "X-10 Trouble Restore"
Case &H51
Return "Arm Home"
Case &H52
Return "Arm Away"
Case &H53
Return "Quick Arm Home"
Case &H54
Return "Quick Arm Away"
Case &H55
Return "Disarm"
Case &H56
Return "Fail To Auto-Arm"
Case &H57
Return "Enter To Test Mode"
Case &H58
Return "Exit From Test Mode"
Case &H59
Return "Force Arm"
Case &H5A
Return "Auto Arm"
Case &H5B
Return "Instant Arm"
Case &H5C
Return "Bypass"
Case &H5D
Return "Fail To Arm"
Case &H5E
Return "Door Open"
Case &H5F
Return "Communication Established By Control Panel"
Case &H60
Return "System Reset"
Case &H61
Return "Installer Programming"
Case &H62
Return "Wrong Password"
Case &H63
Return "Not Sys Event"
Case &H64
Return "Not Sys Event"
Case &H65
Return "Extreme Hot Alert"
Case &H66
Return "Extreme Hot Alert Restore"
Case &H67
Return "Freeze Alert"
Case &H68
Return "Freeze Alert Restore"
Case &H69
Return "Human Cold Alert"
Case &H6A
Return "Human Cold Alert Restore"
Case &H6B
Return "Human Hot Alert"
Case &H6C
Return "Human Hot Alert Restore"
Case &H6D
Return "Temperature Sensor Trouble"
Case &H6E
Return "Temperature Sensor Trouble Restore"
Case &H6F
Return "PIR Mask"
Case &H70
Return "PIR Mask Restore"
Case &H7B
Return "Alarmed"
Case &H7C
Return "Restore"
Case &H7D
Return "Alarmed"
Case &H7E
Return "Restore"
Case &H8E
Return "Exit Installer"
Case &H8F
Return "Enter Installer"
Default
Return "Unknown (" & Hex$(bType, 2) & ")"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Zone Even Type information
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayZoneEventType(bType As Byte) As String
Select bType
Case &H00
Return "None"
Case &H01
Return "Tamper Alarm"
Case &H02
Return "Tamper Restore"
Case &H03
Return "Open"
Case &H04
Return "Closed"
Case &H05
Return "Violated (Motion)"
Case &H06
Return "Panic Alarm"
Case &H07
Return "RF Jamming"
Case &H08
Return "Tamper Open"
Case &H09
Return "Communication Failure"
Case &H0A
Return "Line Failure"
Case &H0B
Return "Fuse"
Case &H0C
Return "Not Active"
Case &H0D
Return "Low Battery"
Case &H0E
Return "AC Failure"
Case &H0F
Return "Fire Alarm"
Case &H10
Return "Emergency"
Case &H11
Return "Siren Tamper"
Case &H12
Return "Siren Tamper Restore"
Case &H13
Return "Siren Low Battery"
Case &H14
Return "Siren AC Fail"
Default
Return "Unknown (" & Hex$(bType, 2) & ")"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Zone Type Name
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayZoneType(bType As Byte) As String
Select bType
Case &H00
Return "Non-Alarm"
Case &H01
Return "Emergency"
Case &H02
Return "Flood"
Case &H03
Return "Gas"
Case &H04
Return "Delay 1"
Case &H05
Return "Delay 2"
Case &H06
Return "Interior-Follow"
Case &H07
Return "Perimeter"
Case &H08
Return "Perimeter-Follow"
Case &H09
Return "24 Hours Silent"
Case &H0A
Return "24 Hours Audible"
Case &H0B
Return "Fire"
Case &H0C
Return "Interior"
Case &H0D
Return "Home Delay"
Case &H0E
Return "Temperature"
Case &H0F
Return "Outdoor"
Default
Return "Unknown (" & Hex$(bType, 2) & ")"
End Select
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Display Zones in reverse binary format
' Zones are from e.g. 1-8, but it is stored in 87654321 order in binary format
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub DisplayZoneBin(sBin As Byte) As String
Dim sTmp As String
Dim sStr As String
Dim iCount As Integer
sTmp = Bin$(sBin, 8)
For iCount = Len(sTmp) To 1 Step -1
sStr &= Mid$(sTmp, iCount, 1)
Next
Return sStr
End
'########################################################
'Dim MSGV_DL_PINCODES As Byte[] = [&H3E, &HFA, &H01, &H10, &H00, &HB0, &H00, &H00, &H00, &H00, &H00] '&H3F
' TBD: if 0x10 -> 0x60 we see Master Installer & Installer & PowerLink pin
'########################################################
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Arm or Disarm the PowerMax/Master
' Byte 3 must be one of the following commands:
' 0x00 Disarm
' 0x04 Arm home
' 0x05 Arm away
' 0x14 Arm home instantly
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_ArmDisarm(sMode As String)
Dim bCommand As Byte[]
bCommand = VMSG_ARMDISARM.Copy()
Select Case sMode
Case "Disarm"
bCommand[3] = &H00
Case "ArmHome"
bCommand[3] = &H04
Case "ArmAway"
bCommand[3] = &H05
Case "ArmInstant"
bCommand[3] = &H14
Case Else
If $bDebug Then WriteDebugLog("Error arming alarm, unknown arm command: " & sMode)
Return
End Select
' Send the request, the pin will be added when the command is send
QueueCommand(bCommand, "Disarm/Arm of the PowerMax/Master", &H00, "PIN:MasterCode:4")
End
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' Set the date & time on the PowerMax/Master
' Note: This seems to work only on the Powermax Pro and not on the Powermax Plus
' Byte 4 Minutes
' Byte 5 Hours (0-24)
' Byte 6 Day of month (1-31)
' Byte 7 Month (1-12)
' Byte 8 Year since 2000
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Private Sub SendMsg_SetDateTime()
Dim bCommand As Byte[]
Dim dNow As Date
bCommand = VMSG_SET_DATETIME.Copy()
dNow = Now()
bCommand[4] = Minute(dNow)
bCommand[5] = Hour(dNow)
bCommand[6] = Day(dNow)
bCommand[7] = Month(dNow)
bCommand[8] = Year(dNow) - 2000
' Send the request, the pin will be added when the command is send
QueueCommand(bCommand, "Set Date & Time on PowerMax/Master", &H00)
End
You can’t perform that action at this time.