Skip to content

Instantly share code, notes, and snippets.

@nitobuendia
Created January 1, 2020 08:16
Show Gist options
  • Save nitobuendia/4f5a4f4c7b1cb28ca32a172455dcd080 to your computer and use it in GitHub Desktop.
Save nitobuendia/4f5a4f4c7b1cb28ca32a172455dcd080 to your computer and use it in GitHub Desktop.
Integrate Utilities email on Home-Assistant via MQTT
/**
* 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;
}
################################################################################
# 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