-
Notifications
You must be signed in to change notification settings - Fork 2
/
alarm.py3
183 lines (154 loc) · 6.19 KB
/
alarm.py3
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/python
import re
import codecs
import mechanicalsoup
import argparse
import logging
import json
import sys
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
logging.basicConfig(filename="alarm.log")
class AlarmDotCom(object):
def __init__(self, username, password):
self.username = username
self.password = password
self.state = 'UNKNOWN'
self.browser = None
self.panel_id = None
self.logged_in = False
def _get_browser(self):
if not self.browser:
br = mechanicalsoup.StatefulBrowser(
soup_config={'features': 'lxml'},
raise_on_404=True,
user_agent='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.167 Safari/537.36'
)
# br.set_verbose(8)
self.browser = br
return self.browser
def _login(self):
if not self.logged_in:
br = self._get_browser()
response = br.open( "https://www.alarm.com/login.aspx" )
location = br.get_url()
content = br.get_current_page().decode("utf-8")
session = re.search(r'\/(\(S[^\/]+)\/', location)
if session:
session = session.group(1)
viewstate = re.search(r'name="__VIEWSTATE".*?value="([^"]*)"', content)
if viewstate:
viewstate = viewstate.group(1)
log.debug("VIEWSTATE is %s", viewstate)
viewstategenerator = re.search(r'name="__VIEWSTATEGENERATOR".*?value="([^"]*)"', content)
if viewstategenerator:
viewstategenerator = viewstategenerator.group(1)
log.debug("VIEWSTATEGENERATOR is %s", viewstategenerator)
eventval = re.search(r'name="__EVENTVALIDATION".*?value="([^"]*)"', content)
if eventval:
eventval = eventval.group(1)
log.debug("EVENTVALIDATION is %s", eventval)
self.logged_in = None
try:
postresponse = br.post('https://www.alarm.com/web/Default.aspx',
data={'__VIEWSTATE': viewstate, '__EVENTVALIDATION': eventval, '__VIEWSTATEGENERATOR': viewstategenerator, 'IsFromNewSite': '1', 'JavaScriptTest': '1', 'ctl00$ContentPlaceHolder1$loginform$hidLoginID': '', 'ctl00$ContentPlaceHolder1$loginform$txtUserName': self.username, 'ctl00$ContentPlaceHolder1$loginform$txtPassword': self.password, 'ctl00$ContentPlaceHolder1$loginform$signInButton': 'Logging In...', 'ctl00$bottom_footer3$ucCLS_ZIP$txtZip': 'Zip Code'})
log.debug("Post login URL is %s", postresponse.url)
self.logged_in = True
except:
e = sys.exc_info()[0]
log.debug("got an error %s", e)
return self.logged_in
def _get_panel(self):
if not self.panel_id:
result = self.api_call('systems/availableSystemItems')
user_id = result['data'][0]['id']
log.debug('user id is '+user_id)
result = self.api_call('systems/systems/'+user_id)
panel_id = result['data']['relationships']['partitions']['data'][0]['id']
log.debug('panel id is '+panel_id)
self.panel_id = panel_id
return self.panel_id
def api_call(self, apiUrl, apiMethod='GET', apiBody=''):
br = self._get_browser()
if not self.logged_in:
self._login()
log.debug('Logged In: '+str(self.logged_in)) # True
cookiejar = br.get_cookiejar()
ajaxkey = None
for cookie in cookiejar:
# log.debug(cookie.name+': '+cookie.value)
if 'afg' == cookie.name:
ajaxkey = cookie.value
log.debug("ajaxkey is %s", ajaxkey)
result = None
try:
apiCall = br.request(method=apiMethod,
url='https://www.alarm.com/web/api/'+apiUrl,
data=apiBody,
headers={'ajaxrequestuniquekey': ajaxkey, 'Accept': 'application/vnd.api+json', 'Content-Type': 'application/json; charset=UTF-8'}
)
responsecontent = apiCall.content.decode("utf-8")
log.debug("Post command JSON is %s", responsecontent)
result = json.loads(responsecontent)
log.debug(result)
except:
log.debug('apiUrl: '+apiUrl)
log.debug('apiBody: '+apiBody)
e = sys.exc_info()[0]
log.debug("got an error %s", e)
return result
def refresh(self):
return self.command('STATUS')
def disarm(self):
return self.command('DISARM')
def arm_stay(self, forceBypass=False, noEntryDelay=False, silentArming=True):
return self.command('ARMSTAY', forceBypass, noEntryDelay, silentArming)
def arm_away(self, forceBypass=False, noEntryDelay=False, silentArming=True):
return self.command('ARMAWAY', forceBypass, noEntryDelay, silentArming)
def command(self, command, forceBypass=False, noEntryDelay=False, silentArming=True):
states = ['UNKNOWN', 'DISARM', 'ARMSTAY', 'ARMAWAY']
commands = {'ARMSTAY': '/armStay', 'ARMAWAY': '/armAway', 'DISARM': '/disarm', 'STATUS': ''}
panel_id = self._get_panel()
command = command.upper()
apiUrl = 'devices/partitions/'+panel_id+commands[command]
if('STATUS' == command):
apiMethod = 'GET'
apiBody = ''
else:
apiMethod = 'POST'
apiBody = '{"forceBypass":'+str(forceBypass).lower()+',"noEntryDelay":'+str(noEntryDelay).lower()+',"silentArming":'+str(silentArming).lower()+',"statePollOnly":false}'
result = self.api_call(apiUrl, apiMethod, apiBody)
currentstate = result['data']['attributes']['state']
self.state = states[currentstate]
panel_id = result['data']['relationships']['stateInfo']['data']['id']
log.debug ("Current state is "+states[currentstate])
log.debug ("panel_id is "+panel_id)
return self.state
def main():
parser = argparse.ArgumentParser(description='Command line interface to alarm.com panels')
parser.add_argument('-u', '--username',
metavar='username',
help='alarm.com username')
parser.add_argument('-p', '--password',
metavar='password',
help='alarm.com password')
parser.add_argument('operation',
choices = ['armstay', 'armaway', 'disarm', 'status'],
help='panel operation command: armstay, armaway, disarm or status')
parser.add_argument('-s', '--silent',
action='store_true',
help='enable silent arming')
parser.add_argument('-b', '--bypass',
action='store_true',
help='force bypass of open sensors')
parser.add_argument('-n', '--nodelay',
action='store_true',
help='enable arming with no entry delay')
#production:
args = parser.parse_args()
#development:
# args = parser.parse_args(['status','--username=username','--password=password'])
alarm = AlarmDotCom(args.username, args.password)
print("current status is " + alarm.command(args.operation, args.bypass, args.nodelay, args.silent))
if __name__ == '__main__':
main()