blob: d89e82726647e9d6f6b4f3d5047ab953330bccb3 [file] [log] [blame]
Hyunsun Moondb72b8f2020-11-02 18:03:39 -08001#!/usr/bin/env python3
Hyunsun Moon53097ea2020-09-04 17:20:29 -07002
3# Copyright 2020-present Open Networking Foundation
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License
16
17import sys
18import os
19import json
20import logging
21import enum
22import requests
23import time
24import serial
25from collections import namedtuple
26
27'''
28Simple script that checks Aether network operational status periodically
29by controlling the attached 4G/LTE modem with AT commands and
30report the result to the central monitoring server.
31'''
32
33
34CONF = json.loads(
35 open(os.getenv('CONFIG_FILE', "./config.json")).read(),
36 object_hook=lambda d: namedtuple('X', d.keys())(*d.values())
37)
38
39logging.basicConfig(
40 filename=CONF.log_file,
41 format='%(asctime)s [%(levelname)s] %(message)s',
42 level=logging.INFO
43)
44
45report = {
46 'name': CONF.edge_name,
47 'status': {
48 'control_plane': None,
49 'user_plane': None
50 }
51}
52
53
54class State(enum.Enum):
55 error = "-1"
56 disconnected = "0"
57 connected = "1"
58
59 @classmethod
60 def has_value(cls, value):
61 return value in cls._value2member_map_
62
63
64class Modem():
65 log = logging.getLogger('aether_edge_monitoring.Modem')
66
67 read_timeout = 0.1
68
69 def __init__(self, port, baudrate):
70 self.port = port
71 self.baudrate = baudrate
72 self._response = None
73
74 def connect(self):
75 self.serial = serial.Serial(
76 port=self.port,
77 baudrate=self.baudrate,
78 timeout=1)
79
80 def _write(self, command):
81 if self.serial.inWaiting() > 0:
82 self.serial.flushInput()
83
84 self._response = b""
85
86 self.serial.write(bytearray(command + "\r", "ascii"))
87 read = self.serial.inWaiting()
88 while True:
89 if read > 0:
90 self._response += self.serial.read(read)
91 else:
92 time.sleep(self.read_timeout)
93 read = self.serial.inWaiting()
94 if read == 0:
95 break
96 return self._response.decode("ascii").replace('\r\n', ' ')
97
98 def write(self, command, wait_resp=True):
99 response = self._write(command)
100 self.log.debug("%s: %s", command, response)
101
102 if wait_resp and "ERROR" in response:
103 return False, None
104 return True, response
105
106 def is_connected(self):
107 success, result = self.write('AT+CGATT?')
108 if not success or 'CGATT:' not in result:
109 return State.error
110 state = result.split('CGATT:')[1].split(' ')[0]
111 return State(state)
112
113 def close(self):
114 self.serial.close()
115
116
117def get_control_plane_state(modem):
118 # Delete the existing session
119 # "echo" works more stable than serial for this action
120 # success, result = modem.write('AT+CFUN=0')
121 logging.debug("echo 'AT+CFUN=0' > " + CONF.modem.port)
122 success = os.system("echo 'AT+CFUN=0' > " + CONF.modem.port)
123 logging.debug("result: %s", success)
124 if success is not 0:
125 msg = "Write 'AT+CFUN=0' failed"
126 logging.error(msg)
127 return State.error, msg
128
129 # Wait until the modem is fully disconnected
130 retry = 0
131 state = None
132 while retry < 5:
133 state = modem.is_connected()
134 if state is State.disconnected:
135 break
136 time.sleep(1)
137 retry += 1
138
139 # Consider the modem is not responding if disconnection failed
140 if state is not State.disconnected:
141 msg = "Failed to disconnect."
142 logging.error(msg)
143 return State.error, msg
144
145 time.sleep(2)
146 # Create a new session
147 # "echo" works more stable than serial for this action
148 # success, result = modem.write('AT+CGATT=1')
149 logging.debug("echo 'AT+CFUN=1' > " + CONF.modem.port)
150 success = os.system("echo 'AT+CFUN=1' > " + CONF.modem.port)
151 logging.debug("result: %s", success)
152 if success is not 0:
153 msg = "Write 'AT+CFUN=1' failed"
154 logging.error(msg)
155 return State.error, msg
156
157 # Give 10 sec for the modem to be fully connected
158 retry = 0
159 while retry < 10:
160 state = modem.is_connected()
161 if state is State.connected:
162 break
163 time.sleep(1)
164 retry += 1
165 # CGATT sometimes returns None
166 if state is State.error:
167 state = State.disconnected
168
169 time.sleep(2)
170 return state, None
171
172
173def get_user_plane_state(modem):
174 resp = os.system("ping -c 3 " + CONF.ping_to + ">/dev/null 2>&1")
175 return State.connected if resp is 0 else State.disconnected, None
176
177
178def report_status(cp_state, up_state):
179 report['status']['control_plane'] = cp_state.name
180 report['status']['user_plane'] = up_state.name
181
182 logging.info("Sending report %s", report)
183 try:
184 result = requests.post(CONF.report_url, json=report)
185 except requests.exceptions.ConnectionError:
186 logging.error("Failed to report for %s", e)
187 pass
188 try:
189 result.raise_for_status()
190 except requests.exceptions.HTTPError as e:
191 logging.error("Failed to report for %s", e)
192 pass
193
194
195def main():
196 modem = Modem(CONF.modem.port, CONF.modem.baud)
197 try:
198 modem.connect()
199 except serial.serialutil.SerialException as e:
200 logging.error("Failed to connect the modem for %s", e)
Hyunsun Moonf4242372020-10-04 23:32:38 -0700201 os.system("sudo shutdown -r now")
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700202
203 success = os.system("sudo ip route replace {}/32 via {}".format(
204 CONF.ping_to, CONF.modem.ip_addr))
205 if success is not 0:
206 logging.error("Failed to add test routing.")
207 sys.exit(1)
208
209 while True:
210 cp_state, cp_msg = get_control_plane_state(modem)
211 up_state, up_msg = get_user_plane_state(modem)
212
213 if cp_state is State.error:
214 logging.error("Modem is in error state.")
Hyunsun Moonf4242372020-10-04 23:32:38 -0700215 sys.exit(1)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700216
217 report_status(cp_state, up_state)
218 time.sleep(CONF.report_interval)
219
220 modem.close()
221
222
223if __name__ == "__main__":
224 main()