Skip to content

Commit

Permalink
feat: revamped code for setting sensor entities and other fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Codestian committed Oct 11, 2022
1 parent 18af04b commit 26f4a08
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 42 deletions.
1 change: 0 additions & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
# LTA bus arrival timings custom component for Home Assistant 🚌
# Singapore LTA bus timings custom component for Home Assistant 🚍

Home Assistant custom component to retrieve bus timings, using the [Datamall API](https://datamall.lta.gov.sg/content/datamall/en.html) provided by LTA.
Home Assistant custom component to retrieve bus timings in Singapore, using the [Datamall API](https://datamall.lta.gov.sg/content/datamall/en.html) provided by LTA.

## What's new (0.2.1)
- Changed `device_state_attributes` to `extra_state_attributes`, longitude and latitude attributes should be working again.
## What's new (0.3.0)
- Added `operator` and `type` attributes
- Now you can tell if its SMRT or Tower Transit etc
- As well as if its single or double deck
- Fixed a bug where Home Assistant throws an error when buses tracking are not in operation
- [**BREAKING CHANGE**] All sensor values are now strictly integers (no more `ARR` and `NA` text)
- This is to make automations easier to edit
- Buses that are arriving or not in operation have values set to `0` min
- To identify the timing statuses, the attribute flags `bus_arriving` and `bus_unavailable` must be used
- Refer to table below in **Usage** for explanation

## Installation (HACS)

Expand Down Expand Up @@ -54,11 +62,23 @@ lta.BUS_STOP_CODE-BUS_NUMBER-BUS_ORDER
For example, sensor ```lta.98051-19-1``` indicates the first timing for bus 19 towards bus stop code 98051. Sensor ```lta.98051-19-2``` will indicate the next subsequent timing for the same bus number.
Some buses only operate on certain timings. Home Assistant will automatically add and remember them when they are in operation.
Some buses only operate on certain timings. By default, all bus numbers configured will have their timings and attributes set to either `0` or `""` unless the response from the API contains the necessary data.
Most buses provide their present locations and can be viewed on the map.
Most buses provide their present locations and can be viewed on the map. If no locations are provided by the API, both latitude and longitude are set to `0` respectively.
### Use cases
- Tell the bus timing before leaving the house through a smart speaker
- Setup a dashboard to track timings without opening phone app
- Make use of timing entities for other IOT related projects
- Make use of timing entities for other IOT related projects
- Have full control of what you want to do with bus timings
### Attribute flags
Version 0.3.0 introduces two new flags that can determine if a bus is either arriving or not in operation. The truth table is as follows:
| `bus_arriving` | `bus_unavailable` | Result | Comments |
|--------------|-----------------|--------|----------------------------------------------------------------------------|
| T | T | F | Should not happen, considered invalid as bus cannot be unavailable and arriving at same time |
| T | F | T | Bus is less than 1 min to arrive at bus stop |
| F | T | T | Bus is not in operation at point in time or does not exist |
| F | F | F | Bus is taking X min to arrive at bus stop |
2 changes: 1 addition & 1 deletion custom_components/ha-lta/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"codeowners": [
"@Codestian"
],
"version": "0.2.1"
"version": "0.3.0"
}
116 changes: 83 additions & 33 deletions custom_components/ha-lta/sensor.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
"""Real time bus arrival timings from LTA DataMall"""
from datetime import datetime, timedelta, timezone
import logging

import async_timeout
from dateutil import parser, tz
from ltapysg import get_bus_arrival
import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import TIME_MINUTES
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

import logging
import async_timeout
import voluptuous as vol
from datetime import datetime, timedelta, timezone
from dateutil import parser, tz
from ltapysg import get_bus_arrival
from math import floor

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(minutes=1)
Expand All @@ -22,9 +22,6 @@
CONF_CODE = "code"
CONF_BUSES = "buses"

BUS_ARRIVING = "ARR"
BUS_UNAVAILABLE = "NA"

BUS_SCHEMA = {
vol.Required(CONF_CODE): cv.string,
vol.Required(CONF_BUSES): vol.All(cv.ensure_list, [cv.string]),
Expand All @@ -42,35 +39,53 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
"""Setup sensor and initialize platform with configuration values."""

def create_bus_sensor(
bus_stop, bus_number, bus_order, bus_latitude, bus_longitude, bus_timing
bus_stop,
bus_number,
bus_order,
bus_latitude,
bus_longitude,
bus_timing,
bus_operator,
bus_type,
):
def convert_datetime(bustime):
def buildDict(bus_timing):
"""Convert UTC 8+ datetime to the number of minutes before bus arrives."""
if bustime:
time_bus = parser.parse(bustime).astimezone(tz.UTC)

bus_arriving = False
bus_unavailable = False
bus_state = 0

if bus_timing:
time_bus = parser.parse(bus_timing).astimezone(tz.UTC)
time_now = datetime.now(tz=timezone.utc)

time_diff = time_bus - time_now

time_diff_formatted = round(time_diff.total_seconds() / 60)
time_diff_formatted = floor(time_diff.total_seconds() / 60)

if time_diff_formatted <= 1:
return BUS_ARRIVING
bus_arriving = True
else:
return time_diff_formatted
bus_state = time_diff_formatted
else:
return BUS_UNAVAILABLE
bus_unavailable = True

bus_dict = {
"unique_id": f"{bus_stop}_{bus_number}_{bus_order}",
"attributes": {
"bus_arriving": bus_arriving,
"bus_unavailable": bus_unavailable,
"latitude": bus_latitude,
"longitude": bus_longitude,
"operator": bus_operator,
"type": bus_type,
},
"state": bus_state,
}

bus_dict = {
"unique_id": f"{bus_stop}_{bus_number}_{bus_order}",
"attributes": {}
if (bus_latitude == "0" and bus_longitude == "0")
or (bus_latitude == "" and bus_longitude == "")
else {"latitude": bus_latitude, "longitude": bus_longitude},
"state": convert_datetime(bus_timing),
}
return bus_dict

return bus_dict
return buildDict(bus_timing)

async def async_update_data():
"""Poll API and update data to sensors."""
Expand All @@ -86,10 +101,20 @@ async def async_update_data():
config.get(CONF_API_KEY), bus_stop["code"]
)

for bus in data:
"""Loop through retrieved data and filter by configured buses."""
if bus["ServiceNo"] in list(bus_stop["buses"]):

print(data)

for one in list(bus_stop["buses"]):
idx = next(
(
index
for (index, d) in enumerate(data)
if d["ServiceNo"] == one
),
None,
)
"""If configured bus exists in API response, use data from response"""
if idx is not None:
bus = data[idx]
"""Create entity for 1st timing."""
sensors.append(
create_bus_sensor(
Expand All @@ -99,6 +124,8 @@ async def async_update_data():
bus["NextBus"]["Latitude"],
bus["NextBus"]["Longitude"],
bus["NextBus"]["EstimatedArrival"],
bus["Operator"],
bus["NextBus"]["Type"],
)
)

Expand All @@ -111,6 +138,8 @@ async def async_update_data():
bus["NextBus2"]["Latitude"],
bus["NextBus2"]["Longitude"],
bus["NextBus2"]["EstimatedArrival"],
bus["Operator"],
bus["NextBus2"]["Type"],
)
)

Expand All @@ -123,6 +152,27 @@ async def async_update_data():
bus["NextBus3"]["Latitude"],
bus["NextBus3"]["Longitude"],
bus["NextBus3"]["EstimatedArrival"],
bus["Operator"],
bus["NextBus3"]["Type"],
)
)
else:
"""Create entity for 1st timing."""
sensors.append(
create_bus_sensor(
bus_stop["code"], one, "1", 0, 0, 0, "", ""
)
)
"""Create entity for 2nd timing."""
sensors.append(
create_bus_sensor(
bus_stop["code"], one, "2", 0, 0, 0, "", ""
)
)
"""Create entity for 3rd timing."""
sensors.append(
create_bus_sensor(
bus_stop["code"], one, "3", 0, 0, 0, "", ""
)
)

Expand Down

0 comments on commit 26f4a08

Please sign in to comment.