├── README.md
├── custom_components
└── project_zero_three
│ ├── __init__.py
│ ├── manifest.json
│ └── sensor.py
├── hacs.json
└── img
├── 1.png
├── 2.png
└── install.png
/README.md:
--------------------------------------------------------------------------------
1 | # Project Three Zero (7-11 Fuel Lock) for Home Assistant
2 |
3 | A simple component for home assistant to display the cheapest prices around australia for your helicopter journeys :)
4 |
5 |
6 |
7 |
8 |
9 |
10 | ## Using the component
11 |
12 | 1) Install [HACS](https://hacs.xyz/docs/installation/manual) if you haven't already
13 | 2) Head over to the `HACS` tab, and click on the "Custom Repositories" menu icon.
14 | 3) Enter the URL `https://github.com/atymic/project_three_zero_ha` and `Integration" for the category and hit save.
15 | 4) Add it to your `platforms` in your `configuration.yaml`:
16 | ```yaml
17 | sensor:
18 | - platform: project_zero_three
19 | ```
20 |
21 | 4) Restart HA
22 | 4) Check the entity list, you should see them populated (named `project_three_zero_`)
--------------------------------------------------------------------------------
/custom_components/project_zero_three/__init__.py:
--------------------------------------------------------------------------------
1 | """The project_zero_three component."""
2 |
--------------------------------------------------------------------------------
/custom_components/project_zero_three/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "domain": "project_zero_three",
3 | "name": "7-11 Fuel Price Tacker",
4 | "documentation": "https://github.com/atymic/project_three_zero_ha",
5 | "requirements": [],
6 | "codeowners": ["@atymic"],
7 | "version": "1.0.1"
8 | }
9 |
--------------------------------------------------------------------------------
/custom_components/project_zero_three/sensor.py:
--------------------------------------------------------------------------------
1 | """Sensor platform to display the current fuel prices at a NSW fuel station."""
2 | import datetime
3 | import logging
4 | from typing import Optional
5 |
6 | import requests
7 | import voluptuous as vol
8 |
9 | from homeassistant.components.sensor import PLATFORM_SCHEMA
10 | from homeassistant.const import ATTR_ATTRIBUTION
11 | import homeassistant.helpers.config_validation as cv
12 | from homeassistant.helpers.entity import Entity
13 | from homeassistant.util import Throttle
14 |
15 | _LOGGER = logging.getLogger(__name__)
16 |
17 | ATTR_LATITUDE = "latitude"
18 | ATTR_LONGITUDE = "longitude"
19 |
20 |
21 | CONF_UPDATE_FREQUENCY = 'update_frequency'
22 | CONF_UPDATE_FREQUENCY_DEFAULT = 5
23 |
24 | CONF_FUEL_TYPES = "fuel_types"
25 | CONF_ALLOWED_FUEL_TYPES = [
26 | "E10",
27 | "U91",
28 | "U95",
29 | "U98",
30 | "Diesel",
31 | "LPG",
32 | ]
33 | CONF_DEFAULT_FUEL_TYPES = ["E10", "U91", "U95", "U98"]
34 |
35 | ATTRIBUTION = "Data provided by Project Zero Three"
36 |
37 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
38 | {
39 | vol.Optional(CONF_UPDATE_FREQUENCY, default=CONF_UPDATE_FREQUENCY_DEFAULT): cv.positive_int,
40 | vol.Optional(CONF_FUEL_TYPES, default=CONF_ALLOWED_FUEL_TYPES): vol.All(
41 | cv.ensure_list, [vol.In(CONF_ALLOWED_FUEL_TYPES)]
42 | ),
43 | }
44 | )
45 |
46 | NOTIFICATION_ID = "project_zero_three_notification"
47 | NOTIFICATION_TITLE = "Project Three Zero Setup"
48 |
49 | # TODO figure out to how to do this dynamically
50 | MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(minutes=5)
51 |
52 |
53 | def setup_platform(hass, config, add_entities, discovery_info=None):
54 | """Set up the NSW Fuel Station sensor."""
55 |
56 | update_frequency = config[CONF_UPDATE_FREQUENCY]
57 | fuel_types = config[CONF_FUEL_TYPES]
58 |
59 | data = FuelPriceData()
60 | data.update()
61 |
62 | if data.error is not None:
63 | message = "Error: {}. Check the logs for additional information.".format(
64 | data.error
65 | )
66 |
67 | hass.components.persistent_notification.create(
68 | message, title=NOTIFICATION_TITLE, notification_id=NOTIFICATION_ID
69 | )
70 | return
71 |
72 | entities = []
73 | for region in data.get_regions():
74 | available_fuel_types = data.get_available_fuel_types(region)
75 |
76 | entities.extend(
77 | StationPriceSensor(data, fuel_type, region)
78 | for fuel_type in fuel_types
79 | if fuel_type in available_fuel_types
80 | )
81 |
82 | add_entities(entities)
83 |
84 | class FuelPriceData:
85 | """An object to store and fetch the latest data for multiple regions."""
86 |
87 | def __init__(self) -> None:
88 | """Initialize the sensor."""
89 | self._data = None
90 | self.error = None
91 |
92 | @Throttle(MIN_TIME_BETWEEN_UPDATES)
93 | def update(self):
94 | """Update the internal data using the API client."""
95 | try:
96 | res = requests.get(
97 | "https://projectzerothree.info/api.php?format=json",
98 | headers={'User-Agent': 'Mozilla/5.0'}
99 | )
100 | self._data = res.json()['regions']
101 | except requests.RequestException as exc:
102 | self.error = str(exc)
103 | _LOGGER.error("Failed to fetch project zero three price data. %s", exc)
104 |
105 | def get_regions(self):
106 | """Return the list of regions."""
107 | if self._data is None:
108 | return []
109 | return [region['region'] for region in self._data]
110 |
111 | def get_available_fuel_types(self, region_name):
112 | """Return the available fuel types for a given region."""
113 | region_data = next((r for r in self._data if r['region'] == region_name), None)
114 | if not region_data:
115 | return []
116 | return [price['type'] for price in region_data['prices']]
117 |
118 | def for_fuel_type(self, fuel_type: str, region_name: str):
119 | """Return the price of the given fuel type in a specific region."""
120 | region_data = next((r for r in self._data if r['region'] == region_name), None)
121 | if not region_data:
122 | return None
123 | return next((price for price in region_data['prices'] if price['type'] == fuel_type), None)
124 |
125 | class StationPriceSensor(Entity):
126 | """Implementation of a sensor that reports the fuel price for a station."""
127 |
128 | def __init__(self, data: FuelPriceData, fuel_type: str, region: str):
129 | """Initialize the sensor."""
130 | self._data = data
131 | self._fuel_type = fuel_type
132 | self._region = region
133 |
134 | def get_price_data(self) -> Optional[dict]:
135 | """Return the state of the sensor."""
136 | return self._data.for_fuel_type(self._fuel_type, self._region)
137 |
138 | @property
139 | def unique_id(self) -> Optional[str]:
140 | """Return the unique ID of the sensor."""
141 | data = self.get_price_data()
142 | if self._region == "All":
143 | return f"project_zero_three_{data['type']}"
144 | return f"project_zero_three_{data['type']}_{self._region}"
145 |
146 | @property
147 | def name(self) -> str:
148 | """Return the name of the sensor."""
149 | data = self.get_price_data()
150 | if not self.registry_entry:
151 | return self.unique_id
152 | return f"{data['type']} @ {data['suburb']} {data['postcode']} ({data['state']})"
153 |
154 | @property
155 | def state(self) -> Optional[float]:
156 | """Return the state of the sensor."""
157 | data = self.get_price_data()
158 | return data['price'] if data else None
159 |
160 | @property
161 | def extra_state_attributes(self) -> dict:
162 | """Return the state attributes of the device."""
163 | data = self.get_price_data()
164 | return {
165 | ATTR_ATTRIBUTION: ATTRIBUTION,
166 | ATTR_LATITUDE: data['lat'] if data else None,
167 | ATTR_LONGITUDE: data['lng'] if data else None,
168 | }
169 |
170 | @property
171 | def unit_of_measurement(self) -> str:
172 | """Return the units of measurement."""
173 | return "¢/L"
174 |
175 | def update(self):
176 | """Update current conditions."""
177 | self._data.update()
178 |
--------------------------------------------------------------------------------
/hacs.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Project Three Zero (7-11 Fuel Lock Monitor)",
3 | "render_readme": true
4 | }
5 |
--------------------------------------------------------------------------------
/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atymic/project_three_zero_ha/31a7f4a45e5a00d4e62d02eb76f6571a2bed2668/img/1.png
--------------------------------------------------------------------------------
/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atymic/project_three_zero_ha/31a7f4a45e5a00d4e62d02eb76f6571a2bed2668/img/2.png
--------------------------------------------------------------------------------
/img/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/atymic/project_three_zero_ha/31a7f4a45e5a00d4e62d02eb76f6571a2bed2668/img/install.png
--------------------------------------------------------------------------------