125 |
126 | | Platform |
127 | Payment methods |
128 | Link |
129 | Comment |
130 |
131 |
132 | | Ko-fi |
133 |
134 | PayPal
135 | Credit card
136 | |
137 |
138 |
139 | |
140 |
141 | No fees
142 | Single or monthly payment
143 | |
144 |
145 |
146 | | buycoffee.to |
147 |
148 | BLIK
149 | Bank transfer
150 | |
151 |
152 |
153 | |
154 | |
155 |
156 |
157 | | PayPal |
158 |
159 | PayPal
160 | |
161 |
162 |
163 | |
164 |
165 | No fees
166 | |
167 |
168 |
169 | | Revolut |
170 |
171 | Revolut
172 | Credit Card
173 | |
174 |
175 |
176 | |
177 |
178 | No fees
179 | |
180 |
181 |
182 |
183 | ### Powered by
184 | [](https://jb.gg/OpenSourceSupport)
185 |
186 |
187 | [ko_fi_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Ko-Fi&color=F16061&logo=ko-fi&logoColor=white
188 |
189 | [ko_fi]: https://ko-fi.com/piotrmachowski
190 |
191 | [buycoffee_to_shield]: https://shields.io/badge/buycoffee.to-white?style=flat&labelColor=white&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TpaIVh1YQcchQnayIijhKFYtgobQVWnUweemP0KQhSXFxFFwLDv4sVh1cnHV1cBUEwR8QVxcnRRcp8b6k0CLGC4/3cd49h/fuA4R6malmxzigapaRisfEbG5FDLzChxB6MIZ+iZl6Ir2QgWd93VM31V2UZ3n3/Vm9St5kgE8knmW6YRGvE09vWjrnfeIwK0kK8TnxqEEXJH7kuuzyG+eiwwLPDBuZ1BxxmFgstrHcxqxkqMRTxBFF1ShfyLqscN7irJarrHlP/sJgXltOc53WEOJYRAJJiJBRxQbKsBClXSPFRIrOYx7+QcefJJdMrg0wcsyjAhWS4wf/g9+zNQuTE25SMAZ0vtj2xzAQ2AUaNdv+PrbtxgngfwautJa/UgdmPkmvtbTIEdC3DVxctzR5D7jcAQaedMmQHMlPSygUgPcz+qYcELoFulfduTXPcfoAZGhWSzfAwSEwUqTsNY93d7XP7d+e5vx+AIahcq//o+yoAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5wETCy4vFNqLzwAAAVpJREFUOMvd0rFLVXEYxvHPOedKJnKJhrDLuUFREULE7YDCMYj+AydpsCWiaKu29hZxiP4Al4aWwC1EdFI4Q3hqEmkIBI8ZChWXKNLLvS0/Qcza84V3enm/7/s878t/HxGkeTaIGziP+EB918nawu7Dq1d0e1+2J2bepnk2jFEUVVF+qKV51o9neBCaugfge70keoxxUbSWjrQ+4SUyzKZ5NlnDZdzGG7w4DIh+dtZEFntDA98l8S0MYwctNGrYz9WqKJePFLq80g5Sr+EHlnATp+NA+4qLaZ7FfzMrzbMBjGEdq8GrJMZnvAvFC/8wfAwjWMQ8XmMzaW9sdevNRgd3MFhvNpbaG1u/Dk2/hOc4gadVUa7Um425qii/7Z+xH9O4jwW8Cqv24Tru4hyeVEU588cfBMgpPMI9nMFe0BkFzVOYrYqycyQgQJLwTC2cDZCPeF8V5Y7jGb8BUpRicy7OU5MAAAAASUVORK5CYII=
192 |
193 | [buycoffee_to]: https://buycoffee.to/piotrmachowski
194 |
195 | [buy_me_a_coffee_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Buy%20me%20a%20coffee&color=6f4e37&logo=buy%20me%20a%20coffee&logoColor=white
196 |
197 | [buy_me_a_coffee]: https://www.buymeacoffee.com/PiotrMachowski
198 |
199 | [paypal_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=PayPal.Me&logo=paypal
200 |
201 | [paypal_me]: https://paypal.me/PiMachowski
202 |
203 | [revolut_me_shield]: https://img.shields.io/static/v1.svg?label=%20&message=Revolut&logo=revolut
204 |
205 | [revolut_me]: https://revolut.me/314ma
206 |
--------------------------------------------------------------------------------
/custom_components/rozkladzik/sensor.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import datetime
3 |
4 | import voluptuous as vol
5 |
6 | from homeassistant.components.sensor import PLATFORM_SCHEMA, ENTITY_ID_FORMAT
7 | from homeassistant.const import CONF_NAME
8 | import homeassistant.helpers.config_validation as cv
9 | from homeassistant.helpers.entity import Entity
10 | from homeassistant.helpers.entity import async_generate_entity_id
11 |
12 | CONF_STOPS = 'stops'
13 | CONF_STOP_ID = 'id'
14 | CONF_STOP_NAME = 'name'
15 | CONF_GROUP_MODE = 'stops_group_mode'
16 | CONF_LINES = 'lines'
17 | CONF_DIRECTIONS = 'directions'
18 | CONF_CITY = 'city'
19 |
20 | DEFAULT_NAME = 'Rozkładzik'
21 |
22 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
23 | vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
24 | vol.Required(CONF_CITY): cv.string,
25 | vol.Required(CONF_STOPS): vol.All(cv.ensure_list, [
26 | vol.Schema({
27 | vol.Required(CONF_STOP_ID): cv.positive_int,
28 | vol.Required(CONF_STOP_NAME): cv.string,
29 | vol.Optional(CONF_GROUP_MODE, default=False): cv.boolean,
30 | vol.Optional(CONF_LINES, default=[]): cv.ensure_list,
31 | vol.Optional(CONF_DIRECTIONS, default=[]): cv.ensure_list
32 | })])
33 | })
34 |
35 |
36 | def setup_platform(hass, config, add_entities, discovery_info=None):
37 | name = config.get(CONF_NAME)
38 | city = config.get(CONF_CITY)
39 | stops = config.get(CONF_STOPS)
40 | dev = []
41 | for stop in stops:
42 | stop_id = stop.get(CONF_STOP_ID)
43 | stop_name = stop.get(CONF_STOP_NAME)
44 | group_mode = stop.get(CONF_GROUP_MODE)
45 | lines = stop.get(CONF_LINES)
46 | directions = stop.get(CONF_DIRECTIONS)
47 | uid = '{}_{}_{}'.format(name, city, stop_id)
48 | entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, uid, hass=hass)
49 | dev.append(RozkladzikSensor(entity_id, name, city, stop_id, stop_name, group_mode, lines, directions))
50 | add_entities(dev, True)
51 |
52 |
53 | class RozkladzikSensor(Entity):
54 | def __init__(self, entity_id, name, city, stop_id, stop_name, group_mode, watched_lines, watched_directions):
55 | self.entity_id = entity_id
56 | self._name = name
57 | self._stop_id = stop_id
58 | self._stop_name = stop_name
59 | self._city = city
60 | self._city_data = self.get_city_data()
61 | self._group_mode = group_mode
62 | self._watched_lines = watched_lines
63 | self._watched_directions = watched_directions
64 | self._last_response = None
65 | self._departures_number = 0
66 | self._departures_ordered = []
67 | self._departures_by_line = dict()
68 | self._state = None
69 |
70 | @property
71 | def name(self):
72 | return '{} {} - {}'.format(self._name, self._city, self._stop_name)
73 |
74 | @property
75 | def state(self):
76 | if self._departures_number is not None and self._departures_number > 0:
77 | return RozkladzikSensor.departure_to_str(self._departures_ordered[0])
78 | return None
79 |
80 | @staticmethod
81 | def departure_to_str(departure):
82 | return '{} kier. {}: {} ({}m)'.format(departure[0], departure[1], departure[2], departure[3])
83 |
84 | @property
85 | def unit_of_measurement(self):
86 | return None
87 |
88 | @property
89 | def extra_state_attributes(self):
90 | attr = dict()
91 | if self._departures_ordered is not None:
92 | attr['list'] = self._departures_ordered
93 | attr['html_timetable'] = self.get_html_timetable()
94 | attr['html'] = attr['html_timetable']
95 | attr['html_departures'] = self.get_html_departures()
96 | if self._departures_number > 0:
97 | dep = self._departures_ordered[0]
98 | attr['line'] = dep[0]
99 | attr['direction'] = dep[1]
100 | attr['departure'] = dep[2]
101 | attr['time_to_departure'] = dep[3]
102 | return attr
103 |
104 | def update(self):
105 | now = datetime.datetime.now()
106 | r_time = now.hour * 60 + now.minute
107 | if self._should_update(r_time):
108 | url_template = 'https://www.rozkladzik.pl/{}/timetable.txt?c=tsa&t={}&day={}&time={}'
109 | if self._group_mode:
110 | url_template = 'https://www.rozkladzik.pl/{}/timetable.txt?c=bsa&b={}&day={}&time={}'
111 | response = requests.get(url_template.format(self._city, self._stop_id, now.weekday(), r_time))
112 | if response.status_code == 200 and response.content.__len__() > 0:
113 | self._last_response = response.text
114 | if self._last_response is not None:
115 | self.update_values_for_time(r_time)
116 |
117 | def _should_update(self, now):
118 | return self._last_response is None or self._departures_ordered is None or len(self._departures_ordered) == 0 or self._departures_ordered[0][4] < now
119 |
120 | def update_values_for_time(self, r_time):
121 | raw_array = self._last_response.split("|")
122 | self._departures_ordered = []
123 | self._departures_by_line = dict()
124 | departures_by_line = dict()
125 | lines_directions = dict()
126 | for r in raw_array:
127 | line, direction_number, departures = self.process_raw(r, r_time)
128 | direction = self.get_direction(line, direction_number)
129 | if len(self._watched_lines) > 0 and line not in self._watched_lines \
130 | or len(self._watched_directions) > 0 and direction not in self._watched_directions:
131 | continue
132 | if not line in lines_directions:
133 | lines_directions[line] = []
134 | lines_directions[line].append(direction)
135 | departures_by_line[(line, direction)] = []
136 | for departure_time, departure_diff, departure_timestamp in departures:
137 | self._departures_ordered.append((line, direction, departure_time, departure_diff, departure_timestamp))
138 | departures_by_line[(line, direction)].append((departure_time, departure_diff))
139 | departures_by_line[(line, direction)].sort(key=lambda e: e[1])
140 | for line in lines_directions:
141 | self._departures_by_line[line] = dict()
142 | for direction in lines_directions[line]:
143 | self._departures_by_line[line][direction] = []
144 | for departure in departures_by_line[(line, direction)]:
145 | self._departures_by_line[line][direction].append(departure)
146 | self._departures_ordered.sort(key=lambda e: e[3])
147 | self._departures_number = len(self._departures_ordered)
148 |
149 | def get_html_timetable(self):
150 | html = '