Created
January 1, 2020 08:16
-
-
Save nitobuendia/4f5a4f4c7b1cb28ca32a172455dcd080 to your computer and use it in GitHub Desktop.
Integrate Utilities email on Home-Assistant via MQTT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Fetches utility consumption details from the Utilities email and sends it to Hassio MQTT. | |
* | |
* Apps Script on drive.google.com | |
*/ | |
/* Configuration. */ | |
/** @const {string} URL for MQTT Server API. */ | |
var MQTT_URL = 'https://your-ha-domain:port/api/services/mqtt/publish'; | |
/** @const {string} (Long-Lived) Access token for API. */ | |
var API_TOKEN = 'your-api-token' | |
/** @const {string} MQTT Topic for Utility Bill. */ | |
var EXPENSE_TOPIC = '/utilities/bill'; | |
/** @const {string} MQTT Topic for Electricity Consumption. */ | |
var ELECTRICITY_TOPIC = '/utilities/electricity'; | |
/** @const {string} MQTT Topic for Water Consumption. */ | |
var WATER_TOPIC = '/utilities/water'; | |
/** @const {string} MQTT Topic for Gas Consumption. */ | |
var GAS_TOPIC = '/utilities/gas'; | |
/** @const {string} Query search to fetch utility emails. */ | |
var UTILITY_QUERY = 'subject:"Utility Bill Summary"'; | |
/** @const {!RegExp} RegExp to find monetary values in SGD. */ | |
var SGD_REGEXP = new RegExp(/S\$[0-9]+\.[0-9]{2}/g); | |
/** @const {!RegExp} RegExp to find kWh energy values. */ | |
var KWH_REGEXP = new RegExp(/[0-9]+ kWh/g); | |
/** @const {!RegExp} RegExp to find Cu M water values. */ | |
var CUM_REGEXP = new RegExp(/[0-9]+\.[0-9]+ Cu M/); | |
/* Main workflow. */ | |
/** | |
* Parses content of utilities email and send data to Hassio. | |
* Main function. Schedule on a periodic basis (e.g. weekly). | |
*/ | |
function main() { | |
var utilityEmail = fetchLastUtilityEmail(); | |
if (!utilityEmail) return; | |
var utilityContent = getEmailContent(utilityEmail); | |
if (!utilityContent) return; | |
var billExpense = getBillExpense(utilityContent); | |
var electricityConsumption = getElectricityConsumption(utilityContent); | |
var waterConsumption = getWaterConsumption(utilityContent); | |
var gasConsumption = getGasConsumption(utilityContent); | |
sendToMqttServer(EXPENSE_TOPIC, billExpense); | |
sendToMqttServer(ELECTRICITY_TOPIC, electricityConsumption); | |
sendToMqttServer(WATER_TOPIC, waterConsumption); | |
sendToMqttServer(GAS_TOPIC, gasConsumption); | |
} | |
/* Url fetching utilities. */ | |
/** | |
* Send data to MQTT server. | |
* @param {string} topic MQTT topic to which to send data. | |
* @param {*} data Data to send to MQTT. | |
*/ | |
function sendToMqttServer(topic, data) { | |
var payload = { | |
'topic': topic, | |
'payload': data, | |
'retain': true, | |
}; | |
var headers = { | |
'Authorization': 'Bearer ' + API_TOKEN, | |
'Content-Type': 'application/json', | |
}; | |
var fetchOptions = { | |
'method': 'post', | |
'headers': headers, | |
'payload': JSON.stringify(payload), | |
'escaping': false, | |
}; | |
UrlFetchApp.fetch(MQTT_URL, fetchOptions); | |
} | |
/* Email parsing. */ | |
/** | |
* Fetches the last utility email. | |
* @return {GmailMessage} Message for the utility bill. | |
* Returns null if the thread is not found. | |
*/ | |
function fetchLastUtilityEmail() { | |
var threads = GmailApp.search(UTILITY_QUERY); | |
if (threads.length === 0) return null; | |
var messages = threads[0].getMessages(); | |
return messages[0]; | |
} | |
/** | |
* Get email contents. | |
* @param {!GmailMessage} emailMessage Email message from which to get body. | |
* @return {string} Contents of the message. | |
*/ | |
function getEmailContent(emailMessage) { | |
return emailMessage.getBody(); | |
} | |
/** | |
* Extracts expense from utility email. | |
* @param {string} emailBody Contents of the utility email. | |
* @return {?number} Total expense of the month for utility email. | |
* Returns null if not found or unparseable as float. | |
*/ | |
function getBillExpense(emailBody) { | |
return parseContentVariable(emailBody, SGD_REGEXP, 0, 'S$', parseFloatOrNull); | |
} | |
/** | |
* Extracts electricity consumption from utility email. | |
* @param {string} emailBody Contents of the utility email. | |
* @return {?number} Electricity consumption of the month for utility email. | |
* Returns null if not found or unparseable as float. | |
*/ | |
function getElectricityConsumption(emailBody) { | |
return parseContentVariable(emailBody, KWH_REGEXP, 0, ' kWh', parseIntOrNull); | |
} | |
/** | |
* Extracts water consumption from utility email. | |
* @param {string} emailBody Contents of the utility email. | |
* @return {?number} Water consumption of the month for utility email. | |
* Returns null if not found or unparseable as float. | |
*/ | |
function getWaterConsumption(emailBody) { | |
return parseContentVariable(emailBody, CUM_REGEXP, 0, ' Cu M', parseFloatOrNull); | |
} | |
/** | |
* Extracts gas usage from utility email. | |
* @param {string} emailBody Contents of the utility email. | |
* @return {?number} Gas consumption of the month for utility email. | |
* Returns null if not found or unparseable as float. | |
*/ | |
function getGasConsumption(emailBody) { | |
return parseContentVariable(emailBody, KWH_REGEXP, 3, ' kWh', parseIntOrNull); | |
} | |
/* Email parsing utilities. */ | |
/** | |
* Extracts a clean content from a text. | |
* @param {string} content Content from which to extract text. | |
* @param {!RegExp} pattern Pattern to extract content. | |
* @param {number=} index Index for matched pattern. | |
* @param {string=} replacementText Text to remove before proceeding. | |
* @param {function(string):(?string|?number)=} modifierFunction Function to manipulate value. | |
* @return {?number|?string} Parsed content. | |
* Null if not found, or unparseable. | |
*/ | |
function parseContentVariable(content, pattern, index, replacementText, modifierFunction) { | |
if (index === undefined) index = 0; | |
if (replacementText === undefined) replacementText = ''; | |
var matches = content.match(pattern); | |
if (!matches || matches.length < index + 1) return null; | |
var positionedMatch = matches[index]; | |
if (replacementText) positionedMatch = positionedMatch.replace(replacementText, ''); | |
if (modifierFunction) return modifierFunction(positionedMatch); | |
return positionedMatch; | |
} | |
/** | |
* Parses to int or null. | |
* @param {string} string String to parse to int. | |
* @return {?number} Parsed int, or null if not parseable. | |
*/ | |
function parseIntOrNull(string) { | |
var int = parseInt(string, 10); | |
if (isNaN(int)) return null; | |
return int; | |
} | |
/** | |
* Parses to float or null. | |
* @param {string} string String to parse to int. | |
* @return {?number} Parsed float, or null if not parseable. | |
*/ | |
function parseFloatOrNull(string) { | |
var float = parseFloat(string, 10); | |
if (isNaN(float)) return null; | |
return float; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
################################################################################ | |
# Utilities consumption. | |
################################################################################ | |
sensor: | |
- platform: mqtt | |
name: "Utilities Bill" | |
unit_of_measurement: "SGD$" | |
state_topic: "/utilities/bill" | |
- platform: mqtt | |
name: "Electricity Consumption" | |
unit_of_measurement: "kWh" | |
state_topic: "/utilities/electricity" | |
- platform: mqtt | |
name: "Water Consumption" | |
unit_of_measurement: "Cu M" | |
state_topic: "/utilities/water" | |
- platform: mqtt | |
name: "Gas Consumption" | |
unit_of_measurement: "kWh" | |
state_topic: "/utilities/gas" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment