├── 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 --------------------------------------------------------------------------------