1
|
|
|
#!/usr/bin/python3 |
2
|
|
|
#flake8 --extend-ignore=R1723 |
3
|
|
|
|
4
|
|
|
""" Main file for scooter program with Handler class. """ |
5
|
|
|
|
6
|
|
|
import math |
7
|
|
|
import sys |
8
|
|
|
import inspect |
9
|
|
|
import time |
10
|
|
|
from threading import Thread |
11
|
|
|
from datetime import datetime, timedelta |
12
|
|
|
|
13
|
|
|
from src.api import ApiData |
14
|
|
|
from src.scooter import Scooter |
15
|
|
|
|
16
|
|
|
class Handler(): |
17
|
|
|
""" Handler class """ |
18
|
|
|
|
19
|
|
|
# menu options |
20
|
|
|
_OPTIONS = { |
21
|
|
|
"1": "start_scooter", |
22
|
|
|
"2": "stop_running", |
23
|
|
|
"3": "get_scooter_info", |
24
|
|
|
"4": "return_scooter", |
25
|
|
|
"5": "charge_scooter", |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
# scooter status |
29
|
|
|
_running = False |
30
|
|
|
|
31
|
|
|
# Leave the scooter, stop the thread |
32
|
|
|
_return = False |
33
|
|
|
|
34
|
|
|
# start renting time, a list of integers [H:M:S] |
35
|
|
|
start_time = None |
36
|
|
|
|
37
|
|
|
|
38
|
|
|
def __init__(self) -> None: |
39
|
|
|
""" Initialize class """ |
40
|
|
|
self.scooter = Scooter() |
41
|
|
|
self.api = ApiData(user_id = 6) # user id (random user) |
42
|
|
|
|
43
|
|
|
# create a Thread |
44
|
|
|
self._thread = Thread(target=self.run, name="Move scooter") |
45
|
|
|
|
46
|
|
|
|
47
|
|
|
def _get_method(self, method_name): |
48
|
|
|
""" Uses function getattr() to dynamically get value of an attribute. """ |
49
|
|
|
return getattr(self, self._OPTIONS[method_name]) |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
def _print_menu(self) -> None: |
53
|
|
|
""" Prints options for the program. """ |
54
|
|
|
menu = "" |
55
|
|
|
|
56
|
|
|
for key in sorted(self._OPTIONS): |
57
|
|
|
method = self._get_method(key) |
58
|
|
|
docstring = inspect.getdoc(method) |
59
|
|
|
|
60
|
|
|
menu += "{choice}: {explanation}\n".format( |
61
|
|
|
choice = key, |
62
|
|
|
explanation = docstring |
63
|
|
|
) |
64
|
|
|
|
65
|
|
|
print(chr(27) + "[2J" + chr(27) + "[;H") |
66
|
|
|
print(menu) |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
def run(self) -> None: |
70
|
|
|
""" Starts a Thread. """ |
71
|
|
|
while True: |
72
|
|
|
if self._return: |
73
|
|
|
break |
74
|
|
|
if self.scooter.check_scooter_in_city() is False: |
75
|
|
|
print("\nScooter is outside of the city\n") |
76
|
|
|
print("You can't use the scooter anymore. Press 4 to cancel the rental.") |
77
|
|
|
|
78
|
|
|
self.stop_running() |
79
|
|
|
time.sleep(10) |
80
|
|
|
elif self._running: |
81
|
|
|
self.battery_check() |
82
|
|
|
time.sleep(5) |
83
|
|
|
|
84
|
|
|
|
85
|
|
|
def battery_check(self) -> None: |
86
|
|
|
""" Checks battery level, if battery < 20% print warning message and stop the scooter. """ |
87
|
|
|
if self.scooter.check_battery(): |
88
|
|
|
self.stop_running() |
89
|
|
|
print("\n\033[1;31m*\033[1;0m Low battery!! the scooter needs to be charged.") |
90
|
|
|
else: |
91
|
|
|
self.scooter.change_location() |
92
|
|
|
self.scooter.move_scooter() |
93
|
|
|
|
94
|
|
|
# update API |
95
|
|
|
self.api.update_rented_scooter() |
96
|
|
|
|
97
|
|
|
|
98
|
|
|
def start_scooter(self) -> None: |
99
|
|
|
""" Move the scooter to a random location. """ |
100
|
|
|
if self.scooter.check_battery(): |
101
|
|
|
print("\n\033[1;31m*\033[1;0m Low battery!! the scooter needs to be charged.") |
102
|
|
|
print("\nPress 4 to end the rental and leave the scooter at charging station.") |
103
|
|
|
print("Or you can press 5 to fully charge the scooter and end the rental.") |
104
|
|
|
else: |
105
|
|
|
self._running = True |
106
|
|
|
|
107
|
|
|
|
108
|
|
|
def stop_running(self) -> None: |
109
|
|
|
""" Stop the scooter. """ |
110
|
|
|
if self._running is True: |
111
|
|
|
self._running = False |
112
|
|
|
self.scooter.stop_scooter() |
113
|
|
|
|
114
|
|
|
# update API |
115
|
|
|
self.api.update_rented_scooter() |
116
|
|
|
|
117
|
|
|
|
118
|
|
|
def rental_time(self) -> timedelta: |
119
|
|
|
""" Returns the time the scooter has been rented by a user. """ |
120
|
|
|
current = datetime.now().time().strftime("%H:%M:%S") |
121
|
|
|
|
122
|
|
|
current_time = timedelta( |
123
|
|
|
hours = int(current[0:2]), |
124
|
|
|
minutes = int(current[3:5]), |
125
|
|
|
seconds = int(current[6:8]) |
126
|
|
|
) |
127
|
|
|
|
128
|
|
|
start_time = timedelta( |
129
|
|
|
hours = self.start_time[0], |
130
|
|
|
minutes = self.start_time[1], |
131
|
|
|
seconds = self.start_time[2] |
132
|
|
|
) |
133
|
|
|
|
134
|
|
|
return current_time - start_time |
135
|
|
|
|
136
|
|
|
|
137
|
|
|
def get_scooter_info(self) -> None: |
138
|
|
|
""" Get the scooter information. """ |
139
|
|
|
if self._running: |
140
|
|
|
print("\nScooter is running.") |
141
|
|
|
else: |
142
|
|
|
print("\nScooter is in sleep mode.") |
143
|
|
|
|
144
|
|
|
print(self.scooter.__str__()) |
145
|
|
|
print("Rent time: " + str(self.rental_time())) |
146
|
|
|
|
147
|
|
|
|
148
|
|
|
def charge_scooter(self) -> None: |
149
|
|
|
"""Charge the scooter and end the rental. """ |
150
|
|
|
# Stop thread |
151
|
|
|
self._return = True |
152
|
|
|
self._running = False |
153
|
|
|
self._thread.join() |
154
|
|
|
|
155
|
|
|
# Fully charges the battery and leave it at the charging Station |
156
|
|
|
charging = self.api.get_station("1") |
157
|
|
|
|
158
|
|
|
self.scooter.data["battery"] = 100 |
159
|
|
|
self.scooter.stop_scooter(status = "1") # Available status |
160
|
|
|
self.scooter.move_to_station(charging) |
161
|
|
|
|
162
|
|
|
|
163
|
|
|
# update API |
164
|
|
|
self.api.return_scooter(math.ceil(self.rental_time().seconds / 60)) |
165
|
|
|
self.api.update_scooter() |
166
|
|
|
sys.exit() |
167
|
|
|
|
168
|
|
|
|
169
|
|
|
def return_scooter(self): |
170
|
|
|
""" Stop the rental and leave the scooter. """ |
171
|
|
|
# Stop thread |
172
|
|
|
self._return = True |
173
|
|
|
self._running = False |
174
|
|
|
self._thread.join() |
175
|
|
|
|
176
|
|
|
self.end_rental() |
177
|
|
|
sys.exit() |
178
|
|
|
|
179
|
|
|
|
180
|
|
|
def end_rental(self) -> None: |
181
|
|
|
""" Checks scooter's battery/maintenance/zone and stops the scooter. """ |
182
|
|
|
if self.scooter.check_scooter_in_city() is False: |
183
|
|
|
self.scooter.stop_scooter(status = "2") ## Unavailable status |
184
|
|
|
|
185
|
|
|
# update API |
186
|
|
|
self.api.return_scooter(math.ceil(self.rental_time().seconds / 60)) |
187
|
|
|
self.api.update_rented_scooter() |
188
|
|
|
elif self.scooter.check_battery(): |
189
|
|
|
charging = self.api.get_station("1") ## Charging Station |
190
|
|
|
|
191
|
|
|
self.scooter.stop_scooter(status = "4") ## Charging status |
192
|
|
|
self.scooter.move_to_station(charging) |
193
|
|
|
|
194
|
|
|
# update api |
195
|
|
|
self.api.return_scooter(math.ceil(self.rental_time().seconds / 60)) |
196
|
|
|
self.api.update_scooter() |
197
|
|
|
elif self.scooter.check_maintenance(): |
198
|
|
|
maintenance = self.api.get_station("4") ## Maintenance Station |
199
|
|
|
|
200
|
|
|
self.scooter.stop_scooter(status = "3") ## Maintenance status |
201
|
|
|
self.scooter.move_to_station(maintenance) |
202
|
|
|
|
203
|
|
|
# update api |
204
|
|
|
self.api.return_scooter(math.ceil(self.rental_time().seconds / 60)) |
205
|
|
|
self.api.update_scooter() |
206
|
|
|
else: |
207
|
|
|
self.scooter.stop_scooter(status = "1") ## Available status |
208
|
|
|
|
209
|
|
|
# update API |
210
|
|
|
self.api.return_scooter(math.ceil(self.rental_time().seconds / 60)) |
211
|
|
|
self.api.update_rented_scooter() |
212
|
|
|
|
213
|
|
|
|
214
|
|
|
def rent_scooter(self): |
215
|
|
|
""" Print menu """ |
216
|
|
|
while True: |
217
|
|
|
self._print_menu() |
218
|
|
|
choice = input("What do you want to do: ") |
219
|
|
|
|
220
|
|
|
try: |
221
|
|
|
self._get_method(choice.lower())() |
222
|
|
|
except KeyError: |
223
|
|
|
print("\nInvalid choice!") |
224
|
|
|
|
225
|
|
|
input("\nPress any key to continue ...") |
226
|
|
|
|
227
|
|
|
|
228
|
|
|
def start(self): |
229
|
|
|
""" |
230
|
|
|
Makes requests to the API, creates log, gets cities and |
231
|
|
|
stations data. start a thread and view rent menu. |
232
|
|
|
""" |
233
|
|
|
# Create new payment/log and update customer's account |
234
|
|
|
self.api.rent_scooter() |
235
|
|
|
|
236
|
|
|
# get city data and add it to dict |
237
|
|
|
city = self.api.get_city_data() |
238
|
|
|
self.scooter.add_city_to_dict(city) |
239
|
|
|
|
240
|
|
|
# start a Thread and show a rent menu |
241
|
|
|
self._thread.start() |
242
|
|
|
self.rent_scooter() |
243
|
|
|
|
244
|
|
|
|
245
|
|
|
def main(self): |
246
|
|
|
""" Main method """ |
247
|
|
|
try: |
248
|
|
|
print("\n************ Welcome to Scooter program **************\n") |
249
|
|
|
|
250
|
|
|
while True: |
251
|
|
|
scooter = int(input("Enter scooter id: ")) |
252
|
|
|
data = self.api.get_scooter_data(scooter) |
253
|
|
|
|
254
|
|
|
try: |
255
|
|
|
if self.scooter.check_scooter_status(data): |
256
|
|
|
# Start renting time |
257
|
|
|
current = datetime.now().time().strftime("%H:%M:%S") |
258
|
|
|
self.start_time = [int(current[:2]), int(current[3:5]), int(current[6:8])] |
259
|
|
|
break |
260
|
|
|
|
261
|
|
|
print("\n\033[1;31m*\033[1;0m Scooter is not available.\n") |
262
|
|
|
except TypeError: |
263
|
|
|
print("\nScooter does not exist.\n") |
264
|
|
|
|
265
|
|
|
self.start() |
266
|
|
|
except ValueError: |
267
|
|
|
print("\nScooter id must be a number.") |
268
|
|
|
|
269
|
|
|
|
270
|
|
|
if __name__ == "__main__": |
271
|
|
|
Handler().main() |
272
|
|
|
|