Package: visonicalarm
Visonic Alarm LibraryHi and welcome! Click the button below if you enjoy this library and want to support my work. A lot of coffee is consumed as a software developer you know 😁Needless to say, this is completely voluntary.IntroductionA simple library for the Visonic PowerMaster API written in Python 3.It's built using same technique used in the Visonic-Go app (a REST API). So if you can use the phone app to connect to your alarm system, the chances are you can use this library as well. I have developed and tested it with a Visonic PowerMaster-10 using a PowerLink 3 IP module.It is probably also compatible with more devices from the PowerMaster family (PM-10, PM-30, PM-33, PM-360 and PM-360-R) but this has not been confirmed. The app also support Bentel (BW-30 and BW-64) and DSC (WP8010, WP8030 and WP8033), so these alarm system might work as well. Any feedback is welcome.DemoHere is a small command-line demo application showing the most basic options that this library support. It allows you to arm and disarm the alarm system as well as show a list of devices and detailed status information.Check out the source code to the demo here:alarm-control-demo.py.API version supportFinally my alarm company has upgraded their PowerManage REST API to version 9.0 so I could upgrade the Visonic Alarm library to support it.The upgrade from API version 4.0 to 9.0 was amajor upgradewhich broke more or less the entire Visonic Alarm library. I had to rewrite large portions of the code base which means that the latest version (3.x) of Visonic Alarm for Python 3 isnot backwards compatiblewith the previous versions. One of the large changes is that the API now require two sets of authentication.First withemail and passwordagainst the API server.And then thepanel serialandmaster codebetween the API server and your alarm panel.Some other changes are the way we arm and disarm the alarm system (endpoint changes). The data structures returned by the API server also differs a bit so almost all of the classes (Status,Device,Event,Trouble,...) have been updated to reflect these changes. See the examples in the rest of this document on how to use them.Library compatibility:9.0 - Compatible (this is the version the library is developed against)8.0 - Seems to be compatible when testing but needs user feedback7.0 - Seems to be compatible when testing but needs user feedback4.0 - Use the previous version of the library (2.0.1), see belowNevertheless, the library is still really easy getting started with.Need support for API 4.0?Even though the latest version of the library no longer support API version 4.0 you can still run it, simply install a previous version:pip install visonicalarm==2.0.1The documentation for this version can be foundhere.InstallationInstall the latest version withpip:pip install visonicalarmBasicsSetupUse the same settings you are using when logging in to the phone app.fromvisonicimportalarmhostname='your.alarmcompany.com'user_code='1234'app_id='00000000-0000-0000-0000-000000000000'panel_id='123ABC'user_email='user@example.com'user_password='An.Extremely.Long.Random.and.Secure.Password!'alarm=alarm.Setup(hostname,app_id)Theapp_idis a UUID (UniversallyUniqueIDentifier) that should be unique to each app communicating with the API server.Create a UUID with a simple one liner:python -c "import uuid; print(uuid.uuid4())"This will output a UUID (for example:e9bce150-57c9-47b9-8447-129158356c63) that can be used to replace the zeroedapp_idin the example above.It's important that you create an account in the app prior to setting up the library.All of the following code assume you have completed the Setup step prior to calling any of the methods.API version selectionIn thealarm.Setup()call the library checks which version(s) the API server support andautomaticallyselects the latest version by default.The automatic version selection can be overridden by calling theset_rest_version()methodbeforecallingauthenticate()andlogin().alarm.set_rest_version("9.0")Find out which version(s) of the API your alarm company support by calling theget_rest_versions()method.print('Supported REST API version(s): '+', '.join(alarm.get_rest_versions()))AuthenticateThe next step is toauthenticateyourself against the API server with an email address and a password. This is done using the same email and password beeing used when logging in to the phone app.alarm.authenticate(user_email,user_password)Note that this method will raise an exception if the authentication fails. See exceptions section below.LoginOnce the authentication has succeeded, it's time to establish a connection between the API server and the alarm panel.alarm.panel_login(panel_serial,user_code)Thepanel_serialis the ID of the panel (a hexadecimal number like1A2B3C) and theuser_codeis the master code (it's important to use the master code).Note that this method will raise an exception if the login fails. See exceptions section below.ExceptionsAll of the methods callable from the library will throw exceptions on failure. A full list of exceptions can be foundhere.fromvisonic.exceptionsimport*...try:alarm.panel_login(panel_serial,user_code)exceptUserCodeIncorrectErrorase:print(e)Printing Objects and PropertiesThe objects representing various entities in the alarm system can be output with theprint()method for easy inspection of its properties.As an example, you can output the properties of a user object by passing it to theprint()method:print(user)# Output: <class 'visonic.classes.User'>: {'id': 1, 'name': 'John Doe', 'email': 'john@doe.com', 'partitions': [1, 2, 3, 4, 5]}Also, the properties are easily accessed from the object:print('User ID:    '+str(user.id))print('User Name:  '+user.name)print('Email:      '+user.email)print('Partitions: '+str(user.partitions))This is the same for all object classes in the library: Users, devices, events, locations, troubles, and so on...Panel InitializationBefore connecting to an alarm panel it is necessary to associate it to your user account. If you want to know which panels are already associated with your account your can call theget_panels()method. There are methods to add, rename and delete (unlink) alarm panels.Add a panelTo add a new panel to your account, call thepanel_add()method. You have to provide a validpanel serialnumber and themaster user codein order for the process to complete successfully.alias='My House'panel_serial='123ABC'master_user_code='1234'alarm.panel_add(alias,panel_serial,master_user_code)Important:You must use themaster user codefor this to work.Rename a panelTo rename an existing alarm panel, use thepanel_rename()method.panel_serial='123ABC'alias='House'alarm.panel_rename(panel_serial,alias)Unlink a panelTo remove or unlink an alarm panel from your user account you use thepanel_unlink()method.panel_serial='123ABC'user_password='An.Extremely.Long.Random.and.Secure.Password!'app_id='00000000-0000-0000-0000-000000000000'alarm.panel_unlink(panel_serial,user_password,app_id)This only removes the link between your alarm system and the API server. Establish the link again by callingpanel_add().Panel ControlAccess ControlIn order for a user to login to the alarm system we have tograntthe user access to it. Also, if we want to prevent a currently active user from logging in we canrevokeits access.Grant access by callingaccess_grant(user_id, email), whereuser_idis the ID of the user andemailis the email address the user will be using to login to the system.alarm.access_grant(3,'user@example.com')Revoke access by callingaccess_revoke(user_id), whereuser_idis the ID of the user that we no longer want to access the system.alarm.access_revoke(3)Read more about how to find user account informationhere.Alarm PanelAfter calling thelogin()method it takes a few moments for the API server to connect to the alarm panel in your house. To check of the connection has been made, call theconnected()method:ifalarm.connected():print('Alarm Panel connected')else:print('Alarm Panel disconnected')Use theconnected()method to make sure you are connected to the alarm panel before calling arm/disarm methods to avoid exceptions.CamerasA camera is defined in theCameraclass and contains some basic information.Get alistof all cameras by calling theget_cameras()method.forcamerainalarm.get_cameras():print(camera)Output:<class 'visonic.classes.Camera'>: {'location': 'Basement', 'partitions': [1], 'preenroll': False, 'preview_path': None, 'status': 'FAILED', 'timestamp': None, 'zone': 4, 'zone_name': 'Basement'}
<class 'visonic.classes.Camera'>: {'location': 'Garage', 'partitions': [1], 'preenroll': False, 'preview_path': None, 'status': 'FAILED', 'timestamp': None, 'zone': 9, 'zone_name': 'Garage'}
...It's not fully clear what this information should be used for, but I suspect it's used for downloading images. Sadly this functions is locked on my alarm system so I can't test it.DevicesThese are the devices connected to your alarm system (contacts, cameras, keypads, and so on).A device is defined in theDevicebase class and, more specifically, in one of its sub-classes (CameraDevice,ContactDevice,GenericDevice,GSMDevice,KeyFobDevice,PGMDeviceandSmokeDevice).Get alistof all devices by calling theget_devices()method.fordeviceinalarm.get_devices():print(device)Output:<class 'visonic.devices.ContactDevice'>: {'device_number': 14, 'device_type': 'ZONE', 'enrollment_id': '100-0305', 'id': 12340, 'name': '', 'partitions': [1], 'preenroll': False, 'removable': True, 'renamable': True, 'subtype': 'CONTACT', 'warnings': None, 'zone_type': 'PERIMETER', 'location': 'Garage', 'soak': False}
<class 'visonic.devices.CameraDevice'>:  {'device_number': 15, 'device_type': 'ZONE', 'enrollment_id': '120-2041', 'id': 12341, 'name': '', 'partitions': [1], 'preenroll': False, 'removable': True, 'renamable': True, 'subtype': 'MOTION_CAMERA', 'warnings': None, 'zone_type': 'INTERIOR_FOLLOW', 'location': 'Vardagsrum', 'soak': False, 'vod': {}}
<class 'visonic.devices.SmokeDevice'>:   {'device_number': 16, 'device_type': 'ZONE', 'enrollment_id': '300-3546', 'id': 12343, 'name': '', 'partitions': [1], 'preenroll': False, 'removable': True, 'renamable': True, 'subtype': 'SMOKE', 'warnings': None, 'zone_type': 'FIRE', 'location': 'Vardagsrum', 'soak': False}
...Zone BypassingA device with adevice_typeofZONEcan bebypassed(which basically disables the sensor and prevent it from triggering) by calling theset_bypass_zone(zone, set_enabled)method. Thezoneparameter refers to the device number of the device andset_enabledis used to enable or disable the bypass functionality.This method only works if the alarm panel has zone bypassing enabled and the device supports it.Enable zone bypass for device number 1:token=alarm.set_bypass_zone(1,True)Disable zone bypass for device number 1::token=alarm.set_bypass_zone(1,False)Note that this method returns aprocess token. Check the status of the request with the methodget_process_status().EventsEvents are generated when the alarm system is armed, disarmed, phone line changes (GSM), and so on.An event is defined in theEventclass. Get alistof all events by calling theget_events()method.foreventinalarm.get_events():print(event)Output:<class 'visonic.classes.Event'>: {'id': 333801, 'type_id': 89, 'label': 'DISARM', 'description': 'Disarm', 'appointment': 'Mikael Schultz', 'datetime': '2022-09-11 06:59:08', 'video': False, 'device_type': 'USER', 'zone': 1, 'partitions': [1], 'name': 'Mikael Schultz'}
<class 'visonic.classes.Event'>: {'id': 334310, 'type_id': 86, 'label': 'ARM', 'description': 'Arm Away', 'appointment': 'User 2', 'datetime': '2022-09-11 07:55:55', 'video': False, 'device_type': 'USER', 'zone': 2, 'partitions': [1], 'name': None}
...FeaturesCheck which features are enabled and available for interaction via the API. Among other things you can find out if your alarm system has partitions enabled and if you can turn the siren on or off.The features of the alarm system is defined in theFeatureSetclass. Get the available features by calling theget_feature_set()method.features=alarm.get_feature_set()print(features)Output:<class 'visonic.classes.FeatureSet'>: {'events_enabled': True, 'datetime_enabled': False, 'partitions_enabled': False, 'partitions_has_labels': False, 'partitions_max_count': 3, 'devices_enabled': True, 'sirens_can_enable': True, 'sirens_can_disable': True, 'home_automation_devices_enabled': True, 'state_enabled': True, 'state_can_set': True, 'state_can_get': True, 'faults_enabled': True, 'diagnostic_enabled': False, 'wifi_enabled': False}Hint:Checkalarm.get_feature_set().partitions_enabledto see if the alarm system is partitioned.LocationsA location is defined in theLocationclass. Get alistof all locations by calling theget_locations()method.forlocationinalarm.get_locations():print(location)Output:<class 'visonic.classes.Location'>: {'id': 0, 'name': 'Entry', 'is_editable': False}
<class 'visonic.classes.Location'>: {'id': 1, 'name': 'Backdoor', 'is_editable': False}
...Panel InformationThe general panel information is defined in thePanelInfoclass. Get the panel information by calling theget_panel_info()method.panel_info=alarm.get_panel_info()print(panel_info)Output:<class 'visonic.classes.PanelInfo'>: {'current_user': 'master_user', 'manufacturer': 'Visonic', 'model': 'PowerMaster 10', 'serial': '123ABC'}PanelsA single alarm panel is defined in thePanelclass. Get alistof panels associated with your account by calling theget_panels()method.forpanelinalarm.get_panels():print(panel)Output:<class 'visonic.classes.Panel'>: {'panel_serial': '123ABC', 'alias': 'Home'}
<class 'visonic.classes.Panel'>: {'panel_serial': '456DEF', 'alias': 'Cabin'}Use this information to select an alarm panel to connect to when callinglogin().PasswordThe password of a user can be reset using the API as well. This is done in two steps.Step 1Call thepassword_reset(email)method which takes anemailaddress as the only parameter. A password reset email will be sent to this address in which apassword reset codeis provided.alarm.password_reset('username@example.com')Step 2Call thepassword_reset_complete(reset_password_code, new_password)method which takes two arguments. Thereset_password_codeis the code you received in the email message andnew_passwordis the new password to set on the user account. Make sure the password is complex, otherwise anNewPasswordStrengthError()exception will be raised.token=alarm.password_reset_complete('ADQRSESA54','This.is.a.Super.Mega.s3cure.p@ssw0rd!')print(f"User-Token: '{token}'")Output:User-Token: '125840b4-3028-4176-8a4f-6c705bcbbcaa'Thepassword_reset_complete()method will return a newuser tokenwhich I suspect should be used if you are changing the password of the user you are currently logged in with.Process InformationSome API methods return aprocess tokenas a return value. This makes it possible to find out how the call went and make you aware of potential errors that occured. The API methods returning a token seems to be the ones that change the state of the alarm system, such asarm_home(),arm_away()anddisarm().A process is defined in theProcessclass. Get alistof all processes associated with aprocess tokenby calling theget_process_status()method.token=alarm.disarm()forprocessinalarm.get_process_status(token):print(process)Output:<class 'visonic.classes.Process'>: {'token': '346eca73-1316-4a1e-b922-4b2061d79b71', 'status': 'start', 'message': '', 'error': None}SirenYou can turn on and off the siren connected to the alarm system by calling theactivate_siren()anddisable_siren()methods. Both methods return aprocess tokenwhich can be inspected with theget_process_status()method.alarm.activate_siren()...alarm.disable_siren()Warning:Make sure the building is empty before testing theactivate_siren()method since it willmake a lot of noise!StatusThe status of the alarm system is defined in theStatusclass. Get the current status by calling theget_status()method.This method will allow you to view the current status of the PowerLink 3 IP module (bba), mobile module (gprs) as well as all partitions (defined in thePartitionclass) in the alarm system.If you don't have a multi partition alarm system, the-1partition will always be used.status=alarm.get_status()print(status)Output:<class 'visonic.classes.Status'>: {'connected': True, 'bba_connected': True, 'bba_state': 'online', 'gprs_connected': False, 'gprs_state': 'online', 'discovery_completed': True, 'discovery_stages': 17, 'discovery_in_queue': 0, 'discovery_triggered': None, 'partitions': [Partition(id = -1, state = 'DISARM', status = '', ready = True, options = [])], 'rssi_level': 'ok', 'rssi_network': 'Unknown'}Since the partitions are located in a list you can iterate over them like this:forpartitioninstatus.partitions:print(partition)Output:<class 'visonic.classes.Partition'>: {'id': -1, 'state': 'DISARM', 'status': '', 'ready': True, 'options': []}Single partition system?Just runprint(status.partitions[0].state)to get the current arm state.TroublesWhen something is in need of attention a trouble is triggered. It might be a door that's open or the control panel running on battery when a power outage occurs.A trouble is defined in theTroubleclass. Get alistof all troubles by calling theget_troubles()method.fortroubleinalarm.get_troubles():print(trouble)Output:<class 'visonic.classes.Trouble'>: {'device_type': 'CONTROL_PANEL', 'location': None, 'partitions': [1], 'trouble_type': 'AC_FAILURE', 'zone': None, 'zone_name': None, 'zone_type': None}
<class 'visonic.classes.Trouble'>: {'device_type': 'ZONE', 'location': 'Front door', 'partitions': [1], 'trouble_type': 'OPENED', 'zone': 3, 'zone_name': '', 'zone_type': 'PERIMETER'}UsersA user is defined in theUserclass. Get alistof all users by calling theget_users()method.foruserinalarm.get_users():print(user)Output:<class 'visonic.classes.User'>: {'id': 1, 'name': 'John Doe', 'email': 'john@doe.com', 'partitions': [1, 2, 3, 4, 5]}
<class 'visonic.classes.User'>: {'id': 2, 'name': '', 'email': '', 'partitions': [1]}
...Change the name of a userIt's possible to change the name of a user by calling theset_name_user(user_id, name)method. You have to provide theidof the user as well as the newname.token=alarm.set_name_user(4,'bitcanon')Note that this method returns aprocess token. Check the status of the request with the methodget_process_status().result=alarm.get_process_status(token)print(result)Output:[Process(token = '4efc2e3a-13ef-47aa-8d25-92eb8dfa2791', status = 'succeeded', message = '', error = 'None')]Set the user codeTo set or change a user code you use theset_user_code(user_id, user_code)method. This method has several usages; of course to change the code of a user, but also toadd and remove a user. There is a finite number of user accounts in the alarm panel (8 in my case) and they are considered to exist if they have a user codenotequal to0000. So in short, add a user by setting a user code, and remove a user by setting the user code to0000.Note that this method returns aprocess token. Check the status of the request with the methodget_process_status().Add user or change user code:token=alarm.set_user_code(4,'8675')Remove user:token=alarm.set_user_code(4,'0000')Wakeup SMSGet the information needed to send a wakeup SMS, which is defined in theWakeupSMSclass. Get thephone_numberandmessagerequired by calling theget_wakeup_sms()method.sms=alarm.get_wakeup_sms()print(sms)Output:<class 'visonic.classes.WakeupSMS'>: {'phone_number': '+467190123456789', 'message': 'CONNECT;ABCD;AB-1;SEQ-1234;'}Arming and DisarmingThere are two ways to arm your alarm system.Arm Home:This will arm your perimeter protection (often doors and windows). You can still move around inside the house.Arm Away:This will arm the entire alarm system (doors, windows, motion, cameras, etc). Moving around in the house will trigger the alarm to go off.Arm HomeTo arm the alarm system inhome modejust call thearm_home()method.alarm.arm_home()When using a multi partition alarm system, just pass the partition ID as an argument to thearm_home()method.alarm.arm_home(partition=2)Poll thestateproperty of your partition in theget_status()method to watch the state changing.alarm.get_status().partitions[0].state# Output: 'HOME'Arm AwayTo arm the alarm system inaway modejust call thearm_away()method.alarm.arm_away()When using a multi partition alarm system, just pass the partition ID as an argument to thearm_away()method.alarm.arm_away(partition=2)Poll thestateproperty of your partition in theget_status()method to watch the state changing.alarm.get_status().partitions[0].state# Output: 'AWAY'DisarmTo disarm the alarm system just call thedisarm()method.alarm.disarm()When using a multi partition alarm system, just pass the partition ID as an argument to thedisarm()method.alarm.disarm(partition=2)Poll thestateproperty of your partition in theget_status()method to watch the state changing.alarm.get_status().partitions[0].state# Output: 'DISARM'ExamplesFind more examples here:/visonicalarm/examples