main.Handler.main()   A
last analyzed

Complexity

Conditions 5

Size

Total Lines 23
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 17
nop 1
dl 0
loc 23
rs 9.0833
c 0
b 0
f 0
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