There seems to be some python code out there for the IT-100, which I’m hoping someone can work into Home Assistant.
A Python driver for the DSC IT-100 integration module
There seems to be some python code out there for the IT-100, which I’m hoping someone can work into Home Assistant.
A Python driver for the DSC IT-100 integration module
Just a follow up my earlier post to see if anyone has made any progress with getting Home Assistant to work with a DSC Alarm panel via their IT-100 (RS232) module.
It would be a great addition.
Having same problem getting home assistant to work with IT-100 module. If someone have figured it out it would be great.
I’m not sure if this helps - but while I’m not using it with HASS, I am using my IT-100 with my Vera home automation controller via a raspberry pi , USB to serial cable (running ser2net)
That allows me to connect to the it-100 which is connected to my DSC panel over Ethernet/IP like Envisalink
I am interested in implementing this. Python is not my native language but I should have no problem putting something together. That driver linked above seems pretty simple to use. I can’t commit to making this fully featured but I’m pretty sure I can get some basic functionality up for this. Has anyone already began any work here?
Hi @pho3nixf1re
I’m not aware of anyone starting on this so it would be great to have the IT-100 supported. If I can help with testing please let me know.
EDIT: Ignore the code in this post, use the github repo at https://github.com/SolidElectronics/evl-emu
I’d like to see a native driver as well, but in the meantime I wrote this.
It’s a service that connects to the serial port and emulates an Envisalink so Home Assistant can use it. I haven’t tested this extensively, and I offer no guarantees it will work, but I thought I’d share it in case it’s useful to someone. It’s written using the Python multiprocessing libraries so it spawns several processes that each perform one task. Ideally this would be rewritten in a cleaner way with asyncio but that’s currently beyond my ability.
I have this running under Hassbian on a rPi3 with the DSC panel connected via a serial-USB adapter at /dev/ttyUSB0. Note: It requires the ‘pyserial’ module for interacting with the serial port.
Include this in /etc/rc.local to automatically start after boot.
/bin/su -c '/home/homeassistant/.homeassistant/evl-emu.py >/dev/null 2>&1' homeassistant
configuration.yaml
envisalink: !include envisalink.yaml
envisalink.yaml
Change 0000 to a valid code (for arming and disarming the panel)
I only included two zones here, and it probably only works with one partition.
host: 127.0.0.1
panel_type: DSC
user_name: user
password: pass
code: '0000'
zones:
1:
name: 'Front door'
type: 'door'
2:
name: 'Garage back door'
type: 'door'
#...
partitions:
1:
name: 'Alarm'
/home/homeassistant/.homeassistant/evl-emu.py
Change the SERIAL_PORT to match your setup. If possible, create a udev rule to make the USB-serial adapter always show up as /dev/it100.
#!/usr/bin/env python3
"""
Support for DSC alarm control panels using IT-100 integration module by emulating an EnvisaLink EVL-4
"""
import logging
import sys
import itertools
import os
import time
import multiprocessing
import subprocess
import signal
import serial
import inspect
import random
import socket
REQUIREMENTS = ['pyserial']
_LOGGER = logging.getLogger(__name__)
DEFAULT_PARTITIONS = 1
DEFAULT_ZONES = 64
SERIAL_PORT = '/dev/ttyUSB0'
#SERIAL_PORT='/dev/it100'
SERIAL_BAUD = 9600
NETWORK_HOST = '127.0.0.1'
NETWORK_PORT = 4025
HEX_MSG = True
# Zone state definitions
ZONE_OPEN = 0
ZONE_CLOSED = 1
# --------------------------------------------------------------------------------
# DSC Protcol definitions
# --------------------------------------------------------------------------------
COMMAND_POLL = '000'
COMMAND_STATUS_REQUEST = '001'
COMMAND_LABELS_REQUEST = '002'
COMMAND_SET_TIME_DATE = '010'
COMMAND_OUTPUT_CONTROL = '020'
COMMAND_PARTITION_ARM_CONTROL_AWAY = '030'
COMMAND_PARTITION_ARM_CONTROL_STAY = '031'
COMMAND_PARTITION_ARM_CONTROL_ZERO_ENTRY = '032'
COMMAND_PARTITION_ARM_CONTROL_WITH_CODE = '033'
COMMAND_PARTITION_DISARM_CONTROL = '040'
COMMAND_TIME_STAMP_CONTROL = '055'
COMMAND_TIME_DATE_BCAST_CONTROL = '056'
COMMAND_TEMPERATURE_BCAST_CONTROL = '057'
COMMAND_VIRTUAL_KEYBOARD_CONTROL = '058'
COMMAND_TRIGGER_PANIC_ALARM = '060'
COMMAND_KEY_PRESSED = '070'
COMMAND_SET_BAUD_RATE = '080'
COMMAND_CODE_SEND = '200'
NOTIFY_ACK = '500'
NOTIFY_ERROR = '501'
NOTIFY_SYSTEM_ERROR = '502'
NOTIFY_TIME_DATE_BCAST = '550'
NOTIFY_LABELS = '570'
NOTIFY_BAUD_RATE_SET = '580'
NOTIFY_ZONE_ALARM = '601'
NOTIFY_ZONE_ALARM_RESTORE = '602'
NOTIFY_ZONE_TAMPER = '603'
NOTIFY_ZONE_TAMPER_RESTORE = '604'
NOTIFY_ZONE_FAULT = '605'
NOTIFY_ZONE_FAULT_RESTORE = '606'
NOTIFY_ZONE_OPEN = '609'
NOTIFY_ZONE_RESTORED = '610'
NOTIFY_DURESS_ALARM = '620'
NOTIFY_FIRE_KEY_ALARM = '621'
NOTIFY_FIRE_KEY_RESTORED = '622'
NOTIFY_AUXILARY_KEY_ALARM = '623'
NOTIFY_AUXILARY_KEY_RESTORED = '624'
NOTIFY_PANIC_KEY_ALARM = '625'
NOTIFY_PANIC_KEY_RESTORED = '626'
NOTIFY_AUXILARY_INPUT_ALARM = '631'
NOTIFY_AUXILARY_INPUT_RESTORED = '632'
NOTIFY_PARTITION_READY = '650'
NOTIFY_PARTITION_NOT_READY = '651'
NOTIFY_PARTITION_ARMED = '652'
NOTIFY_PARTITION_READY_TO_FORCE_ARM = '653'
NOTIFY_PARTITION_IN_ALARM = '654'
NOTIFY_PARTITION_DISARMED = '655'
NOTIFY_PARTITION_EXIT_DELAY = '656'
NOTIFY_PARTITION_ENTRY_DELAY = '657'
NOTIFY_KEYPAD_LOCKOUT = '658'
NOTIFY_KEYPAD_BLANKING = '659'
NOTIFY_COMMAND_OUTPUT = '660'
NOTIFY_INVALID_CODE = '670'
NOTIFY_FUNCTION_NOT_AVAILABLE = '671'
NOTIFY_FAILED_TO_ARM = '672'
NOTIFY_PARTITION_BUSY = '673'
NOTIFY_PARTITION_USER_CLOSING = '700'
NOTIFY_PARTITION_SPECIAL_CLOSING = '701'
NOTIFY_PARTITION_PARTIAL_CLOSING = '702'
NOTIFY_PARTITION_USER_OPENING = '750'
NOTIFY_PARTITION_SPECIAL_OPENING = '751'
NOTIFY_PANEL_BATTERY_TROUBLE = '800'
NOTIFY_PANEL_BATTERY_RESTORED = '801'
NOTIFY_PANEL_AC_TROUBLE = '802'
NOTIFY_PANEL_AC_RESTORED = '803'
NOTIFY_SYSTEM_BELL_TROUBLE = '806'
NOTIFY_SYSTEM_BELL_RESTORED = '807'
NOTIFY_GENERAL_DEV_LOW_BATTERY = '821'
NOTIFY_GENERAL_DEV_LOW_BATTERY_RESTORED = '822'
NOTIFY_GENERAL_SYSTEM_TAMPER = '829'
NOTIFY_GENERAL_SYSTEM_TAMPER_RESTORED = '830'
NOTIFY_PARTITION_TROUBLE = '840'
NOTIFY_PARTITION_TROUBLE_RESTORED = '841'
NOTIFY_FIRE_TROUBLE_ALARM = '842'
NOTIFY_FIRE_TROUBLE_RESTORED = '843'
NOTIFY_KEYBUS_FAULT = '896'
NOTIFY_KEYBUS_RESTORED = '897'
NOTIFY_CODE_REQUIRED = '900'
NOTIFY_BEEP_STATUS = '904'
NOTIFY_VERSION = '908'
# --------------------------------------------------------------------------------
# Envisalink Protcol definitions
# --------------------------------------------------------------------------------
EVL_LOGIN_REQUEST = '005'
EVL_DUMP_TIMERS = '008'
EVL_KEY_STRING = '071'
EVL_LOGIN_INTERACTION = '505'
EVL_DUMP_TIMER_RESPONSE = '615'
"""
Command codes not handled by DSC
005
008
071
072
073
074
80 ??
Response codes not handled by DSC
505
510
511
615
616
663
664
674
680
815
849
912
921
922
"""
# --------------------------------------------------------------------------------
# Classes
# --------------------------------------------------------------------------------
class dsc_zone():
def __init__(self, zone):
self.zone = zone
self.state = ZONE_CLOSED
self.description = ""
self.close_time = 0
def getZone(self):
return self.zone
def setState(self, newstate):
# Update zone timer if zone is going from open to closed
if (self.state == ZONE_OPEN and newstate == ZONE_CLOSED):
self.close_time = time.time()
# Set new state
self.state = newstate
def getState(self):
return self.state
def setDescription(self, desc):
self.description = desc
def getDescription(self):
return self.description
"""
Report zone timer as 4-byte little-endian string
FFFF = open
When closed, start counting down from 0xFFFF every five seconds
eg. after ten seconds the value is 0xFFFD, return little-endian as FDFF
Implementation note:
If HA polls the zone timers within five seconds of closing and sees 0xFFFF because it hasn't decremented yet, it assumes the zone has re-opened.
None of the documentation mentions this.
NOTE: any result less than 30 seconds is treated as still open. just wow.
https://community.home-assistant.io/t/dsc-alarm-integration/409/390
It's in pyenvisalink/envisalink_base_client.py
Solution is to start counting down from 0xFFFA
"""
def getTimer(self):
if (self.state == ZONE_OPEN):
return "FFFF"
else:
timedelta = int((time.time() - self.close_time) / 5)
timedeltastring = format(max(0, 0xFFFF - 6 - timedelta), '04X')
timedeltastringLE = timedeltastring[2:4] + timedeltastring[0:2]
return timedeltastringLE
# --------------------------------------------------------------------------------
# Serial I/O Routines
# --------------------------------------------------------------------------------
"""
Listen to serial port and put incoming messages into the read queue
- Only passes message content. Checksum and CR/LF are removed.
"""
def serialRead(readQueueSer, port):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
lastdatatime = time.time()
msgbuf = ''
while True:
read_byte = port.read(1)
# If there is a long delay between messages while data is in the buffer, assume something went wrong and throw out the old data
thisdatatime = time.time()
if ( ((thisdatatime - lastdatatime) > 0.5) and (msgbuf.__len__() > 0) ):
print ("ERROR: flushing stale data from receive buffer {}".format(msgbuf))
msgbuf=''
# Add each byte of received data to message buffer
msgbuf += read_byte.decode('ASCII')
lastdatatime = time.time()
# If we have enough characters for a full message, start checking for CR/LR terminator
if (msgbuf.__len__() >= 7):
if (ord(msgbuf[msgbuf.__len__()-2]) == 0x0D and ord(msgbuf[msgbuf.__len__()-1]) == 0x0A):
# Found terminator, message is complete.
if (HEX_MSG):
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
print ("{} DSC In > {} {}".format(timestamp, ":".join("{:02X}".format(ord(c)) for c in msgbuf), msgbuf[0:msgbuf.__len__() - 2] ))
# Queue message if checksum OK
msgdata = msgbuf[0:msgbuf.__len__() - 4]
msgchksum = msgbuf[msgbuf.__len__() - 4:msgbuf.__len__() - 2]
if (msgchksum == dsc_checksum(msgdata)):
readQueueSer.put(msgdata)
else:
print ("{} DSC In > Checksum error".format(timestamp))
msgbuf = ''
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
"""
Pull messages from write queue and send them to the serial port
- Does not add checksum or CR/LF
"""
def serialWrite(writeQueueSer, port):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
while True:
# Block until something is in the queue
msg = writeQueueSer.get(True, None)
# Send message
if (HEX_MSG):
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
print("{} DSC Out < {} {}".format(timestamp, ":".join("{:02X}".format(ord(c)) for c in msg), msg[0:msg.__len__() - 2] ))
port.write(bytes(msg, 'UTF-8'))
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
# --------------------------------------------------------------------------------
# Network Functions
# --------------------------------------------------------------------------------
"""
Listen to socket connection and put incoming messages into the read queue
This routine does not handle the actual connection, just interacting with the existing connection
- Only passes message content. Checksum and CR/LF are removed.
"""
def networkRead(readQueueNet, conn):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
lastdatatime = time.time()
msgbuf = ''
while True:
read_byte = conn.recv(1)
if (read_byte == b''):
raise NameError('Connection closed by client')
# If there is a long delay between messages while data is in the buffer, assume something went wrong and throw out the old data
thisdatatime = time.time()
if ( ((thisdatatime - lastdatatime) > 0.5) and (msgbuf.__len__() > 0) ):
print ("ERROR: flushing stale data from receive buffer {}".format(msgbuf))
msgbuf=''
# Add each byte of received data to message buffer
msgbuf += read_byte.decode('ASCII')
lastdatatime = time.time()
# If we have enough characters for a full message, start checking for CR/LR terminator
if (msgbuf.__len__() >= 7):
if (ord(msgbuf[msgbuf.__len__()-2]) == 0x0D and ord(msgbuf[msgbuf.__len__()-1]) == 0x0A):
# Found terminator, message is complete.
if (HEX_MSG):
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
print ("{} EVL In > {} {}".format(timestamp, ":".join("{:02X}".format(ord(c)) for c in msgbuf), msgbuf[0:msgbuf.__len__() - 2] ))
# Queue message if checksum OK
msgdata = msgbuf[0:msgbuf.__len__() - 4]
msgchksum = msgbuf[msgbuf.__len__() - 4:msgbuf.__len__() - 2]
if (msgchksum == dsc_checksum(msgdata)):
readQueueNet.put(msgdata)
else:
print ("{} EVL In > Checksum error".format(timestamp))
msgbuf = ''
except NameError:
print ("Connection closed by client, terminating networkRead thread.")
conn.close()
return None
except OSError:
print ("OSError: {}".format(inspect.stack()[0][3]))
conn.close()
return None
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
"""
Pull messages from write queue and send them to the socket connection
- Does not add checksum or CR/LF
"""
def networkWrite(writeQueueNet, conn):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
while True:
# This should block until something is in the queue
msg = writeQueueNet.get(True, None)
# Print message and send it out the socket connection
if (HEX_MSG):
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
print("{} EVL Out < {} {}".format(timestamp, ":".join("{:02X}".format(ord(c)) for c in msg), msg[0:msg.__len__() - 2] ))
conn.send(bytes(msg, 'UTF-8'))
except OSError:
print ("OSError: {}".format(inspect.stack()[0][3]))
conn.close()
return None
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
"""
Test function
Open a network socket to accept fake commands that look like they're originating from the panel
Note: Don't send checksum or CR/LF, they're not needed.
"""
def networkReadTest(readQueueSer):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
sock = socket.socket()
sock.bind((NETWORK_HOST, (1 + NETWORK_PORT)))
sock.setblocking(1)
sock.listen(5)
print ("Test listening on {}:{}".format(NETWORK_HOST, str(1 + NETWORK_PORT)))
while True:
conn, addr = sock.accept()
msg = conn.recv(128).decode("UTF-8")
if (HEX_MSG):
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
#print ("{} Test In > {} {}".format(timestamp, ":".join("{:02X}".format(ord(c)) for c in msg), msg ))
print ("{} Test In > {}".format(timestamp, msg))
readQueueSer.put(msg)
conn.close()
except OSError:
print ("OSError: {}".format(inspect.stack()[0][3]))
conn.close()
return None
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
# --------------------------------------------------------------------------------
# Event Processing
# --------------------------------------------------------------------------------
"""
Process messages that arrive from DSC via the serial queue.
"""
def msghandler_dsc(readQueueSer, writeQueueSer, writeQueueNet, zones):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
while True:
msg = readQueueSer.get()
if (msg.__len__() > 0):
command = str(msg[0:3])
data = str(msg[3:msg.__len__()])
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
# Track zone state changes
if (command == NOTIFY_ZONE_OPEN):
zoneobj = zones[int(data)-1]
zoneobj.setState(ZONE_OPEN)
zones[int(data)-1] = zoneobj
elif (command == NOTIFY_ZONE_RESTORED):
zoneobj = zones[int(data)-1]
zoneobj.setState(ZONE_CLOSED)
zones[int(data)-1] = zoneobj
# All other messages relay to EVL
writeQueueNet.put(dsc_send(msg))
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
"""
Process messages that arrive from the EVL client via the network queue
"""
def msghandler_evl(readQueueNet, writeQueueNet, writeQueueSer, zones):
print("Starting {} ({})".format(inspect.stack()[0][3], os.getpid()))
try:
while True:
msg = readQueueNet.get()
if (msg.__len__() > 0):
# Print incoming message
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
# Decode message and handle it as appropriate
command = str(msg[0:3])
data = str(msg[3:(msg.__len__())])
# --------------------------------------------------------------------------------
# Message handlers
# This needs to intercept EVL-specific messages and not send those to the panel
# --------------------------------------------------------------------------------
timestamp=time.strftime("[%H:%M:%S]", time.localtime())
# Login
if (command == EVL_LOGIN_REQUEST):
writeQueueNet.put(dsc_send(EVL_LOGIN_INTERACTION + "1"))
# Dump timers
elif (command == EVL_DUMP_TIMERS):
timermsg = ""
for z in zones:
timermsg += z.getTimer()
writeQueueNet.put(dsc_send(EVL_DUMP_TIMER_RESPONSE + timermsg))
# Key sequence
# - Enables virtual keypad only while code is being sent
elif (command == EVL_KEY_STRING):
writeQueueSer.put(dsc_send(COMMAND_VIRTUAL_KEYBOARD_CONTROL + '1'))
time.sleep(0.5)
for c in data[1:]:
keypress = dsc_send(COMMAND_KEY_PRESSED + c)
writeQueueSer.put(keypress)
time.sleep(0.25)
keypress = dsc_send(COMMAND_KEY_PRESSED + '^')
writeQueueSer.put(keypress)
time.sleep(0.25)
writeQueueSer.put(dsc_send(COMMAND_VIRTUAL_KEYBOARD_CONTROL + '0'))
# Code padding (most commands require 6 digits, 4-digit codes need two zeros appended.
# -- Partition disarm
elif (command == COMMAND_PARTITION_DISARM_CONTROL):
disarm_zone = data[0]
disarm_code = data[1:]
if (len(disarm_code) == 4):
disarm_code += '00'
writeQueueSer.put(dsc_send(command + disarm_zone + disarm_code))
# -- Code request
elif (command == COMMAND_CODE_SEND):
if (len(data) == 4):
data += '00'
# DSC documentation is incorrect here. Need to send partition number ahead of code.
# Ideally pyenvisalink would do this correctly by remembering the partition from the '900'.
writeQueueSer.put(dsc_send(command + '1' + data))
# Customizations
# - Change "arm stay" to "arm zero entry delay"
elif (command == COMMAND_PARTITION_ARM_CONTROL_STAY):
writeQueueSer.put(dsc_send(COMMAND_PARTITION_ARM_CONTROL_ZERO_ENTRY + data))
# All other messages just relay to DSC as-is
else:
writeQueueSer.put(dsc_send(msg))
except KeyboardInterrupt:
pass
except:
print("Caught exception in {}: {}".format(inspect.stack()[0][3], sys.exc_info()[0]))
raise
print("Exiting {}".format(inspect.stack()[0][3]))
return None
# --------------------------------------------------------------------------------
# Helper functions
# --------------------------------------------------------------------------------
# Return checksum string for a given message
def dsc_checksum(msg):
total = 0
for i in msg:
total += ord(i)
total = total % 256
return "{:02X}".format(total)
# Append checksum and CR/LF for outgoing messages
def dsc_send(msg):
msg += dsc_checksum(msg)
msg += chr(0x0D)
msg += chr(0x0A)
return msg
# Signal handler
def signal_handler(signal, frame):
print("Signal handler called with signal {}".format(signal))
sys.exit(0)
# --------------------------------------------------------------------------------
# MAIN
# --------------------------------------------------------------------------------
if __name__ == "__main__":
try:
print("Process: {}".format(os.getpid()))
# Open serial port
ser = None
ser = serial.Serial(SERIAL_PORT, SERIAL_BAUD, timeout=None)
# Create socket
sock = socket.socket()
# Create shared queues for inter-process message handling
readQueueSer = multiprocessing.Queue()
writeQueueSer = multiprocessing.Queue()
readQueueNet = multiprocessing.Queue()
writeQueueNet = multiprocessing.Queue()
# Create shared data space
mgr = multiprocessing.Manager()
zones = mgr.list()
# Allocate zone objects
for z in range(64):
zones.append(dsc_zone(zone=z))
# Start worker threads
p_serialread = multiprocessing.Process(target=serialRead, args=(readQueueSer, ser))
p_serialwrite = multiprocessing.Process(target=serialWrite, args=(writeQueueSer, ser))
p_msghandler_dsc = multiprocessing.Process(target=msghandler_dsc, args=(readQueueSer, writeQueueSer, writeQueueNet, zones))
p_msghandler_evl = multiprocessing.Process(target=msghandler_evl, args=(readQueueNet, writeQueueNet, writeQueueSer, zones))
# Startup threads
p_serialread.start()
p_serialwrite.start()
p_msghandler_dsc.start()
p_msghandler_evl.start()
# Stop execution here until a SIGINT or SIGTERM is received. At this point all the work is being done by subprocesses and this function is just waiting to exit
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
time.sleep(3)
print("---------- Panel Initialization Start ----------")
writeQueueSer.put(dsc_send(COMMAND_POLL))
# time.sleep(1)
# writeQueueSer.put(dsc_send(COMMAND_VIRTUAL_KEYBOARD_CONTROL + '1'))
time.sleep(2)
print("---------- Panel Initialization End ----------")
# Startup test network connection
p_networkreadtest = multiprocessing.Process(target=networkReadTest, args=(readQueueSer, ))
p_networkreadtest.start()
# Handle network connections
sock.bind((NETWORK_HOST, NETWORK_PORT))
sock.setblocking(1)
sock.listen(5)
print ("EVL listening on {}:{}".format(NETWORK_HOST, str(NETWORK_PORT)))
while(1):
"""
This should only attempt to handle one client at a time.
If a new client connects, the queues are flushed and the network read/write threads are destroyed and recreated for the new connection.
The client should immediately be sent the login interaction message to request authentication.
"""
# Wait for client connection.
conn, addr = sock.accept()
print ("Client connected: {}".format(addr))
# Flush queues
while ( readQueueSer.empty() == False ): readQueueSer.get()
while ( readQueueNet.empty() == False ): readQueueNet.get()
while ( writeQueueSer.empty() == False ): writeQueueSer.get()
while ( writeQueueNet.empty() == False ): writeQueueNet.get()
# Terminate old network I/O threads if they exist
if 'p_networkread' in locals(): p_networkread.terminate()
if 'p_networkwrite' in locals(): p_networkwrite.terminate()
# Create new network I/O threads for this connection
p_networkread = multiprocessing.Process(target=networkRead, args=(readQueueNet, conn))
p_networkwrite = multiprocessing.Process(target=networkWrite, args=(writeQueueNet, conn))
p_networkread.start()
p_networkwrite.start()
# Ask client to log in. After this happens, the message handler thread will do the remainder of the interaction with the client
writeQueueNet.put(dsc_send(EVL_LOGIN_INTERACTION + '3'))
signal.pause()
except (serial.serialutil.SerialException):
print("Can't open port")
except (KeyboardInterrupt, SystemExit):
raise
except:
print("Caught exception in main: {}".format(sys.exc_info()[0]))
raise
finally:
print("Terminating threads")
if 'p_serialread' in locals(): p_serialread.terminate()
if 'p_serialwrite' in locals(): p_serialwrite.terminate()
if 'p_networkread' in locals(): p_networkread.terminate()
if 'p_networkwrite' in locals(): p_networkwrite.terminate()
if 'p_msghandler_dsc' in locals(): p_msghandler_dsc.terminate()
if 'p_msghandler_evl' in locals(): p_msghandler_evl.terminate()
if 'p_networkreadtest' in locals(): p_networkreadtest.terminate()
ser.close()
sock.shutdown(socket.SHUT_RDWR)
sock.close()
print("Done.")
Thank you so much @SolidElectronics! This was such a tremendous help! I am finally up and running using the DSC IT100 USB adapter connected to a Pi2 running hassbian. The emulator works great!
I had two snags in my setup:
I was getting a connect error in the logs. I checked and discovered that evl-emu.py was not running (listening). This was even after I included your line in /etc/rc.local. I worked around it and piped the output to a log file.
Once the emulator was listening, I got the following error: “device reports readiness to read but returned no data.” I figured another process was already using the serial-USB adapter, so I commented out alarmdecoder in configuration.yaml
envisalink: !include envisalink.yaml #alarmdecoder: !include alarmdecoder.yaml
Restarted both the emulator (evl-emu.py) and HA, now it’s working like a charm! Thank you, again, for sharing!
@SolidElectronics - great work on this - I thought I would reach out to see if you have updated the python script at all or if you had it on a public repo?
Thanks for the suggestion, I’d been meaning to put it in a public repo but never got around to it until now.
https://github.com/SolidElectronics/evl-emu
Since the original post I switched over to running HA inside Docker and moved this script to a standalone Pi1 but there were essentially no changes to the script other than updating a couple of IP addresses and the device name.
@SolidElectronics - awesome; thanks! I am running it the same way as well (an external PI (away from HA)).
All, I wrote up the process on how I got HA working with the IT-100 using @SolidElectronics script - works great and response time is nominal for the binary sensors as well.
Hi @cbschuld, I followed the instructions you posted on your site using a fresh RPi 3 B+ and a known working USB to serial converter (known working, as in it was doing the same job with openHAB, which I am moving away from).
Unfortunately, I just cannot get this working! I tried a commit from Feb (9b3eb4c), considering that was closer to when you posted your instructions (after I couldn’t get the latest working), and there is definitely more output in that version.
I can see what I assume to be serial input and output (the EVL In and EVL Out) when running the script manually, and it also shows the IP address of my hass.io instance making the connection.
The defined sensors matched up with my zones are not reporting status in Home Assistant, nor are the arm actions working - when I send an arm request, I can see that the script sees it (I get an EVL In, with a bunch of data), but nothing happens.
Was hoping you or @SolidElectronics might have some pointers?
And since I posted this, I dug a little deeper… Still not working properly, but at least I know what’s going on…
My baud rate isn’t set to the default 9600, and is instead 115200. I changed that, and then was getting responses from the panel (I assumed I was getting responses from the panel before, but looks like it was just responses from the IT100 module only).
Only problem is that it is detecting all data from the panel as having a bad checksum. To confirm if the data was really bad, I commented out the lines in the script that validate the checksum, and was finally able to see the state change in Home Assistant for the zones!
However… as soon as the scheduled blob of data comes through (I guess regular polling or something?), it wipes out the current reported status. This is the chunk of data I am talking about: -
[14:09:05] EVL Out < 36:31:35:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:30:39:43:0D:0A 61500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009C
Once that is received, all zone statuses are wiped out and they go back to being “closed” or equivalent.
Would the checksum change with the baud rate? I wouldn’t think it would make a difference, but I don’t have a great deal of experience.
Hi @ebadayon
First things first, I’d really try to get the latest version of the code working, there definitely some bugs in the older version, mostly around HA not being able to reconnect after it restarts.
The EVL Out messages are the ones being sent from the script (pretending to be a real EVL) to HA, likely in response to HA occasionally polling the status. The ‘615’ message is the EVL sending the zone timer values back to HA. A value of zero means they’re basically uninitialized (EVL has never seen the zone close). When a zone closes, the value gets set to 0xFFFA and counts down by one every 30s.
The checksum is defined in the DSC IT-100 interfacing guide, it’s definitely not supposed to be dependent on baud rate.
Can you try the latest code version and send me a full debug output and I’ll see if I can make any sense of it?
@SolidElectronics, thanks for the reply! I’ll give the latest code a go when I get back home tonight (I’m in Australia). I did try the latest version first, but that was obviously before I sorted out a couple of my other local issues.
Is there a particular or preferred way to obtain the debug output?
@SolidElectronics, when I run the latest version (after updating the baud rate and device), I get the following output only: -
Process Process-2:
Traceback (most recent call last):
File "/usr/lib/python3.7/multiprocessing/process.py", line 297, in _bootstrap
self.run()
File "/usr/lib/python3.7/multiprocessing/process.py", line 99, in run
self._target(*self._args, **self._kwargs)
File "./evl-emu.py", line 264, in serialRead
logger.debug ("{} DSC In > Checksum error".format(timestamp))
NameError: name 'timestamp' is not defined
Edit: When I get rid of the .format(timestamp) off the end of the logger.debug, I get no output at all after executing the script (left for 5 minutes with nothing at all happening). There’s also no status update in Home Assistant when I deliberately trip sensors, so it doesn’t appear to actually be doing anything now.
Interestingly, I seem to be able to issue commands to the DSC panel now (I was able to arm the system), but I am not getting anything FROM it (it didn’t know it was armed, it doesn’t report zone changes, etc.)
EDIT: I have a workaround, which is basically to…
zonedump_interval: 0
” in my yaml to prevent zone status from being overwritten every 30 secondsThe last item is obviously less than ideal, but I have no idea why the checksums are failing for everything when the data is obviously valid. Would be great to be able to work out what’s going on at least with that last point!
I was able to fix the timestamp issue, thanks for finding that. I’ve never had a checksum error that causes that section of code to run.
I’d still like to see the a log of the interaction between your panel and the software to see how it compares to the specification document from DSC. Can you tell me which panel and serial interface you’re using? I’ve got a Power864 with an IT-100 at 9600 baud, which is really the only thing this has ever been tested with.
I attached a log of what mine does when starting up if you want to compare it.
It starts the serial read/write processes immediately, then waits for HA to connect. When that happens, it starts up the network read/write processes and message handlers. After doing the initial connection and login stuff, HA will send a status request message (001 91) that causes the panel to start dumping out its software version followed by all the partition and zone status messages.
~/evl-emu.py --debug --hex
11/05 09:11:33 - -------------------- STARTUP --------------------
11/05 09:11:33 - Startup Process: 11093
11/05 09:11:33 - EVL waiting for connection on 0.0.0.0:4025
11/05 09:11:33 - Starting serialRead (11100)
11/05 09:11:33 - Starting serialWrite (11101)
11/05 09:12:00 - Client connected: ('1.1.1.1', 58536)
11/05 09:12:00 - Flushing queues
11/05 09:12:00 - Terminating old processes
11/05 09:12:00 - All processes are stopped, continuing.
11/05 09:12:00 - Creating new p_networkread and p_networkwrite
11/05 09:12:00 - Doing client login
11/05 09:12:00 - Starting networkWrite (12135)
11/05 09:12:00 - EVL Out < 35:30:35:33:43:44:0D:0A 5053CD
11/05 09:12:00 - Starting networkRead (12134)
11/05 09:12:00 - EVL In > 30:30:35 ...password/checksum
11/05 09:12:00 - Creating new p_msghandler_dsc and p_msghandler_evl
11/05 09:12:00 - EVL Out < 35:30:35:31:43:42:0D:0A 5051CB
11/05 09:12:00 - EVL In > 30:31:30:30:39:31:32:31:31:30:35:31:39:38:45:0D:0A 01009121105198E
11/05 09:12:00 - EVL In > 30:30:31:39:31:0D:0A 00191
11/05 09:12:00 - Client ready.
11/05 09:12:00 - Starting msghandler_dsc (12140)
11/05 09:12:00 - Starting msghandler_evl (12141)
11/05 09:12:00 - Client: 010:0912110519
11/05 09:12:00 - DSC Out < 30:31:30:30:39:31:32:31:31:30:35:31:39:38:45:0D:0A 01009121105198E
11/05 09:12:00 - Client: Status request
11/05 09:12:00 - DSC Out < 30:30:31:39:31:0D:0A 00191
11/05 09:12:00 - DSC In > 35:30:30:30:31:30:32:36:0D:0A 50001026
11/05 09:12:00 - Panel: ack 010
11/05 09:12:00 - EVL Out < 35:30:30:30:31:30:32:36:0D:0A 50001026
11/05 09:12:00 - DSC In > 35:30:30:30:30:31:32:36:0D:0A 50000126
11/05 09:12:00 - Panel: ack 001
11/05 09:12:00 - EVL Out < 35:30:30:30:30:31:32:36:0D:0A 50000126
11/05 09:12:00 - DSC In > 39:30:38:30:31:30:30:31:31:43:34:0D:0A 908010011C4
11/05 09:12:00 - Panel: version 010011
11/05 09:12:00 - EVL Out < 39:30:38:30:31:30:30:31:31:43:34:0D:0A 908010011C4
11/05 09:12:00 - DSC In > 36:35:30:31:43:43:0D:0A 6501CC
11/05 09:12:00 - Panel: partition 1 ready
11/05 09:12:00 - DSC In > 36:35:30:32:43:44:0D:0A 6502CD
11/05 09:12:00 - Panel: partition 2 ready
11/05 09:12:00 - EVL Out < 36:35:30:31:43:43:0D:0A 6501CC
11/05 09:12:00 - EVL Out < 36:35:30:32:43:44:0D:0A 6502CD
...
Thanks again for continuing to help out!
I’m running a PC1864 with an IT-100 at 115200 baud rate, and here’s the output with the checksum verification still commented out (note that I am still running your initial commit, because I couldn’t get any later ones to run for some reason): -
pi@DSC-BRIDGE:~ $ ~/evl-emu/evl-emu.py --debug --hex
Process: 538
Starting serialRead (546)
Starting serialWrite (547)
Starting msghandler_dsc (548)
Starting msghandler_evl (550)
---------- Panel Initialization Start ----------
[21:32:03] DSC Out < 30:30:30:39:30:0D:0A 00090
[21:32:03] DSC In > 30:37:3A:33:31:3A:30:30:20:35:30:30:30:30:30:32:35:0D:0A 07:31:00 50000025
---------- Panel Initialization End ----------
EVL listening on 0.0.0.0:4025
Starting networkReadTest (557)
Test listening on 0.0.0.0:4026
[21:32:11] DSC In > 30:37:3A:33:32:3A:30:30:20:35:35:30:30:37:33:32:31:31:30:36:31:39:39:38:0D:0A 07:32:00 550073211061998
[21:32:11] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:31:30:30:30:33:32:20:20:44:61:74:65:20:20:20:20:20:54:69:6D:65:20:4E:4F:56:20:30:36:2F:31:39:20:20:37:3A:33:32:61:32:35:0D:0A 07:32:00 90100032 Date Time NOV 06/19 7:32a25
Client connected: ('192.168.1.74', 39434)
Starting networkRead (559)
Starting networkWrite (560)
[21:32:32] EVL Out < 35:30:35:33:43:44:0D:0A 5053CD
[21:32:32] EVL In > 30:30:35:70:61:73:73:34:43:0D:0A 005pass4C
[21:32:32] EVL Out < 35:30:35:31:43:42:0D:0A 5051CB
[21:32:32] EVL In > 30:31:30:30:37:33:32:31:31:30:36:31:39:38:46:0D:0A 01007321106198F
[21:32:32] EVL In > 30:30:31:39:31:0D:0A 00191
[21:32:32] DSC Out < 30:31:30:30:37:33:32:31:31:30:36:31:39:38:46:0D:0A 01007321106198F
[21:32:32] DSC Out < 30:30:31:39:31:0D:0A 00191
[21:32:32] DSC In > 30:37:3A:33:32:3A:30:30:20:35:30:30:30:31:30:32:36:0D:0A 07:32:00 50001026
[21:32:32] DSC In > 30:37:3A:33:32:3A:30:30:20:35:30:30:30:30:31:32:36:0D:0A 07:32:00 50000126
[21:32:32] EVL Out < 30:37:3A:33:32:3A:30:30:20:35:30:30:30:31:30:45:36:0D:0A 07:32:00 500010E6
[21:32:32] EVL Out < 30:37:3A:33:32:3A:30:30:20:35:30:30:30:30:31:45:36:0D:0A 07:32:00 500001E6
[21:32:32] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:38:30:31:30:32:30:31:43:35:0D:0A 07:32:00 908010201C5
[21:32:32] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:38:30:31:30:32:30:31:38:35:0D:0A 07:32:00 90801020185
[21:32:32] DSC In > 30:37:3A:33:32:3A:30:30:20:38:39:37:41:38:0D:0A 07:32:00 897A8
[21:32:32] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:39:37:36:38:0D:0A 07:32:00 89768
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:35:30:31:43:43:0D:0A 07:32:00 6501CC
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:35:30:31:38:43:0D:0A 07:32:00 65018C
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:32:44:32:0D:0A 07:32:00 6732D2
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:32:39:32:0D:0A 07:32:00 673292
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:33:44:33:0D:0A 07:32:00 6733D3
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:33:39:33:0D:0A 07:32:00 673393
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:34:44:34:0D:0A 07:32:00 6734D4
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:34:39:34:0D:0A 07:32:00 673494
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:35:44:35:0D:0A 07:32:00 6735D5
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:35:39:35:0D:0A 07:32:00 673595
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:36:44:36:0D:0A 07:32:00 6736D6
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:36:39:36:0D:0A 07:32:00 673696
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:37:44:37:0D:0A 07:32:00 6737D7
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:37:39:37:0D:0A 07:32:00 673797
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:37:33:38:44:38:0D:0A 07:32:00 6738D8
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:37:33:38:39:38:0D:0A 07:32:00 673898
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:31:43:45:0D:0A 07:32:00 8411CE
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:31:38:45:0D:0A 07:32:00 84118E
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:32:43:46:0D:0A 07:32:00 8412CF
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:32:38:46:0D:0A 07:32:00 84128F
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:33:44:30:0D:0A 07:32:00 8413D0
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:33:39:30:0D:0A 07:32:00 841390
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:34:44:31:0D:0A 07:32:00 8414D1
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:34:39:31:0D:0A 07:32:00 841491
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:35:44:32:0D:0A 07:32:00 8415D2
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:35:39:32:0D:0A 07:32:00 841592
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:36:44:33:0D:0A 07:32:00 8416D3
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:36:39:33:0D:0A 07:32:00 841693
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:37:44:34:0D:0A 07:32:00 8417D4
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:37:39:34:0D:0A 07:32:00 841794
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:38:34:31:38:44:35:0D:0A 07:32:00 8418D5
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:38:34:31:38:39:35:0D:0A 07:32:00 841895
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:31:31:46:45:0D:0A 07:32:00 90311FE
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:31:31:42:45:0D:0A 07:32:00 90311BE
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:32:30:46:45:0D:0A 07:32:00 90320FE
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:32:30:42:45:0D:0A 07:32:00 90320BE
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:33:30:46:46:0D:0A 07:32:00 90330FF
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:33:30:42:46:0D:0A 07:32:00 90330BF
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:34:30:30:30:0D:0A 07:32:00 9034000
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:34:30:43:30:0D:0A 07:32:00 90340C0
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:35:30:30:31:0D:0A 07:32:00 9035001
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:35:30:43:31:0D:0A 07:32:00 90350C1
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:36:30:30:32:0D:0A 07:32:00 9036002
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:36:30:43:32:0D:0A 07:32:00 90360C2
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:37:30:30:33:0D:0A 07:32:00 9037003
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:37:30:43:33:0D:0A 07:32:00 90370C3
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:38:31:30:35:0D:0A 07:32:00 9038105
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:38:31:43:35:0D:0A 07:32:00 90381C5
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:39:30:33:39:30:30:35:0D:0A 07:32:00 9039005
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:39:30:33:39:30:43:35:0D:0A 07:32:00 90390C5
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:31:32:38:0D:0A 07:32:00 61000128
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:31:45:38:0D:0A 07:32:00 610001E8
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:32:32:39:0D:0A 07:32:00 61000229
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:32:45:39:0D:0A 07:32:00 610002E9
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:33:32:41:0D:0A 07:32:00 6100032A
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:33:45:41:0D:0A 07:32:00 610003EA
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:34:32:42:0D:0A 07:32:00 6100042B
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:34:45:42:0D:0A 07:32:00 610004EB
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:35:32:43:0D:0A 07:32:00 6100052C
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:35:45:43:0D:0A 07:32:00 610005EC
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:36:32:44:0D:0A 07:32:00 6100062D
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:36:45:44:0D:0A 07:32:00 610006ED
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:37:32:45:0D:0A 07:32:00 6100072E
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:37:45:45:0D:0A 07:32:00 610007EE
[21:32:33] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:38:32:46:0D:0A 07:32:00 6100082F
[21:32:33] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:38:45:46:0D:0A 07:32:00 610008EF
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:39:33:30:0D:0A 07:32:00 61000930
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:30:39:46:30:0D:0A 07:32:00 610009F0
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:30:32:38:0D:0A 07:32:00 61001028
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:30:45:38:0D:0A 07:32:00 610010E8
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:31:32:39:0D:0A 07:32:00 61001129
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:31:45:39:0D:0A 07:32:00 610011E9
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:32:32:41:0D:0A 07:32:00 6100122A
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:32:45:41:0D:0A 07:32:00 610012EA
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:33:32:42:0D:0A 07:32:00 6100132B
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:33:45:42:0D:0A 07:32:00 610013EB
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:34:32:43:0D:0A 07:32:00 6100142C
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:34:45:43:0D:0A 07:32:00 610014EC
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:35:32:44:0D:0A 07:32:00 6100152D
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:35:45:44:0D:0A 07:32:00 610015ED
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:36:32:45:0D:0A 07:32:00 6100162E
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:36:45:45:0D:0A 07:32:00 610016EE
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:37:32:46:0D:0A 07:32:00 6100172F
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:37:45:46:0D:0A 07:32:00 610017EF
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:38:33:30:0D:0A 07:32:00 61001830
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:38:46:30:0D:0A 07:32:00 610018F0
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:39:33:31:0D:0A 07:32:00 61001931
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:31:39:46:31:0D:0A 07:32:00 610019F1
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:30:32:39:0D:0A 07:32:00 61002029
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:30:45:39:0D:0A 07:32:00 610020E9
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:31:32:41:0D:0A 07:32:00 6100212A
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:31:45:41:0D:0A 07:32:00 610021EA
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:32:32:42:0D:0A 07:32:00 6100222B
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:32:45:42:0D:0A 07:32:00 610022EB
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:33:32:43:0D:0A 07:32:00 6100232C
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:33:45:43:0D:0A 07:32:00 610023EC
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:34:32:44:0D:0A 07:32:00 6100242D
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:34:45:44:0D:0A 07:32:00 610024ED
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:35:32:45:0D:0A 07:32:00 6100252E
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:35:45:45:0D:0A 07:32:00 610025EE
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:36:32:46:0D:0A 07:32:00 6100262F
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:36:45:46:0D:0A 07:32:00 610026EF
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:37:33:30:0D:0A 07:32:00 61002730
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:37:46:30:0D:0A 07:32:00 610027F0
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:38:33:31:0D:0A 07:32:00 61002831
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:38:46:31:0D:0A 07:32:00 610028F1
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:39:33:32:0D:0A 07:32:00 61002932
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:32:39:46:32:0D:0A 07:32:00 610029F2
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:30:32:41:0D:0A 07:32:00 6100302A
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:30:45:41:0D:0A 07:32:00 610030EA
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:31:32:42:0D:0A 07:32:00 6100312B
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:31:45:42:0D:0A 07:32:00 610031EB
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:32:32:43:0D:0A 07:32:00 6100322C
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:32:45:43:0D:0A 07:32:00 610032EC
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:33:32:44:0D:0A 07:32:00 6100332D
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:33:45:44:0D:0A 07:32:00 610033ED
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:34:32:45:0D:0A 07:32:00 6100342E
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:34:45:45:0D:0A 07:32:00 610034EE
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:35:32:46:0D:0A 07:32:00 6100352F
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:35:45:46:0D:0A 07:32:00 610035EF
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:36:33:30:0D:0A 07:32:00 61003630
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:36:46:30:0D:0A 07:32:00 610036F0
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:37:33:31:0D:0A 07:32:00 61003731
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:37:46:31:0D:0A 07:32:00 610037F1
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:38:33:32:0D:0A 07:32:00 61003832
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:38:46:32:0D:0A 07:32:00 610038F2
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:39:33:33:0D:0A 07:32:00 61003933
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:33:39:46:33:0D:0A 07:32:00 610039F3
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:30:32:42:0D:0A 07:32:00 6100402B
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:30:45:42:0D:0A 07:32:00 610040EB
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:31:32:43:0D:0A 07:32:00 6100412C
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:31:45:43:0D:0A 07:32:00 610041EC
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:32:32:44:0D:0A 07:32:00 6100422D
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:32:45:44:0D:0A 07:32:00 610042ED
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:33:32:45:0D:0A 07:32:00 6100432E
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:33:45:45:0D:0A 07:32:00 610043EE
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:34:32:46:0D:0A 07:32:00 6100442F
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:34:45:46:0D:0A 07:32:00 610044EF
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:35:33:30:0D:0A 07:32:00 61004530
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:35:46:30:0D:0A 07:32:00 610045F0
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:36:33:31:0D:0A 07:32:00 61004631
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:36:46:31:0D:0A 07:32:00 610046F1
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:37:33:32:0D:0A 07:32:00 61004732
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:37:46:32:0D:0A 07:32:00 610047F2
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:38:33:33:0D:0A 07:32:00 61004833
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:38:46:33:0D:0A 07:32:00 610048F3
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:39:33:34:0D:0A 07:32:00 61004934
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:34:39:46:34:0D:0A 07:32:00 610049F4
[21:32:34] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:30:32:43:0D:0A 07:32:00 6100502C
[21:32:34] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:30:45:43:0D:0A 07:32:00 610050EC
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:31:32:44:0D:0A 07:32:00 6100512D
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:31:45:44:0D:0A 07:32:00 610051ED
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:32:32:45:0D:0A 07:32:00 6100522E
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:32:45:45:0D:0A 07:32:00 610052EE
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:33:32:46:0D:0A 07:32:00 6100532F
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:33:45:46:0D:0A 07:32:00 610053EF
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:34:33:30:0D:0A 07:32:00 61005430
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:34:46:30:0D:0A 07:32:00 610054F0
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:35:33:31:0D:0A 07:32:00 61005531
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:35:46:31:0D:0A 07:32:00 610055F1
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:36:33:32:0D:0A 07:32:00 61005632
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:36:46:32:0D:0A 07:32:00 610056F2
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:37:33:33:0D:0A 07:32:00 61005733
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:37:46:33:0D:0A 07:32:00 610057F3
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:38:33:34:0D:0A 07:32:00 61005834
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:38:46:34:0D:0A 07:32:00 610058F4
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:39:33:35:0D:0A 07:32:00 61005935
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:35:39:46:35:0D:0A 07:32:00 610059F5
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:30:32:44:0D:0A 07:32:00 6100602D
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:30:45:44:0D:0A 07:32:00 610060ED
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:31:32:45:0D:0A 07:32:00 6100612E
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:31:45:45:0D:0A 07:32:00 610061EE
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:32:32:46:0D:0A 07:32:00 6100622F
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:32:45:46:0D:0A 07:32:00 610062EF
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:33:33:30:0D:0A 07:32:00 61006330
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:33:46:30:0D:0A 07:32:00 610063F0
[21:32:35] DSC In > 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:34:33:31:0D:0A 07:32:00 61006431
[21:32:35] EVL Out < 30:37:3A:33:32:3A:30:30:20:36:31:30:30:36:34:46:31:0D:0A 07:32:00 610064F1
[21:32:58] EVL In > 30:30:30:39:30:0D:0A 00090
[21:32:58] DSC Out < 30:30:30:39:30:0D:0A 00090
[21:32:58] DSC In > 30:37:3A:33:32:3A:30:30:20:35:30:30:30:30:30:32:35:0D:0A 07:32:00 50000025
[21:32:58] EVL Out < 30:37:3A:33:32:3A:30:30:20:35:30:30:30:30:30:45:35:0D:0A 07:32:00 500000E5