Smanos w100 with HomeAssistant

Control the amanos bulgar alarm panel modes and receive swnsor events with home assistant.
Available info
Smanos as a company is a rebranding of Chinese chuango for the western market.
w100 alarm panel is a WiFi panel and can fallback to landline call “notifications”
It’s closed source and exposes no API
The company stoped its notifications server in early 2025 and removed the w100 from playstore and Apple Store .
The android app can be easily side loaded as an apk
The iOS app is only available for you device if you had it installed before the removal .
We can achieve some major functionality of the apps like setting mode to home, arm , disarm and more importantly we can achieve notification delivery on alarm triggering using a python socket listener and home assistant .
Thankfully the panel has the tcp port 6003 open and we can capture data from it and send commands to it .
1
2
3
4
5
6
7
8
9
10
11
sudo nmap -p- 192.168.1.18
Starting Nmap 7.93 ( https://nmap.org ) at 2025-08-27 20:51 EEST
Nmap scan report for 192.168.1.18
Host is up (0.055s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
80/tcp open http
60003/tcp open unknown
MAC Address: AC:CF:23:37:1F:54 (Hi-flying electronics technology)
Nmap done: 1 IP address (1 host up) scanned in 254.44 seconds
Due to a CVE we already know that we can send a payload to 6003 set up WiFi and set the panel mode to OFF .
https://github.com/lodi-g/CVE-2019-13361/blob/75712ea4d6308d2c2d5bc3693b27170da6869cc9/poc.py
The scripts already mentions the protocols CGWPCS48 and CGWPCS53
I Decompiled the android app. Uploaded the relative java class containg the payload info
https://pastebin.com/2J7aGxti
From there I listed all protocols mentioned in the Java class
1
2
3
4
5
6
7
8
9
10
11
CGWPCS48 Set WiFi
CGWPCS51 Parameter
CGWPCS52 RemoveAlarm
CGWPCS53 SetMode
CGWPCS54 QueryJion
CGWPCS56 NewVersion
CGWPCS58 SysnTime
CGWPCS49 ServerRegister
CGWPCS50 InsideLogin
CGWPCS55 ClearUser
CGWPCSUDP UDP Data (broadcast protocol)
I didn’t manage to make use of any ather protocols other than set WiFi and set mode .
I did try to remove accessories with RemoveAlarm with no luck .
The CVE script
Running the CVE script:
1
python3 poc.py 192.168.1.18 wifi_ssid wifi_password
Return:
1
set_wifi: sending: b'CGWPCS4805wifi_ssid320wifipassword20250824165905\r\n'
CGWPCS48 is set WiFi code
05 is action code? In this case setup?
20250824165905 is the timestamp
24 aug 2025 16:59 and 05 sec
then it prints
1
2
set_wifi: receiving: b'CGWPSC03030123456A*
*\r'
which is an Acknowledge message with the device id 1234567A
then it prints our device id and the disarm commands which we’ll need in our following scripts
1
2
3
4
set_wifi: device_id: b'1234567A**'
disarm: sending: b'CGWPCS5300001234567A*
*0'
CGWPCS53 is the Set mode class
0000 is action code?
Disarm command **0 and last it prints
1
disarm: receiving: b'CGWPSC5300001234567A**1001\r'
again an Acknowledge **1001
Creating arm disarm and home mode commands
So: The command for disarm is
1
2
b'CGWPCS5300001234567A*
*0'
For arm
1
2
3
‘b'CGWPCS5300001234567A*
*1'’
And for home mode
1
2
‘b'CGWPCS5300001234567A*
*2'’
What change is:
**0 disarm **1 arm **2 home
Mind the 1234567A which is your personal device id
So:
The scripts for home assistant will be something like:
Arm:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/bash
ip="192.168.1.18"
ssid="wifi_ssid"
pswd="wifi_password"
port=60003
payload="CGWPCS530000DEVICE_ID**1"
# Function to send payload and exit after 10 second
send_payload() {
{ echo -n "$payload" | nc -w 10 "$ip" "$port"; } > /dev/null 2>&1 &
sleep 1
kill %1
}
# Main
send_payload
Create 3 identical scripts Only change the
**0 disarm **1 arm **2 home
In
payload=”CGWPCS530000DEVICE_ID**1”
Setting up home assistant
Add your mode script to config/shell_scripts , create dir if missing
And add the paths to configuration.yaml
1
2
3
4
shell_command:
w100_home: /config/shell_scripts/w100_home.sh
w100_disarm: /config/shell_scripts/w100_disarm.sh
w100_arm: /config/shell_scripts/w100_arm
Create the Dashboard:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
type: horizontal-stack
title: Smanos W100
cards:
- type: button
name: Arm
icon: mdi:shield-lock
tap_action:
action: call-service
service: script.arm_alarm
show_name: true
show_icon: true
styles:
card:
- border-radius: 12px
- background-color: '#2c3e50'
- color: white
- box-shadow: 2px 2px 6px rgba(0,0,0,0.3)
- transition: all 0.3s ease
- type: button
name: Disarm
icon: mdi:shield-off
tap_action:
action: call-service
service: script.disarm_alarm
show_name: true
show_icon: true
styles:
card:
- border-radius: 12px
- background-color: '#34495e'
- color: white
- box-shadow: 2px 2px 6px rgba(0,0,0,0.3)
- transition: all 0.3s ease
- type: button
name: Home
icon: mdi:home
tap_action:
action: call-service
service: script.home_alarm
show_name: true
show_icon: true
styles:
card:
- border-radius: 12px
- background-color: '#1abc9c'
- color: white
- box-shadow: 2px 2px 6px rgba(0,0,0,0.3)
- transition: all 0.3s ease
receive panel event notifications and deliver notifications to mobile devices
W100 panel as we already saw has port 6003 open and we send our payload to it to perform mode change actions to it.
- Listening panel broadcasts
We will use a simple tcp listener , a python socket library in our example to grab traffic from the port .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3
import socket
def listen_notifications(s):
while True:
try:
data = s.recv(1024)
if not data:
break
print(f"Notification received: {data}")
except Exception as e:
print("Error receiving:", e)
break
def main():
ip = "192.168.1.100" # replace with your panel IP
port = 60003 # default port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((ip, port))
print("Connected, waiting for notifications...")
listen_notifications(s)
if __name__ == '__main__':
main()
Triggering a sensor will the panel mode armed or home will print
1
Notification received: b'CGWPSC01010112345A6**020202202508231910510\r'
CGWPSC01 Protocol header (type of message, probably “event/notification”).
010101 event type (sensor triggered)
12345A6 device ID (matches what I extracted in my Wi-Fi setup step).
020202 sensor number ( when you add sensors to the panel they get an increasing number
20250823191051 timestamp: 2025 → year 08 → month 23 → day 19:10:51 → time (h/m/s)
0\r → End of packet marker.
Or maybe 510 is the current sensor event counter 0-999 and the end packet marker is just \r
Other message types
- Heartbeat
A 2-5 minute interval CGWPSC03030DEVICE_ID** code is broadcasted from the panel We’ll use this interval as a failsafe keepalive mechanism for the listener script
- Arm , disarm , home modes acknowledge
The panel sends a succes/acknowledge message every time mode change
1
2
b'CGWPSC5300003000125A**
1001\r'
- Finalising the listener script
We need a tcp listener that runs 27/4 with low memory and processor load .
We also need to filter out sensors by their increasing number . That’s panel specific you have to remember the sequence you added your sensor to the panel . So in my case it’s
1
2
3
4
5
01 Beam sensor triggered
02 Contact sensor triggered
03 Leaving room PIR triggered
04 Curtain PIR balcony triggered
06 Curtain PIR bedroom triggered
And finally we need to filter out heartbeat and mode change acknowledge messages
In addition to that I’ll post 2 notification methods
One simple using ntfy public server And one using home assistant companion app notifications
- NTFY
Extending the listener script to send ntfy notifications is simple
Ntfy uses topics to deliver notifications the topic inane in the script have to Mach the topic you subscribe in your mobile app (ios&android)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python3
import socket
import requests
def send_ntfy(message, topic="myalarm"):
url = f"https://ntfy.sh/{topic}"
try:
r = requests.post(url, data=message.encode("utf-8"))
print(f"Sent notification: {message} (status {r.status_code})")
except Exception as e:
print("Error sending ntfy notification:", e)
def parse_notification(data):
msg = data.decode(errors="ignore").strip()
if msg.startswith("CGWPSC01"):
device_id = msg[12:20]
event_code = msg[22:28] # e.g. 020202
timestamp = msg[28:42] # YYYYMMDDHHMMSS
events = {
"020202": "Magnetic contact triggered",
"010101": "Beam sensor triggered",
"030303": "living room sensor triggered",
# Add more as you learn the codes
}
return f"{events.get(event_code, f'Unknown event ({event_code})')} at {timestamp}", msg
return "Unparsed event", msg
def listen_notifications(s):
while True:
try:
data = s.recv(1024)
if not data:
break
parsed, raw = parse_notification(data)
print(f"Notification received: {raw}")
print(f"Parsed: {parsed}")
send_ntfy(parsed)
except Exception as e:
print("Error receiving:", e)
break
def main():
ip = "192.168.1.100" # <-- replace with your panel IP
port = 60003 # <-- default port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((ip, port))
print("Connected, waiting for notifications...")
listen_notifications(s)
if __name__ == '__main__':
main()
Edit the
1
2
3
4
5
6
events = {
"020202": "Magnetic contact triggered",
"010101": "Beam sensor triggered",
"030303": "living room sensor triggered",
# Add more as you learn the codes
}
Block so it matches your sensor numbers and description
Also edit the topic name to your desire under
1
2
def send_ntfy(message, topic="myalarm"):
url = f"https://ntfy.sh/{topic}"
Ntfy is easily selfhostable so you could use your own instance
- home assistant notification
Home assistant delivers notifications to http://192.168.1.24:8123/api/services/notify.mobile_app_my-user endpoint
In order to deliver notifications to multiple devices we have to create a notification group in config/configuration.yaml
1
2
3
4
5
6
notify:
- platform: group
name: family
services:
- service: mobile_app_one
- service: mobile_app_two
And use the http://192.168.1.24:8123/api/services/notify/family endpoint
app_one and app_two are the companion app device names for each device as shown under devices and services
We’ll also need a home assistant long-lived access token . You can create one from user>security
And finally the extended script to cover multiple home assistant notification and per sensor discrimination and heartbeat / panel modes filtering
- Failsafe
Rely on the socket.timeout: The socket.settimeout() will be the primary mechanism for detecting a lack of communication. We’ll set it to be slightly longer than the panel’s heartbeat interval (e.g., 11 minutes).
Use the systemd watchdog as the ultimate failsafe: WatchdogSec will be set to an even higher value (e.g., 15 minutes). It will only be triggered if the entire script freezes and stops processing the socket.timeout exception, or if the system itself is too busy to run the script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env python3
import socket
import requests
import time
import os
# Install this with `pip3 install sdnotify`
try:
from sdnotify import SystemdNotifier
except ImportError:
SystemdNotifier = None
print("sdnotify library not found. Systemd watchdog will not be used.")
# -------------------------
# Panel info
# -------------------------
PANEL_IP = "192.168.1.18"
PANEL_PORT = 60003
# Map sensor IDs to human-readable names
SENSORS = {
"01": "Beam sensor",
"02": "Contact sensor",
"03": "Curtain PIR bedroom",
"04": "Curtain PIR balcony",
"05": "Leaving room PIR",
}
# -------------------------
# Home Assistant info
# -------------------------
HA_URL = "http://192.168.1.24:8123/api/services/notify/family"
HA_TOKEN = "<REDACTED>..."
# Set timeouts based on the panel's heartbeat of ~10 minutes
PANEL_HEARTBEAT_INTERVAL = 600 # 10 minutes in seconds
SOCKET_TIMEOUT = PANEL_HEARTBEAT_INTERVAL + 60 # Give a 1-minute buffer
# -------------------------
# Send notification to HA
# -------------------------
def send_ha_notification(message):
"""Sends a notification to Home Assistant."""
headers = {
"Authorization": f"Bearer {HA_TOKEN}",
"Content-Type": "application/json",
}
payload = {"message": message}
try:
resp = requests.post(HA_URL, json=payload, headers=headers, timeout=5)
print(f"Sent HA notification: {message} (status {resp.status_code})")
except Exception as e:
print(f"Failed to send HA notification: {e}")
# -------------------------
# Parse sensor message
# -------------------------
def parse_sensor_message(data):
"""Parses raw sensor data to a human-readable name."""
try:
ascii_data = data.decode("utf-8", errors="ignore").strip()
idx = ascii_data.find("**")
if idx != -1 and len(ascii_data) > idx + 2:
sensor_code = ascii_data[idx+2:idx+4]
if not sensor_code or sensor_code == "\r":
return None
if sensor_code == "10" or ascii_data[idx+2:idx+6] == "1001":
return None
return SENSORS.get(sensor_code, f"Unknown sensor {sensor_code}")
except Exception as e:
print("Failed to parse sensor message:", e)
return None
# -------------------------
# Listener
# -------------------------
def listen_notifications():
"""Main function to listen for notifications from the alarm panel."""
notifier = SystemdNotifier()
if notifier:
notifier.notify("READY=1")
print("Systemd READY notification sent.")
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(SOCKET_TIMEOUT)
s.connect((PANEL_IP, PANEL_PORT))
print(f"Connected to panel at {PANEL_IP}:{PANEL_PORT}, listening...")
while True:
try:
data = s.recv(1024)
if not data:
print("Connection closed by peer.")
break
# A message was received, so the script is alive.
# Notify systemd to reset its watchdog timer.
if notifier:
notifier.notify("WATCHDOG=1")
print("Sent WATCHDOG ping.")
print(f"Notification received: {data}")
sensor_name = parse_sensor_message(data)
if sensor_name:
send_ha_notification(f"🚨 Sensor triggered: {sensor_name}")
except socket.timeout:
print("Socket timed out. No data received. Reconnecting...")
# This timeout is expected if a heartbeat is missed.
# Breaking the inner loop forces a reconnection attempt.
break
except Exception as e:
print(f"Socket receive error: {e}")
break
except Exception as e:
print(f"Listener error: {e}")
print("Attempting to reconnect in 5 seconds...")
time.sleep(5)
if __name__ == "__main__":
listen_notifications()
Map sensor IDs to human-readable names in:
1
2
3
4
5
SENSORS = {
"01": "semsor one",
"02": "sensor two",
"03": "sensor three",
}
Fill your home assistant instance url and port and you token in:
1
2
HA_URL = "http://192.168.1.24:8123/api/services/notify/family"
HA_TOKEN = "LONG-LIVED_TOKEN"
- systemd service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=W100 Alarm Panel Listener
After=network.target
[Service]
Type=notify
ExecStart=/usr/bin/python3 /run/media/ippo/TOSHIBA/home_assistant/listener.py
WorkingDirectory=/run/media/ippo/TOSHIBA/home_assistant
Restart=always
RestartSec=5
User=ippo
WatchdogSec=900
[Install]
WantedBy=multi-user.target
Use the filepaths for your script location and working dir
- Interactive buttons
In order to highlight the dejected button so that currently selected e is displayed we need a custom button card. For that we need hacs install. for how to install and enable hacs read https://ippocratis.github.io/assistant/ Downside is only mode changes using the scripts will be mirrored means that mode change from the panel itself or the remote control or the official apps are not. Reason is I don’t have an obvious way to capture mode changes. Panel indeed broadcast acknowledge messages but they are identical.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
type: horizontal-stack
title: Smanos W100
cards:
- type: custom:button-card
entity: input_select.w100_mode
name: Arm
icon: mdi:shield-lock
tap_action:
action: call-service
service: script.arm_alarm
show_state: false
show_name: true
show_icon: true
state:
- value: Arm
styles:
card:
- background-color: '#2c3e50'
- color: white
- operator: default
styles:
card:
- background-color: '#34495e'
- color: white
- type: custom:button-card
entity: input_select.w100_mode
name: Disarm
icon: mdi:shield-off
tap_action:
action: call-service
service: script.disarm_alarm
show_state: false
show_name: true
show_icon: true
state:
- value: Disarm
styles:
card:
- background-color: '#e74c3c'
- color: white
- operator: default
styles:
card:
- background-color: '#34495e'
- color: white
- type: custom:button-card
entity: input_select.w100_mode
name: Home
icon: mdi:home
tap_action:
action: call-service
service: script.home_alarm
show_state: false
show_name: true
show_icon: true
state:
- value: Home
styles:
card:
- background-color: '#1abc9c'
- color: white
- operator: default
styles:
card:
- background-color: '#34495e'
- color: white
Automation
- With home assistant mobile voice assistant using the scripts as actions .
Note that HA voice assistants need Local TTS and STT engines on the HASS server.
Read my quick how to selfhost them with docker here:
https://ippocratis.github.io/https://ippocratis.github.io/voice-assist/
Example HA conversation automation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
alias: W100 arm
description: ""
trigger:
- platform: conversation
command:
- alarm on
- Turn on alarm
- Turn-on alarm
- Turnon alarm
- Turn on the alarm
condition: []
action:
- service: shell_command.w100_arm
data: {}
mode: single
- With iOS shortcuts
To make modes available to the iOS home assistant shortcuts and control central I had to add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# W100 scripts
arm_alarm:
alias: "W100 Arm"
sequence:
- service: shell_command.w100_arm
disarm_alarm:
alias: "W100 Disarm"
sequence:
- service: shell_command.w100_disarm
home_alarm:
alias: "W100 Home"
sequence:
- service: shell_command.w100_home
To config/scripts.yaml