[Release] Envisalink Security (Vista ONLY) - Native Hubitat TPI Driver (No Proxy Required)

After years of running the smartthings-nodeproxy integration (Option #2 in the old thread), I've finally replaced it with a fully native Hubitat version — no proxy server, no Raspberry Pi, no Node.js. The Hubitat hub connects directly to the EnvisaLink device over TCP.

This is Vista ONLY, no DSC support (sorry, I can’t test it so I can’t support it).

What changed

The old Option #2 required a Linux machine running 24/7 to bridge HTTP commands from Hubitat to the EnvisaLink's TCP socket. This new version does that entirely on the hub using Hubitat's native telnet API. The EnvisaLink EVL-3/EVL-4 speaks a simple TCP protocol (TPI) on port 4025 — the hub now connects to that directly, authenticates, and receives panel events in real time.

What's included

  • Arm Away, Arm Stay, Arm Instant, Disarm, Chime

  • Relay trigger outputs (17 and 18)

  • Zone bypass (from device page or Rule Machine)

  • Bidirectional HSM sync — disarming/arming in HSM arms/disarms the panel, and vice versa

  • Zone types: Contact, Motion, Smoke, Water, Carbon Monoxide

  • Auto-reconnect if the connection drops

  • Up to 64 zones

Requirements

  • EnvisaLink EVL-3 or EVL-4

  • Honeywell/Ademco Vista panel (tested on Vista 20P)

  • Single partition only (same limitation as before)

Installation via HPM

Search for "Envisalink" in Hubitat Package Manager, or paste the manifest URL directly:
https://raw.githubusercontent.com/bdwilson/hubitat/master/Envisalink/packageManifest.json

Then: Apps → Add User App → Envisalink Security → enter your EnvisaLink IP, network password (default: user), panel security code, and configure your zones.

Migrating from the STNP version

The new app and drivers are completely independent. Install them alongside the old setup, confirm everything works, then remove the old Honeywell Security app and its child devices when you're ready.

I've been running this for a few days without issues. If you hit anything, open a GitHub issue or reply here.

GitHub: https://github.com/bdwilson/hubitat/tree/master/Envisalink

Great work.

I had always planned to bypass the SmartThings proxy someday; now that you have created this native implementation, I can cross it off my bucket list. :laughing:

Thank for your time and effort in developing this. I have been using the native Hubitat integration Option #1 by Doug with an EVL4 and a Honeywell Vista 20P panel for some years now. It has been ok, but the telnet link is chatty, so I assigned it to a dedicated Hubitat Hub. I only use it to arm and disarm the alarm system from Rule Machine automations.

I am interested in your app because the original app is no longer supported. Is your app much different from Doug’s original app?

If I install your app, do I have to uninstall the original app first?

You can just disable his integration and try this one. You will end up with duplicate devices for the sensors. The debug logging stays on if you turn it on, so once you’ve got it working, I’d make sure to turn that off. When I stop making little tweaks, I’ll adjust the debug logs to auto-disable after a period of time.

In terms of how similar it is to Doug’s; I did give Claude his last repo that I cloned and redloro’s STNP repos to review, as well as Envisalink’s TPI documentation. Here’s what the details say:

Code Provenance Breakdown

From bdwilson/hubitat_envisalink (Doug Beard / CybrMage, ~2228 lines)

Borrowed directly:

  • The 16-bit flag bit positions — 0x8000 (armed_stay), 0x4000 (low_battery), 0x1000 (ready), 0x0004 (armed_away), 0x0001 (alarm), etc. These are just the hardware spec, but Doug's driver confirmed the exact values work.

  • The overall Hubitat telnet skeleton: telnetConnect(), telnetStatus(), parse(), sendHubCommand(new hubitat.device.HubAction(..., Protocol.TELNET)) — this is the API proof-of-concept the reference provided.

  • The initialize()connect() lifecycle pattern.

  • The addChildDevice() approach for zones (though we use custom drivers instead of Hubitat's built-in Virtual sensors).

Not used from Doug's driver (~80% of his code):

  • All DSC panel support (his driver is dual-mode DSC/Vista, ours is Vista-only)

  • His %01 / %02 / %03 handlers (we intentionally skip those)

  • Installer code programming, user code management, setDelays

  • Tamper alert, glass break, polling commands

  • His zone state machine (he uses per-zone state directly from %01 bitmaps; we derive everything from %00 sweeps)

  • His child device model (he uses hubitat:Virtual Contact Sensor built-ins with autoInactive timers; we wrote custom zone drivers)

  • His partition/HSM handling


From redloro's STNP (Node.js — repo is now gone)

This was used as the protocol reference, not code to port. Key things derived from it:

  • The confirmation that Vista panels use {securityCode}{digit} keystrokes rather than TPI command codes (2=arm away, 3=arm stay, 1=disarm, etc.)

  • The philosophy of ignoring %01/%02 and driving all state from %00 alone

  • The %FF zone timer dump format (256-char hex, 64 × 4-char little-endian timers, <30s = open)

  • The login handshake sequence ("Login:" → send password → "OK")

  • The %00 field layout: partition, flagsHex, userOrZone, beep, alphaText, checksum

Since it's JavaScript and we're in Groovy, nothing was literally copied — it was pure protocol documentation.


Written from scratch

  • The zone sweep timer mechanism — the tick counter that detects when a zone stops appearing in %00 and closes it. No equivalent in either reference.

  • The reconnect loop fix (unschedule("connect") in the "OK" handler) — a Hubitat-specific race condition, original diagnosis and fix.

  • The checksum stripping (msg[0..-2]) — not handled in either reference.

  • The HSM bidirectional sync — the entire hsmHandler / updatePartitionState / state.lastHSMStatus logic.

  • The re-arm debounce (state.cmdSentAt in the connection driver) — original fix for the panel state lag problem.

  • The App / Connection / Partition / Zone driver architecture — the separation into 8 files with the app owning the connection device and connection device owning the children. Doug's driver is a monolith; STNP is a Node process with callbacks.

  • Idempotent zone sync — the add-only approach in syncZones() that preserves existing devices.

  • All zone drivers — Contact, Motion, Smoke, Water, CO with proper Hubitat capabilities. Doug's code uses Hubitat's built-in Virtual sensors; we wrote typed drivers.

  • The packageManifest.json and HPM integration.


Rough percentage estimate

Source Contribution
Written from scratch ~65%
Doug's driver (protocol proof + flag values + telnet API skeleton) ~20%
redloro STNP (protocol reference / Vista command vocabulary) ~15%

The most valuable thing Doug's repo provided was proving telnetConnect() works in a Hubitat driver and confirming the flag bit values. The most valuable thing from STNP was the %00-only philosophy and the Vista keystroke command format. Everything architectural — the reconnect fix, zone sweep, HSM debounce, the 8-file structure — was new.

Very detailed and informative explanation. I will give it a try. Thank you!

The app is working fine, thank you. Great work! I have one request for future tweaks. Under the child devices could you add “Enable description Text logging” under Preferences? I would like to be able to monitor when certain doors are open or closed.

Added. The test would be the same as device name, so not sure the value since they would be the same.

Thank you for the quick response. Much appreciated. Will this change to the device drivers be uploaded to HPM?

Yes. They should be in HPM

Excellent work. Will try this soon.

Thanks. I'm using the old DB driver and have had issues with reconnection after power failures or if I power down a component to update something. I'm also concerned about hub load in general so I like the things that you've done.

However...I struggle with using a multi-partition system. Specifically when I arm the system I still want non-alarm sensors to report. They STOP reporting in the partition if it's armed. I want them to report so that as I move around the house I can still turn lights on/off automatically with motion sensors with the system armed against intrusion.

The solution to this was to put any sensors that I wanted active into partition 2 so that when Partition 1 is armed I can still have my home automation work.

This works BEAUTIFULLY - except - when I arm the system the DB driver reports ARMED for partition 1 for a few seconds (8-10) and then DISARMED for partition 2 (8-10) seconds. I need to set a virtual switch with logic that buffers this timing to determine if the system is really armed or not and change behavior based on alarm state. (i.e. turn on power to interior cameras that I normally want totally powered off etc.)

This is a bit annoying BUT it does work for the most part, not flawlessly.

If I went to your new driver, would it roughly report on the partitions armed in the same way that the current system does? i.e. you don't support doing things with 2 partitions, but do you still report in the same way? Would I still see devices in partition 1 and 2 or would I only see partition 1 devices?

Is there any hope for me to migrate?

Thanks very much!

Jon D.

Are those sensors you want reported on being bypassed as part of armStay?

Any arm command will cause the alarm system to stop reporting on the state of sensors that will not trigger an alarm. In that sense they are 'bypassed'. There is no way to cause the system to not do this, it's part of it's basic design.

For example, look at a first floor that has front/back/side door sensors which arm on an 'arm stay', and motion detectors (PIRs) in the living room and dining room which detect motion and turn on/off lights. When the system is armed, the door sensors will be active. The system will stop reporting on the motion detectors in the living room and dining room.

The only solution to this is to move the motion detectors into another partition that is not armed. When you do this, you can 'arm stay' and the doors will arm but the motion detectors will remain active.

In my house I have doors that I want armed but motion detectors through the house that turn on/off lights as people walk in and out of rooms. I want these to be active at all times so they are all assigned to partition 2.

With the OLDER driver (the one that I'm currently using), I can login to Hubitat and watch the state of the security system report ARMED (for a few seconds as it reports on an armed Partion 1) and then DISARMED (for a few seconds as it reports on the disarmed state of partition 2). I've written some logic that mostly works to determine if the system is 'armed' by waiting....but it places a load on the hub.

It would be nice if the driver could report alarm state by partition. Default to partition 1 for compatibility, which most people would probably use. If the driver could separate out the security system state by partition - I think that would really be great.

For all other functions, I"m not sure that it matters what partition a sensor is in, provided that folks understand how the system works with disabling non-alarming sensors when armed.

Does that all make sense?

THanks,

Jon D.

I think so. It sounds like when you're arming, your doors are armed but those motion sensors that do things are not armed and explicitly bypassed (i.e. my armStay does this).

Currently, bypassed zones do not report if the system is Armed. I do think there may be TPI reports that are done if one of those bypassed motion zones triggers, but before I did into it, I want to verify that's what you're looking for.

FYI Vista panels don’t report anything for zones that have been bypassed. I don’t use this solution, use a different one for my Vista panel but I did look into this exact situation and found out zero updates come across the serial connection for bypassed zones.

A point of clarification. The zones are NOT bypassed by me. When the system arms, it stops reporting on any zones that will not alarm within the partition. If I have two door sensors that alarm and two motion detectors that don't alarm because I use them to control lights for room occupancy, the two motion detectors STOP responding when the system is armed. Disarm the system, they respond. THAT is why, if you use motion detectors and want to arm the system (STAY) when you're home and still have lights work that are motion activated you move the motion detectors into a non-armed partition. You see?

To help understand the issue with things in different partitions, if you have a multiple partition system and use the variable HSM Status

HSM status reports *changed*

Toggle 00000000 (a lamp that I use to test)

The Hub log shows when the system is ARMED shows HSM status changing every 10 seconds.

dev:320 26-09-21 10:34:11.247 pm info 0000000 was turned on

dev:320 26-09-21 10:34:11.245 pm info 0000000 was set to 35

dev:320 26-09-21 10:34:10.820 pm info 0000000 was turned on

dev:320 26-09-21 10:34:10.818 pm info 0000000 was set to 35

dev:320 26-09-21 10:34:10.769 pm info 0000000 was turned off

dev:320 26-09-21 10:34:10.768 pm info 0000000 was set to 0

dev:320 26-09-21 10:34:09.583 pm info 0000000 was turned off

dev:320 26-09-21 10:34:09.581 pm info 0000000 was set to 0

dev:320 26-09-21 10:34:09.140 pm info 0000000 was turned off

dev:320 26-09-21 10:34:09.138 pm info 0000000 was set to 0

dev:320 26-09-21 10:34:09.062 pm info 0000000 was turned on

dev:320 26-09-21 10:34:09.061 pm info 0000000 was set to 35

dev:320 26-09-21 10:34:06.702 pm info 0000000 was turned on

dev:320 26-09-21 10:34:06.701 pm info 0000000 was set to 35

dev:320 26-09-21 10:34:01.668 pm info 0000000 was turned on

dev:320 26-09-21 10:34:01.666 pm info 0000000 was set to 35

dev:320 26-09-21 10:33:59.539 pm info 0000000 was turned on

dev:320 26-09-21 10:33:59.536 pm info 0000000 was set to 35

dev:320 26-09-21 10:33:59.442 pm info 0000000 was turned on

dev:320 26-09-21 10:33:59.441 pm info 0000000 was set to 35

This is because the system reports on Partition 1, then 10 seconds later, reports on partition 2.

You can't reliably use HSM status in a multi-partition system without buffering it with logic like:

If NOT DISARMED then set our derived status to Away or Stay using HSM Status

IF (NOT HSM status is Disarmed(T) [TRUE]) THEN

IF (HSM status is Delayed Arming Home(F) OR HSM status is Armed Home(T) OR HSM status is Delayed Arming Night(F) OR HSM status is Armed Night(F) [TRUE]) THEN

IF (Variable Alarm-State-Derived(Stay) is ≠ 'Stay'(F) [FALSE]) THEN

Set Alarm-State-Derived to 'Stay'

END-IF

ELSE

It wasn't any of the "stay" states so it must be away

IF (Variable Alarm-State-Derived(Stay) is ≠ 'Away'(T) [TRUE]) THEN

Set Alarm-State-Derived to 'Away'

END-IF

END-IF

ELSE

it was DISARMED, but that may have come from the OTHER partition. If it sticks through a 10 second cycle, then it truly is disarmed, if not, then throw it away because it's not coming from partition 1

Wait for Expression: HSM status is Disarmed(F) [FALSE] --> timeout: 0:00:15

IF (HSM status is Disarmed(F) [FALSE]) THEN

Set Alarm-State-Derived to 'Disarmed'

END-IF

END-IF

I just did a new post showing the 'toggling' behavior of Envisalink reporting between partition 1 and 2. It happens at roughly 10 second intervals.

HSM status will not reliably report. I need to buffer it with some code which is:

  • if HSM status CHANGES
  • If it's NOT disarmed
  • Check to see if it's a 'stay' status (home or nite) and set derived to STAY
    
  • Else set the derived status to AWAY
    
  • If it is disarmed
  • Wait for 15 seconds, if 'disarmed' persists for > 10 seconds then it's reporting disarmed across partition 1 and 2 and it is, indeed disarmed, so set the derived status to DISARMED

I need to do the same buffering for any status thing that I'm monitoring. The system will report a sensor as "CLOSED" in partition 1 for 10 seconds then report it as "OPENED" in partition 2 for 10 seconds. I use the same logic as above for 'debouncing' those other sensors.

For example, I want a fan to go on in front of a radiator when the heat comes on. I have a zone that monitors the furnace valve. I have to 'debounce' it so I can turn on the fan and keep it on.

What does this mean in terms of the Envisalink code?

1.) A 'quick and dirty fix' for HSM (since it only supports one partition) is to match up your code with it and simply toss any partition status info that doesn't come from Partition 1. If those armed/disarmed/state reporting went away it wouldn't confuse HSM and life would be good. You would be 'in synch' with the app instead of breaking it and causing the need for 'debouncing' status.

2.) Ultimately, zones need to be assigned to partitions and only get reports from the partition that they are in. If you could add another attribute of partition for each zone. When you get a report from zone 1, only those sensors in zone 1 accept the data that you've scraped from Envisalink, the data from non-zone 1 sensors isn't valid.

Does that help clarify stuff?

By the way, I hesitated to use your code because it didn't support multiple partitions. I thought that it wouldn't work. But I plowed on. I added another Eyezon board to my system to run 'in parallel'. Renamed all my zones to zone* (to indicated old zone). Added zones from YOUR driver with the original names. I couldn't 'swap' because 'swap' doesn't work with virtual devices. But I was able to edit one-by-one (I have 29 zones) and put them in. Worked like a champ.

But then there was the issue with controlling the alarm. The older driver let me send "ARM" commands to it. I finally figured out that I had to add HSM, which I didn't use before, to get the arming/disarming functionality. Also had to configure it. To control it I used virtual buttons. I can tell Alexa to "turn on the alarm switch" which sets a virtual contact that then pushes a button that is configured in HSM.

My debounce logic in the post that I did in the main board, fixes HSM status with a dervied status that I can use to control other things based on the alarm state.

One 'gotcha' was that I didn't realize the geeky names that I used in the driver setup for zones would be the actual device names. I started configuring each device label but went back and erased my pretty device labels in each child device and their device name and put the pretty names in the zone name configuration. To my horror, that caused ALL the device names and labels to be blank! They didn't auto re-populate! Fortunately the zone number was in the identifier and I figured out how to fill in the names into 'device name' which matched what happened in the original configuration.

From that, suggestions:

1.) Give folks a 'heads up' about needing to use HSM. That's a major difference from the prior driver which let you do commands to the drive to arm/disarm the system. Come configuration hints on what minimum config is needed in HSM would help as well as a 'virtual button' hint for doing arming/disarming easily/programmatically.

