From ae707a7efdadb446c25313f9ec528fe1717fe0d8 Mon Sep 17 00:00:00 2001 From: Jeremy Willans Date: Wed, 28 Jul 2021 19:53:01 +1000 Subject: [PATCH] add support for braava and multi floor example --- lovelace_mop_multifloor.yaml | 250 ++++++++++++ mop.yaml | 709 +++++++++++++++++++++++++++++++++++ mop_multifloor_example.yaml | 109 ++++++ secrets.yaml | 19 +- vacuum/braava.png | Bin 0 -> 1578 bytes vacuum/braava_charging.png | Bin 0 -> 1676 bytes vacuum/braava_stuck.png | Bin 0 -> 1605 bytes 7 files changed, 1086 insertions(+), 1 deletion(-) create mode 100644 lovelace_mop_multifloor.yaml create mode 100644 mop.yaml create mode 100644 mop_multifloor_example.yaml create mode 100755 vacuum/braava.png create mode 100755 vacuum/braava_charging.png create mode 100755 vacuum/braava_stuck.png diff --git a/lovelace_mop_multifloor.yaml b/lovelace_mop_multifloor.yaml new file mode 100644 index 0000000..bcaadb3 --- /dev/null +++ b/lovelace_mop_multifloor.yaml @@ -0,0 +1,250 @@ +### VERTICAL STACK CARD ### +type: vertical-stack +cards: + - entity: sensor.mop + type: custom:roomba-vacuum-card + mode: mop + - type: entities + entities: + - entities: + - entity: input_boolean.mop_schedule_1 + name: Master Ensuite Clean (11am M/W/F) + - entity: input_boolean.mop_schedule_2 + name: Apartment Clean (12pm Sun) + head: + label: Cleaning Schedules + type: section + type: custom:fold-entity-row + - entities: + - type: custom:select-list-card + entity: input_select.mop_maps + head: + label: Map Selection + type: section + type: custom:fold-entity-row + - default: + type: custom:blank-card + entity: input_select.mop_maps + states: + Kitchen: + entities: + - input_boolean.mop_clean_kitchen + - entity: automation.mop_clean_rooms + lock: + enabled: >- + [[[ + + if ((states['group.mop_rooms'].state == "on") && + (states['sensor.mop'].state == "Ready")) + return false; + return true; + + ]]] + exemptions: [] + name: Clean Rooms + styles: + card: + - height: 50px + tap_action: + action: call-service + service: automation.trigger + service_data: + entity_id: automation.mop_clean_rooms + type: custom:button-card + head: + label: Kitchen Selective Room Cleaning + type: section + type: custom:fold-entity-row + Entry: + entities: + - input_boolean.mop_clean_entry + - entity: automation.mop_clean_rooms + lock: + enabled: >- + [[[ + + if ((states['group.mop_rooms'].state == "on") && + (states['sensor.mop'].state == "Ready")) + return false; + return true; + + ]]] + exemptions: [] + name: Clean Rooms + styles: + card: + - height: 50px + tap_action: + action: call-service + service: automation.trigger + service_data: + entity_id: automation.mop_clean_rooms + type: custom:button-card + head: + label: Entry Selective Room Cleaning + type: section + type: custom:fold-entity-row + Bathroom: + entities: + - input_boolean.mop_clean_bathroom + - entity: automation.mop_clean_rooms + lock: + enabled: >- + [[[ + + if ((states['group.mop_rooms'].state == "on") && + (states['sensor.mop'].state == "Ready")) + return false; + return true; + + ]]] + exemptions: [] + name: Clean Rooms + styles: + card: + - height: 50px + tap_action: + action: call-service + service: automation.trigger + service_data: + entity_id: automation.mop_clean_rooms + type: custom:button-card + head: + label: Bathroom Selective Room Cleaning + type: section + type: custom:fold-entity-row + Master Ensuite: + entities: + - input_boolean.mop_clean_master_ensuite + - entity: automation.mop_clean_rooms + lock: + enabled: >- + [[[ + + if ((states['group.mop_rooms'].state == "on") && + (states['sensor.mop'].state == "Ready")) + return false; + return true; + + ]]] + exemptions: [] + name: Clean Rooms + styles: + card: + - height: 50px + tap_action: + action: call-service + service: automation.trigger + service_data: + entity_id: automation.mop_clean_rooms + type: custom:button-card + head: + label: Ensuite Selective Room Cleaning + type: section + type: custom:fold-entity-row + type: custom:state-switch + - entities: + - style: |- + .text-divider { + padding: 0em; + margin: 0em; + } h2 { + font-size: 1em; + margin-block-start: 0em; + margin-block-end: 0em; + } + text: Clean + type: custom:text-divider-row + - color: '#40bf6a' + due: true + entity: sensor.mop_maint_clean_pad + locale: en-au + severity: + - color: '#bfb540' + value: 0 days + - color: '#bf4060' + value: '-1 days' + style: | + ha-card { + padding: 2px; + --ha-card-box-shadow: 'none'; + --paper-card-background-color: rgba(0, 0, 0, 0); + } + timeout: 10 days + title: Mop Pad + type: custom:check-button-card + - color: '#40bf6a' + due: true + entity: sensor.mop_maint_clean_contacts + locale: en-au + severity: + - color: '#bfb540' + value: 0 days + - color: '#bf4060' + value: '-3 days' + style: | + ha-card { + padding: 2px; + --ha-card-box-shadow: 'none'; + --paper-card-background-color: rgba(0, 0, 0, 0); + } + timeout: 18 days + title: Contacts + type: custom:check-button-card + - color: '#40bf6a' + due: true + entity: sensor.mop_maint_clean_wheels + locale: en-au + severity: + - color: '#bfb540' + value: 0 days + - color: '#bf4060' + value: '-3 days' + style: | + ha-card { + padding: 2px; + --ha-card-box-shadow: 'none'; + --paper-card-background-color: rgba(0, 0, 0, 0); + } + timeout: 18 days + title: Wheels + type: custom:check-button-card + - style: |- + .text-divider { + padding: 0em; + margin: 0em; + } h2 { + font-size: 1em; + margin-block-start: 0em; + margin-block-end: 0em; + } + text: Replace + type: custom:text-divider-row + - color: '#40bf6a' + due: true + entity: sensor.mop_maint_replace_pad + locale: en-au + severity: + - color: '#bfb540' + value: 0 months + - color: '#bf4060' + value: '-4 months' + style: | + ha-card { + padding: 2px; + --ha-card-box-shadow: 'none'; + --paper-card-background-color: rgba(0, 0, 0, 0); + } + timeout: 8 months + title: Mop Pad + type: custom:check-button-card + head: + label: Maintenance + type: section + type: custom:fold-entity-row + +### PICTURE GLANCE CARD ### +aspect_ratio: 0% +camera_image: camera.roomba +entities: [] +type: picture-glance \ No newline at end of file diff --git a/mop.yaml b/mop.yaml new file mode 100644 index 0000000..96b2431 --- /dev/null +++ b/mop.yaml @@ -0,0 +1,709 @@ +################################### +# iRobot Mop Package +################################### + +################################### +# Sensor +################################### + +sensor: + # Braava via Rest980_2 Docker Image + - platform: rest + name: rest980_2 + json_attributes: + - batPct + - cleanMissionStatus + - dock + - pose + - signal + - bbmssn + - bbrun + - name + - pmaps + - openOnly + - twoPass + - softwareVer + - detectedPad + - padWetness + - mopReady + - rankOverlap + resource: !secret mop_state + value_template: 'OK' + scan_interval: 10 + - platform: template + sensors: + mop: + friendly_name_template: >- + {{ state_attr('sensor.rest980_2', 'name') }} + value_template: >- + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['cycle'] == 'none' and state_attr('sensor.rest980_2', 'cleanMissionStatus')['notReady'] == 39 %} + Pending + {% elif state_attr('sensor.rest980_2', 'cleanMissionStatus')['notReady'] > 0 %} + Not Ready + {% else %} + {% set mapper = { + 'clean' : 'Clean', + 'quick' : 'Clean', + 'spot' : 'Spot', + 'dock' : 'Dock', + 'train' : 'Train', + 'none' : 'Ready' } %} + {% set state = state_attr('sensor.rest980_2', 'cleanMissionStatus')['cycle'] %} + {{ mapper[state] if state in mapper else state }} + {% endif %} + icon_template: mdi:robot-vacuum + attribute_templates: + notready_msg: >- + {% set mapper = { + 0 : 'n-a', + 2 : 'Uneven Ground', + 15 : 'Low Battery', + 31 : 'Fill Tank', + 39 : 'Pending', + 48 : 'Path Blocked' } %} + {% set state = state_attr('sensor.rest980_2', 'cleanMissionStatus')['notReady'] %} + {{ mapper[state] if state in mapper else state }} + error_msg: >- + {% set mapper = { + 0 : 'n-a', + 18 : 'Docking Issue'} %} + {% set state = state_attr('sensor.rest980_2', 'cleanMissionStatus')['error'] %} + {{ mapper[state] if state in mapper else state }} + battery: >- + {{ state_attr('sensor.rest980_2', 'batPct') }} % + software_ver: >- + {% if state_attr('sensor.rest980_2', 'softwareVer') is defined %} + {% set version = state_attr('sensor.rest980_2', 'softwareVer') %} + {{ version.split('+')[1] }} + {% else %} + n-a + {% endif %} + phase: >- + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['phase'] == 'charge' and state_attr('sensor.rest980_2', 'batPct') == 100 %} + Idle + {% elif state_attr('sensor.rest980_2', 'cleanMissionStatus')['cycle'] == 'none' and state_attr('sensor.rest980_2', 'cleanMissionStatus')['phase'] == 'stop' %} + Stopped + {% else %} + {% set mapper = { + 'charge' : 'Charge', + 'run' : 'Run', + 'stop' : 'Paused', + 'stuck' : 'Stuck', + 'hmUsrDock' : 'Sent Home', + 'hmMidMsn' : 'Mid Dock', + 'hmPostMsn' : 'Final Dock' } %} + {% set state = state_attr('sensor.rest980_2', 'cleanMissionStatus')['phase'] %} + {{ mapper[state] if state in mapper else state }} + {% endif %} + location: >- + {% if state_attr('sensor.rest980_2', 'pose')['theta'] is defined %} + ({{ state_attr('sensor.rest980_2', 'pose')['point']['x'] }}, {{ state_attr('sensor.rest980_2', 'pose')['point']['y'] }}, {{ state_attr('sensor.rest980_2', 'pose')['theta'] }}) + {% else %} + n-a + {% endif %} + clean_mode: >- + {{ state_attr('sensor.rest980_2', 'padWetness')['disposable'] }} + mop_behavior: >- + {% if state_attr('sensor.rest980_2', 'rankOverlap') is defined %} + {% set mapper = { + 25 : 'Extended', + 67 : 'Standard', + 85 : 'Deep' } %} + {% set state = state_attr('sensor.rest980_2', 'rankOverlap') %} + {{ mapper[state] if state in mapper else state }} + {% else %} + n-a + {% endif %} + pad: >- + {% if state_attr('sensor.rest980_2', 'detectedPad') is defined %} + {% set mapper = { + 'reusableDry' : 'Dry', + 'reusableWet' : 'Wet', + 'dispDry' : 'Single Dry', + 'dispWet' : 'Single Wet' + } %} + {% set state = state_attr('sensor.rest980_2', 'detectedPad') %} + {{ mapper[state] if state in mapper else state}} + {% else %} + n-a + {% endif %} + tank: >- + {% if state_attr('sensor.rest980_2', 'mopReady')['tankPresent'] is defined %} + {% if state_attr('sensor.rest980_2', 'mopReady')['tankPresent'] and state_attr('sensor.rest980_2', 'mopReady')['lidClosed'] %} + Ready + {% elif state_attr('sensor.rest980_2', 'mopReady')['tankPresent'] and (state_attr('sensor.rest980_2', 'mopReady')['lidClosed'] == false) %} + Lid Open + {% else %} + Fill Tank + {% endif %} + {% else %} + n-a + {% endif %} + rssi: >- + {% if state_attr('sensor.rest980_2', 'signal')['rssi'] is defined %} + {{ state_attr('sensor.rest980_2', 'signal')['rssi'] }} + {% else %} + n-a + {% endif %} + total_area: >- + {% if state_attr('sensor.rest980_2', 'bbrun')['sqft'] is defined %} + {{ (state_attr('sensor.rest980_2', 'bbrun')['sqft'] / 10.764 * 100)| round() }}m² + {% else %} + n-a + {% endif %} + # {{ (state_attr('sensor.rest980_2', 'bbrun')['sqft'] }}ft² + total_time: >- + {% if state_attr('sensor.rest980_2', 'bbrun')['hr'] is defined %} + {{ state_attr('sensor.rest980_2', 'bbrun')['hr'] }}h {{ state_attr('sensor.rest980_2', 'bbrun')['min'] }}m + {% else %} + n-a + {% endif %} + total_jobs: >- + {% if state_attr('sensor.rest980_2', 'bbmssn')['nMssn'] is defined %} + {{ state_attr('sensor.rest980_2', 'bbmssn')['nMssn'] }} + {% else %} + n-a + {% endif %} + job_initiator: >- + {% set mapper = { + 'schedule' : 'Scheduler', + 'rmtApp' : 'App', + 'manual' : 'Robot', + 'localApp' : 'HA' } %} + {% set state = state_attr('sensor.rest980_2', 'cleanMissionStatus')['initiator'] %} + {{ mapper[state] if state in mapper else state }} + job_time: >- + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['mssnStrtTm'] is defined %} + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['mssnStrtTm'] != 0 %} + {% set time = state_attr('sensor.rest980_2', 'cleanMissionStatus')['mssnStrtTm'] | timestamp_local %} + {% set elapsed = ((as_timestamp(now()) - as_timestamp(time)) / 60) | round(0) %} + {% if elapsed > 60 %} + {{ elapsed // 60 }}h {{ '{:0>2d}'.format(elapsed%60) }}m + {% else %} + {{elapsed}}m + {% endif %} + {% else %} + n-a + {% endif %} + {% else %} + n-a + {% endif %} + job_recharge: >- + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['rechrgTm'] is defined %} + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['rechrgTm'] != 0 %} + {% set time = state_attr('sensor.rest980_2', 'cleanMissionStatus')['rechrgTm'] | timestamp_local %} + {% set resume = ((as_timestamp(time) - as_timestamp(now())) / 60) | round(0) %} + {% if resume > 60 %} + {{ resume // 60 }}h {{ '{:0>2d}'.format(resume%60) }}m + {% else %} + {{resume}}m + {% endif %} + {% else %} + n-a + {% endif %} + {% else %} + n-a + {% endif %} + job_expires: >- + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['expireTm'] is defined %} + {% if state_attr('sensor.rest980_2', 'cleanMissionStatus')['expireTm'] != 0 %} + {% set time = state_attr('sensor.rest980_2', 'cleanMissionStatus')['expireTm'] | timestamp_local %} + {% set resume = ((as_timestamp(time) - as_timestamp(now())) / 60) | round(0) %} + {% if resume > 60 %} + {{ resume // 60 }}h {{ '{:0>2d}'.format(resume%60) }}m + {% else %} + {{resume}}m + {% endif %} + {% else %} + n-a + {% endif %} + {% else %} + n-a + {% endif %} + clean_edges: >- + {% if state_attr('sensor.rest980_2', 'openOnly') is defined %} + {% if state_attr('sensor.rest980_2', 'openOnly') == 'true' %} + False + {% else %} + True + {% endif %} + {% else %} + n-a + {% endif %} + maint_due: >- + {% if is_state('input_boolean.mop_maint_due', 'on') %} + True + {% else %} + False + {% endif %} + pmap0_id: >- + {{ state_attr('sensor.rest980_2', 'pmaps')[0] | regex_findall_index("{'([\w\-]+)': '\w+'}") }} + pmap1_id: >- + {{ state_attr('sensor.rest980_2', 'pmaps')[1] | regex_findall_index("{'([\w\-]+)': '\w+'}") }} + pmap2_id: >- + {{ state_attr('sensor.rest980_2', 'pmaps')[2] | regex_findall_index("{'([\w\-]+)': '\w+'}") }} + pmap3_id: >- + {{ state_attr('sensor.rest980_2', 'pmaps')[3] | regex_findall_index("{'([\w\-]+)': '\w+'}") }} + mop_location: + friendly_name_template: >- + {{ state_attr('sensor.rest980_2', 'name') }} Location + value_template: >- + {{ state_attr('sensor.mop', 'location') }} + icon_template: mdi:home-map-marker + +################################### +# Rest Command +################################### + +rest_command: + mop_action: + url: >- + {{ states('input_text.mop_action') }}{{ command }} + verify_ssl: !secret mop_verify_ssl + method: 'get' + timeout: 20 + mop_clean: + url: >- + {{ states('input_text.mop_action') }}cleanRoom + verify_ssl: !secret mop_verify_ssl + method: POST + content_type: 'application/json' + payload: '{{ payload }}' + +################################### +# Input Boolean +################################### + +input_boolean: + mop_clean_kitchen: + name: Kitchen + icon: mdi:silverware-fork-knife + mop_clean_entry: + name: Entry + icon: mdi:coat-rack + mop_clean_bathroom: + name: Bathroom + icon: mdi:shower + mop_clean_master_ensuite: + name: Master Ensuite + icon: mdi:shower + mop_maint_due: + name: Mop Maintenance + icon: mdi:wrench + mop_schedule_1: + name: Mop Schedule 1 + icon: mdi:timetable + mop_schedule_2: + name: Mop Schedule 2 + icon: mdi:timetable + +################################### +# Input Text +################################### + +input_text: + mop_action: + name: Mop Action URL + initial: !secret mop_action + mop_map: + name: Mop Map URL + initial: !secret mop_map + mop_log: + name: Mop Log Path + initial: !secret mop_log + mop_dir: + name: Mop Dir Path + initial: !secret mop_dir + mop_rooms: + name: Mop Rooms + max: 255 + mop_pmap_id: + name: Mop PMap + + mop_clean_kitchen: + name: Kitchen + initial: !secret mop_kitchen + mop_clean_entry: + name: Entry + initial: !secret mop_entry + mop_clean_bathroom: + name: Bathroom + initial: !secret mop_bathroom + mop_clean_master_ensuite: + name: Master Ensuite + initial: !secret mop_master_ensuite + +################################### +# Group +################################### + +group: + mop_rooms: + entities: + - input_boolean.mop_clean_kitchen + - input_boolean.mop_clean_entry + - input_boolean.mop_clean_bathroom + - input_boolean.mop_clean_master_ensuite + +################################### +# Automation +################################### + +automation: + # Initiate Selective Room Clean + - alias: Mop Clean Rooms + trigger: + - platform: event + event_type: initiate_mop_clean + condition: + condition: not + conditions: + - condition: state + entity_id: input_text.mop_rooms + state: '' + action: + - service: rest_command.mop_clean + data_template: + payload: > + {% set rooms = states('input_text.mop_rooms') %} + {% if rooms[-1:] == ',' %} + {% set rooms = rooms[:-1] %} + {% endif %} + {% set rooms = rooms.split(",") %} + { + "ordered": 1, + "pmap_id": "{{ states('input_text.mop_pmap_id') | string }}", + "regions": [{% for id in rooms %} + {% set room = 'input_text.mop_clean_' + id %} {{ states(room) | string }} {%- if not loop.last %},{%- endif %} + {%- endfor %} + ] + } + - service: input_text.set_value + data: + entity_id: input_text.mop_rooms + value: '' + - service: input_boolean.turn_off + data: + entity_id: group.mop_rooms + + # Update Mop REST Sensor for Location Details + - alias: Mop Update Location + initial_state: true + trigger: + - platform: time_pattern + seconds: /2 + - platform: event + event_type: call_service + event_data: + domain: rest_command + service: mop_clean + condition: + condition: or + conditions: + - condition: template + value_template: "{{ is_state_attr('sensor.mop', 'phase', 'Run') }}" + - condition: template + value_template: "{{ is_state_attr('sensor.mop', 'phase', 'Sent Home') }}" + - condition: template + value_template: "{{ is_state_attr('sensor.mop', 'phase', 'Mid Dock') }}" + - condition: template + value_template: "{{ is_state_attr('sensor.mop', 'phase', 'Final Dock') }}" + action: + - service: homeassistant.update_entity + entity_id: sensor.rest980_2 + + # Log Mop Location to File + - alias: Mop Log Position + initial_state: true + trigger: + platform: state + entity_id: sensor.mop_location + condition: + condition: or + conditions: + - condition: state + entity_id: sensor.mop + state: 'Clean' + - condition: state + entity_id: sensor.mop + state: 'Train' + action: + - service: notify.mopfile + data_template: + message: "{{ states('sensor.mop_location') }}" + + # Initialize Blank Mop Log File + - alias: Mop Clean Log + initial_state: true + trigger: + - platform: state + entity_id: sensor.mop + from: 'Ready' + to: 'Clean' + - platform: state + entity_id: sensor.mop + from: 'Ready' + to: 'Train' + action: + - service: shell_command.mop_clear_log + - service: shell_command.mop_clear_image + + # Update Mop Log File with Finished Status + - alias: Mop Notify on Finished Cleaning + initial_state: true + trigger: + - platform: state + entity_id: sensor.mop + from: 'Clean' + to: 'Ready' + - platform: state + entity_id: sensor.mop + from: 'Train' + to: 'Ready' + - platform: state + entity_id: sensor.mop + from: 'Pending' + to: 'Ready' + action: + - service: notify.mopfile + data_template: + message: "Finished" + + # Update Mop Log File with Stuck Status + - alias: Mop Notify on Stuck Status + initial_state: true + trigger: + platform: template + value_template: "{{ is_state_attr('sensor.mop', 'phase', 'Stuck') }}" + action: + - service: notify.mopfile + data_template: + message: "Stuck" + # DELETE BELOW SECTION IF YOU DONT WANT NOTIFICATIONS + - delay: 5 + - service: !secret mop_notify + data_template: + title: "{{ state_attr('sensor.rest980_2', 'name') }} requires your attention" + message: "{{ state_attr('sensor.rest980_2', 'name') }} is stuck." + # ============= + # NOTICE THIS SECTION IS VALID FOR IOS USERS ONLY! + data: + attachment: + content-type: jpeg + push: + category: camera + entity_id: camera.braava + # ============= + + # Generate Complete Mop Map + - alias: Mop Generate Image after Cleaning + initial_state: true + trigger: + - platform: state + entity_id: sensor.mop + from: 'Clean' + to: 'Ready' + for: + seconds: 10 + - platform: state + entity_id: sensor.mop + from: 'Train' + to: 'Ready' + for: + seconds: 10 + - platform: state + entity_id: sensor.mop + from: 'Pending' + to: 'Ready' + for: + seconds: 10 + action: + - service: shell_command.mop_generate_image + # DELETE BELOW SECTION IF YOU DONT WANT NOTIFICATIONS + - delay: 5 + - service: !secret mop_notify + data_template: + title: "{{ state_attr('sensor.rest980_2', 'name') }}" + message: "{{ state_attr('sensor.rest980_2', 'name') }} successfully completed a job!" + # ============= + # NOTICE THIS SECTION IS VALID FOR IOS USERS ONLY! + data: + attachment: + content-type: jpeg + push: + category: camera + entity_id: camera.braava + # ============= + + # Maintenance Check + - alias: Mop Maintenance Check + initial_state: true + trigger: + - platform: time_pattern + minutes: /15 + - platform: state + entity_id: [ + 'sensor.mop_maint_clean_contacts', + 'sensor.mop_maint_clean_pad', + 'sensor.mop_maint_clean_wheels', + 'sensor.mop_maint_replace_pad' + ] + action: + - service: >- + {% set ns = namespace(count = 0) %} + {% for item in states.sensor if 'sensor.mop_maint' in item.entity_id and (state_attr(item.entity_id, 'timeout_timestamp') < as_timestamp(now())) %} + {% set ns.count = loop.index %} + {% endfor %} + {% if ns.count > 0 %} + input_boolean.turn_on + {% else %} + input_boolean.turn_off + {% endif %} + entity_id: input_boolean.mop_maint_due + + # Add Rooms for Ordered Cleaning + - alias: Mop Add Rooms for Cleaning + initial_state: true + trigger: + platform: state + entity_id: [ + 'input_boolean.mop_clean_kitchen', + 'input_boolean.mop_clean_entry', + 'input_boolean.mop_clean_bathroom', + 'input_boolean.mop_clean_master_ensuite' + ] + to: 'on' + action: + service: input_text.set_value + data_template: + entity_id: input_text.mop_rooms + value: | + {% set room = trigger.entity_id %} + {% set room = room.replace("input_boolean.mop_clean_","") %} + {% if ((states('input_text.mop_rooms') == "unknown") or (states('input_text.mop_rooms') == "")) %} + {{ room }}, + {% else %} + {{ states('input_text.mop_rooms') }}{{ room }}, + {% endif %} + + # Remove Rooms for Ordered Cleaning + - alias: Mop Remove Rooms for Cleaning + initial_state: true + trigger: + platform: state + entity_id: [ + 'input_boolean.mop_clean_kitchen', + 'input_boolean.mop_clean_entry', + 'input_boolean.mop_clean_bathroom', + 'input_boolean.mop_clean_master_ensuite' + ] + to: 'off' + action: + service: input_text.set_value + data_template: + entity_id: input_text.mop_rooms + value: | + {% set room = trigger.entity_id %} + {% set room = room.replace("input_boolean.mop_clean_","") %} + {% set result = states('input_text.mop_rooms') %} + {% set result = result.replace(room + ",","") %} + {{ result }} + + # Mop Cleaning Schedule 1 + - alias: Mop Cleaning Schedule 1 + initial_state: true + trigger: + platform: time_pattern + hours: "11" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.mop_schedule_1 + state: 'on' + - condition: state + entity_id: input_boolean.vacation + state: 'off' + - condition: time + weekday: + - mon + - wed + - fri + action: + - service: input_select.select_option + data: + option: 'Master Ensuite' + entity_id: input_select.mop_maps + - service: input_text.set_value + entity_id: input_text.mop_rooms + data_template: + entity_id: input_text.mop_rooms + value: "master_ensuite" + - service: automation.trigger + entity_id: automation.mop_clean_rooms + + # Mop Cleaning Schedule 2 + - alias: Mop Cleaning Schedule 2 + initial_state: true + trigger: + platform: time_pattern + hours: "12" + condition: + condition: and + conditions: + - condition: state + entity_id: input_boolean.mop_schedule_2 + state: 'on' + - condition: state + entity_id: input_boolean.vacation + state: 'off' + - condition: time + weekday: + - sun + action: + - service: input_text.set_value + entity_id: input_text.mop_rooms + data_template: + entity_id: input_text.mop_rooms + value: "kitchen,entry,bathroom,master_ensuite" + - service: automation.trigger + entity_id: automation.mop_clean_rooms + + # Delete Old Mop Image Files + - alias: Mop Delete Images + initial_state: true + trigger: + platform: time_pattern + hours: "00" + action: + - service: shell_command.mop_delete_images + +################################### +# Notify +################################### + +notify: + - name: MopFile + platform: file + filename: !secret mop_log + +################################### +# Camera +################################### + +camera: + - platform: generic + still_image_url: >- + {{ states('input_text.mop_map') }} + content_type: image/png + name: Braava + +################################### +# Shell Command +################################### + +shell_command: + mop_clear_log: cp /dev/null {{ states('input_text.mop_log') }} + mop_clear_image: curl -X GET -s -O /dev/null '{{ states("input_text.mop_map") }}?clear=true' + mop_generate_image: curl -X GET -s -O /dev/null '{{ states("input_text.mop_map") }}?last=true' + mop_delete_images: find {{ states('input_text.mop_dir') }} -regex ".*[0-9]\.png" -type f -mtime +20 -exec rm -f {} \; diff --git a/mop_multifloor_example.yaml b/mop_multifloor_example.yaml new file mode 100644 index 0000000..72671ac --- /dev/null +++ b/mop_multifloor_example.yaml @@ -0,0 +1,109 @@ +################################### +# iRobot Mop Package - Multi Floor +################################### + +################################### +# Input Select +################################### + +input_select: + mop_maps: + name: Mop Maps + initial: Kitchen + options: + - Kitchen + - Entry + - Bathroom + - Master Ensuite + +################################### +# Input Text +################################### + +input_text: + mop_map_kitchen: + name: Mop Map URL Kitchen + initial: !secret mop_map_kitchen + mop_map_entry: + name: Mop Map URL Entry + initial: !secret mop_map_entry + mop_map_bathroom: + name: Mop Map URL Bathroom + initial: !secret mop_map_bathroom + mop_map_ensuite: + name: Mop Map URL Ensuite + initial: !secret mop_map_ensuite + +################################### +# Automation +################################### + +automation: + # Map Selection on Change and Startup + - alias: Mop Map Selection + initial_state: true + trigger: + - platform: state + entity_id: input_select.mop_maps + - platform: homeassistant + event: start + action: + # Turn off all Mop Rooms + - service: input_boolean.turn_off + data: + entity_id: group.mop_rooms + # Reset Ordered Mop Rooms List + - service: input_text.set_value + data: + entity_id: input_text.mop_rooms + value: '' + # Set Correct PMap based on Room + - service: input_text.set_value + data_template: + entity_id: input_text.mop_pmap_id + value: >- + {% if states('input_select.mop_maps') == 'Kitchen' %} + {{ state_attr('sensor.mop', 'pmap3_id') }} + {% elif states('input_select.mop_maps') == 'Entry' %} + {{ state_attr('sensor.mop', 'pmap2_id') }} + {% elif states('input_select.mop_maps') == 'Bathroom' %} + {{ state_attr('sensor.mop', 'pmap1_id') }} + {% elif states('input_select.mop_maps') == 'Master Ensuite' %} + {{ state_attr('sensor.mop', 'pmap0_id') }} + {% endif %} + # Set Correct Map URL for Shell Commands and Camera + - service: input_text.set_value + data_template: + entity_id: input_text.mop_map + value: >- + {% if states('input_select.mop_maps') == 'Kitchen' %} + {{ states('input_text.mop_map_kitchen') }} + {% elif states('input_select.mop_maps') == 'Entry' %} + {{ states('input_text.mop_map_entry') }} + {% elif states('input_select.mop_maps') == 'Bathroom' %} + {{ states('input_text.mop_map_bathroom') }} + {% elif states('input_select.mop_maps') == 'Master Ensuite' %} + {{ states('input_text.mop_map_ensuite') }} + {% endif %} + +################################### +# Camera +################################### + +camera: + - platform: generic + still_image_url: !secret mop_map_kitchen + content_type: image/png + name: Braava Kitchen + - platform: generic + still_image_url: !secret mop_map_entry + content_type: image/png + name: Braava Entry + - platform: generic + still_image_url: !secret mop_map_bathroom + content_type: image/png + name: Braava Bathroom + - platform: generic + still_image_url: !secret mop_map_ensuite + content_type: image/png + name: Braava Ensuite \ No newline at end of file diff --git a/secrets.yaml b/secrets.yaml index 93f95c1..45f6c50 100644 --- a/secrets.yaml +++ b/secrets.yaml @@ -20,4 +20,21 @@ vacuum_wardrobe: '{"region_id": "9", "type": "rid"}' vacuum_master_ensuite: '{"region_id": "10", "type": "rid"}' vacuum_master_bedroom: '{"region_id": "11", "type": "rid"}' vacuum_table: '{"region_id": "17", "type": "rid"}' -vacuum_clean_zone_fridge: '{"region_id": "0", "type": "zid"}' \ No newline at end of file +vacuum_clean_zone_fridge: '{"region_id": "0", "type": "zid"}' + +# Mop +mop_state: http://:/api/local/info/state +mop_action: http://:/api/local/action/ +mop_verify_ssl: false +mop_notify: notify.mobile_app_xxxxxxx # You can also use a notify group here +mop_map: http://:/mop_kitchen.php # My example is multi floor-plan, could just be mop.php +mop_log: /config/vacuum/mop.log +mop_dir: /config/vacuum +mop_kitchen: '{"region_id": "0", "type": "rid"}' +mop_entry: '{"region_id": "0", "type": "rid"}' +mop_bathroom: '{"region_id": "0", "type": "rid"}' +mop_master_ensuite: '{"region_id": "0", "type": "rid"}' +mop_map_kitchen: http://:/mop_kitchen.php +mop_map_entry: http://:/mop_entry.php +mop_map_bathroom: http://:/mop_bathroom.php +mop_map_ensuite: http://:/mop_ensuite.php \ No newline at end of file diff --git a/vacuum/braava.png b/vacuum/braava.png new file mode 100755 index 0000000000000000000000000000000000000000..ac86ce1f2317dd83c04bc5210462bf94092890f9 GIT binary patch literal 1578 zcmV+_2G#kAP)P000>X1^@s6#OZ}&00001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T0wb>$mfA8&VWhp;S~t4N@tK3M3E`Vo|BA_yPO@B>n(m%Ler?AXX`>uDU`( zL86gw9Q zZDn4at0ghg>$g`=PMY_Qo6V2A?Uw5WK23d}BFJOPNvTwd zQd){uR#qsR&5~i;WJA*FG?mLs!e@%V@1=JB;hj~-DFhNMA( zZs=rNwv6lxul@b(Io-K)hwk6MPjA1oNsKCvW1I;n3L}bCgq`pkpY0plaG<^q2fj~r zI<0$_Wqq@=vqQC7O+3{!je;N$B`o4KPVI))w}Av7;(-qfe&(J^H*S;Bh?1Hg`yNU2n&LcWNY=g80v z^1Lxk#$)0$hHe6pE23#S3}`ay*vIoqxk?8IFA$M1qp4UqyO2S++&4 zo0IuyJd!Z=x_#>Qdc^w&gT5dQ;H{f?$+AokAwk5F#~Mcm=PgY|${LMEl+EP8B#)eQ zh6=?Z)v7f*I~!8B+oN`?O)83UutMch4Py~uNx@iR_zV;;mP(Yi9YMjF=g5pkLxfBX z7>Sxrr+}Ts3kRGx0JSV$bUJOxPqkW+WE+M7FF6l}gs@gylH7H=9XQiPc#GJh3zAGW zBY6S)Y%L076@(tbW5Y-W952Hgrmz91F%p+SEL!5Noy%@pI2Qb3s( zXINJlgeYQVF*YlTBC<>iG1UYn!#zAal)?&8H%K1FNal;eG-qSHk7=tT@wS}?QXM(6 zkmDH}8ER~mJ!hCUzlasL2O1+!tb_$|CErWti^2wYWJDei4v_;DN8Dy$5Y#4(DZHcs z#RDX;F{hyK2WTTrcw)pj|MV5WJe1z` z*Ea<8XsV4YP7q%%kag(?c29yT}Ko+5-D^a&co&Pb!kBy z&dJ1kxV3fd6VU+XRG2Zbn!&qJ#(6<9f_eNmH#b4+GrD^9>IFjoT=*RC4}c6ETcU!# z<*agQ+|B znw8E+7S4}CduNupsE-J1CKxKc?7Y2P7?Db5BYFxgr67&cPFpCf9F#HqSXwzN{m*kY zfE*ya>9h&4@OwsuLSB?7`t$(E(5LYr=~K3@fg;>paeay*Hc~3)(6`_<6f@G~?m&py zAUw~zd35T6kOem(YAr2Dq#IMJh1*hz5u7`kcTX1XzE`enfSzE8lcP)ZRVo%;^r|wg zt}R1Z6&~lPj(OH`gvM`etP2WEMF)>4@B!-=Bd~VBN#dHAiwh6IajK#TGE;1Xq}4v6 zr_UbCXxZ=-$|}_=y}PwV#X{+|^DIFAfb~meeBX1%li_c%ir?oInB(P(hnX`N3~-mU z3I7MA=&F?(JWsmr;)4rY3R=^(f3lhE4K&!ng|>db>*Cdqn0}32DE-$BB}kKdoS*Y6 z{f$Q!754s&Yx%o;z94^#-qeKKZde49a?P cNFJhp0YoQbJf~MZDF6Tf07*qoM6N<$f>j;dxBvhE literal 0 HcmV?d00001 diff --git a/vacuum/braava_charging.png b/vacuum/braava_charging.png new file mode 100755 index 0000000000000000000000000000000000000000..4878b25d655efd560bf9df704d14e4744b7ecabe GIT binary patch literal 1676 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