2.) If the child 'device name' is blank, take it from the zone label.

3.) You CAN run dual EVL boards from Eyezon on the security system. Just set them up with different keypad and IP addresses. It's a nice way to transition and it;'s not bad to have a spare board on hand.

4.) HSM integration between the two drivers may conflict if you use the 2 board approach. Disable it on the driver that you're coming from to proceed.

5.) Would be nice to NOT have to use HSM but be able to send commands to the driver for arming/disarming, etc., just like the older driver.

Hope that helps you ponder about this and provides some useful input.

The driver is great, seems to have faster response than the prior one. So all positive for now.

To recap -

  • Folks need a multi-partition set-up to be able to use sensors for control and monitoring when the Vista is armed and would normally not report in non-alarm sensors in an armed partition. (i.e. motion sensors controlling lights inside when the system is armed.)
  • The biggest issue with the system reporting things in a multi-partition is that it reports on partition 1 then 2 but there is no differentiation on reporting where the status is coming from. 1.) Overall alarm status toggles between two partitions, one armed, one disarmed, how do you get status if there's no partition indicator? The answer is that you wait until both report (two 10 second intervals) disarmed and you can KNOW that it's disarmed. 2.) Sensors report in both partitions, differently. A contact sensor will report one status for the actual partition that it's in and a default status for the partition that it's not in. (i.e. one good status and one bad). Only when the status matches is it valid.

As a result, you need to go through complicated gymnastics (and chew up hub time) to wait for conditions to compare and actually get status.

Minimally if the driver could at least just report armed status in Partition 1 and toss Partition 2, that would be great.

Ideally the driver would only return the status for devices that are assigned to a partition and toss the return from the partition that the zone wasn't assigned to.

All that being said, I've migrated to the new driver - I LIKE IT! It seems to function more efficiently. However I'm chewing up hub time monitoring. I also figured out how to get status from the driver and set arm/away so I've been able to ditch using HSM which would randomly disarm the system. (I think because it didn't understand two partitions.)

How I 'set' an alarm function with Hubitat:

How I query in a conditional:

Select "Custom Attribute" and the Envisilink panel: