iRobot Roomba i7+ Configuration using Rest980

I’ve made a little progress. I copied files to vacuum folder, edited as few as possible to avoid any issues but getting this error when trying to restart HA.

Logger: homeassistant.components.websocket_api.http.connection
Source: components/hassio/__init__.py:598
Integration: Home Assistant WebSocket API (documentation, issues)
First occurred: 14:58:48 (2 occurrences)
Last logged: 14:58:55

[547922853744] The system cannot restart because the configuration is not valid: expected a dictionary for dictionary value @ data['packages']['type']
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 185, in handle_call_service
    await hass.services.async_call(
  File "/usr/src/homeassistant/homeassistant/core.py", line 1495, in async_call
    task.result()
  File "/usr/src/homeassistant/homeassistant/core.py", line 1530, in _execute_service
    await handler.job.target(service_call)
  File "/usr/src/homeassistant/homeassistant/components/hassio/__init__.py", line 598, in async_handle_core_service
    raise HomeAssistantError(
homeassistant.exceptions.HomeAssistantError: The system cannot restart because the configuration is not valid: expected a dictionary for dictionary value @ data['packages']['type']

Files in the vacuum folder
image

vacuum.yaml (just removed the rooms that I don’t have)

###################################
# iRobot Vacuum Package
###################################

###################################
# Sensor
###################################

sensor:
  # Roomba via Rest980 Docker Image
  - platform: rest
    name: rest980
    json_attributes:
      - batPct
      - bin
      - cleanMissionStatus
      - dock
      - pose
      - signal
      - bbmssn
      - bbrun
      - name
      - pmaps
      - vacHigh
      - openOnly
      - twoPass
      - noAutoPasses
      - softwareVer
    resource: !secret vacuum_state
    value_template: 'OK'
    scan_interval: 10
  - platform: template
    sensors:
      vacuum:
        friendly_name_template: >-
          {{ state_attr('sensor.rest980', 'name') }}
        value_template: >-
          {% if state_attr('sensor.rest980', 'cleanMissionStatus')['cycle'] == 'none' and state_attr('sensor.rest980', 'cleanMissionStatus')['notReady'] == 39 %}
            Pending
          {% elif state_attr('sensor.rest980', 'cleanMissionStatus')['notReady'] > 0 %}
            Not Ready
          {% else %}
          {% set mapper =  {
            'clean' : 'Clean',
            'quick' : 'Clean',
            'spot' : 'Spot',
            'evac' : 'Empty',
            'dock' : 'Dock',
            'train' : 'Train',
            'none' : 'Ready' } %}
          {% set state =  state_attr('sensor.rest980', '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',
              39 : 'Pending',
              48 : 'Path Blocked' } %}
            {% set state =  state_attr('sensor.rest980', 'cleanMissionStatus')['notReady'] %}
            {{ mapper[state] if state in mapper else state }}
          error_msg: >-
            {% set mapper =  {
              0 : 'n-a',
              15 : 'Reboot Required',
              18 : 'Docking Issue'} %}
            {% set state =  state_attr('sensor.rest980', 'cleanMissionStatus')['error'] %}
            {{ mapper[state] if state in mapper else state }}
          battery: >-
            {{ state_attr('sensor.rest980', 'batPct') }} %
          software_ver: >-
            {% if state_attr('sensor.rest980', 'softwareVer') is defined %}
            {% set version = state_attr('sensor.rest980', 'softwareVer') %}
              {{ version.split('+')[1] }} 
            {% else %}
              n-a
            {% endif %}
          phase: >-
            {% if state_attr('sensor.rest980', 'cleanMissionStatus')['phase'] == 'charge' and state_attr('sensor.rest980', 'batPct') == 100 %}
              Idle
            {% elif state_attr('sensor.rest980', 'cleanMissionStatus')['cycle'] == 'none' and state_attr('sensor.rest980', 'cleanMissionStatus')['phase'] == 'stop' %}
              Stopped
            {% else %}
            {% set mapper =  {
              'charge' : 'Charge',
              'run' : 'Run',
              'evac' : 'Empty',
              'stop' : 'Paused',
              'stuck' : 'Stuck',
              'hmUsrDock' : 'Sent Home',
              'hmMidMsn' : 'Mid Dock',
              'hmPostMsn' : 'Final Dock' } %}
            {% set state =  state_attr('sensor.rest980', 'cleanMissionStatus')['phase'] %}
            {{ mapper[state] if state in mapper else state }}
            {% endif %}
          bin: >-
            {% set mapper =  {
              true : 'Full',
              false : 'Not Full' } %}
            {% set state =  state_attr('sensor.rest980', 'bin')['full'] %}
            {{ mapper[state] if state in mapper else state }}
          bin_present: >-
            {% set mapper =  {
              true : 'Yes',
              false : 'No' } %}
            {% set state =  state_attr('sensor.rest980', 'bin')['present'] %}
            {{ mapper[state] if state in mapper else state }}
          clean_base: >-
            {% if state_attr('sensor.rest980', 'dock')['state'] is defined %}
              {% set mapper =  {
                300 : 'Ready',
                301 : 'Ready',
                302 : 'Empty',
                303 : 'Empty',
                350 : 'Bag Missing',
                351 : 'Clogged',
                352 : 'Sealing Problem',
                353 : 'Bag Full',
                360 : 'Comms Problem' } %}
              {% set state =  state_attr('sensor.rest980', 'dock')['state'] %}
              {{ mapper[state] if state in mapper else state }}
            {% else %}
              n-a
            {% endif %}
          location: >-
            {% if state_attr('sensor.rest980', 'pose')['theta'] is defined %}
              ({{ state_attr('sensor.rest980', 'pose')['point']['x'] }}, {{ state_attr('sensor.rest980', 'pose')['point']['y'] }}, {{ state_attr('sensor.rest980', 'pose')['theta'] }})
            {% else %}
              n-a
            {% endif %}
          rssi: >-
            {% if state_attr('sensor.rest980', 'signal')['rssi'] is defined %}
              {{ state_attr('sensor.rest980', 'signal')['rssi'] }}
            {% else %}
              n-a
            {% endif %}
          total_area: >-
            {% if state_attr('sensor.rest980', 'bbrun')['sqft'] is defined %}
              {{ (state_attr('sensor.rest980', 'bbrun')['sqft'] / 10.764 * 100)| round() }}m²
            {% else %}
              n-a
            {% endif %}
          #   {{ (state_attr('sensor.rest980', 'bbrun')['sqft'] }}ft²
          total_time: >-
            {% if state_attr('sensor.rest980', 'bbrun')['hr'] is defined %}
              {{ state_attr('sensor.rest980', 'bbrun')['hr'] }}h {{ state_attr('sensor.rest980', 'bbrun')['min'] }}m
            {% else %}
              n-a
            {% endif %}
          total_jobs: >-
            {% if state_attr('sensor.rest980', 'bbmssn')['nMssn'] is defined %}
              {{ state_attr('sensor.rest980', 'bbmssn')['nMssn'] }}
            {% else %}
              n-a
            {% endif %}
          dirt_events: >-
            {% if state_attr('sensor.rest980', 'bbrun')['nScrubs'] is defined %}
              {{ state_attr('sensor.rest980', 'bbrun')['nScrubs'] }}
            {% else %}
              n-a
            {% endif %}
          # evac_events I7+/S9+ Models (Clean Base)
          evac_events: >-
            {% if state_attr('sensor.rest980', 'bbrun')['nEvacs'] is defined %}
              {{ state_attr('sensor.rest980', 'bbrun')['nEvacs'] }}  
            {% else %}
              n-a
            {% endif %}
          job_initiator: >-
            {% set mapper =  {
              'schedule' : 'Scheduler',
              'rmtApp' : 'App',
              'manual' : 'Robot',
              'localApp' : 'HA' } %}
            {% set state =  state_attr('sensor.rest980', 'cleanMissionStatus')['initiator'] %}
            {{ mapper[state] if state in mapper else state }}
          job_time: >-
            {% if state_attr('sensor.rest980', 'cleanMissionStatus')['mssnStrtTm'] is defined %}
              {% if state_attr('sensor.rest980', 'cleanMissionStatus')['mssnStrtTm'] != 0 %}
                {% set time = state_attr('sensor.rest980', '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', 'cleanMissionStatus')['rechrgTm'] is defined %}
              {% if state_attr('sensor.rest980', 'cleanMissionStatus')['rechrgTm'] != 0 %}
                {% set time = state_attr('sensor.rest980', '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', 'cleanMissionStatus')['expireTm'] is defined %}
              {% if state_attr('sensor.rest980', 'cleanMissionStatus')['expireTm'] != 0 %}
                {% set time = state_attr('sensor.rest980', '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_mode: >-
            {% if state_attr('sensor.rest980', 'noAutoPasses') is defined and state_attr('sensor.rest980', 'twoPass') is defined %}
              {% if state_attr('sensor.rest980', 'noAutoPasses') == true and state_attr('sensor.rest980', 'twoPass') == false %}
                One
              {% elif state_attr('sensor.rest980', 'noAutoPasses') == true and state_attr('sensor.rest980', 'twoPass') == true %}
                Two
              {% else %}
                Auto
              {% endif %} 
            {% else %}
              n-a
            {% endif %}
          carpet_boost: >-
            {% if state_attr('sensor.rest980', 'vacHigh') is defined %}
              {% if state_attr('sensor.rest980', 'vacHigh') == false and state_attr('sensor.rest980', 'carpetBoost') == false %}
                Eco
              {% elif state_attr('sensor.rest980', 'vacHigh') == true and state_attr('sensor.rest980', 'carpetBoost') == false %}
                Performance
              {% else %}
                Auto
              {% endif %}
            {% else %}
              n-a
            {% endif %}
          clean_edges: >-
            {% if state_attr('sensor.rest980', 'openOnly') is defined %}
              {% if state_attr('sensor.rest980', 'openOnly') == true %}
                False
              {% else %}
                True
              {% endif %}
            {% else %}
              n-a
            {% endif %}
          maint_due: >-
            {% if is_state('input_boolean.vacuum_maint_due', 'on') %}
              True
            {% else %}
              False
            {% endif %}
          # pmap0_id I7/S9 Models
          pmap0_id: >-
            {% if state_attr('sensor.rest980', 'pmaps')[0] is defined %}
              {{ state_attr('sensor.rest980', 'pmaps')[0] | regex_findall_index("{'([\w\-]+)': '\w+'}") }}
            {% else %}
              n-a
            {% endif %}
      vacuum_location:
        friendly_name_template: >-
          {{ state_attr('sensor.rest980', 'name') }} Location
        value_template: >-
          {{ state_attr('sensor.vacuum', 'location') }}
        icon_template: mdi:home-map-marker

###################################
# Rest Command
###################################

rest_command:
  vacuum_action:
    url: >-
      {{ states('input_text.vacuum_action') }}{{ command }}
    verify_ssl: !secret vacuum_verify_ssl
    method: 'get'
    timeout: 20
  vacuum_clean:
    url: >-
      {{ states('input_text.vacuum_action') }}cleanRoom
    verify_ssl: !secret vacuum_verify_ssl
    method: POST
    content_type: 'application/json'
    payload: '{{ payload }}'

###################################
# Input Boolean
###################################

input_boolean:
  vacuum_clean_kitchen:
    name: Kitchen
    icon: mdi:silverware-fork-knife
  vacuum_clean_entry:
    name: Entry
    icon: mdi:coat-rack
  vacuum_clean_hall:
    name: Hall
    icon: mdi:ceiling-light
  vacuum_clean_living_room:
    name: Living Room
    icon: mdi:sofa
  vacuum_clean_bathroom:
    name: Bathroom
    icon: mdi:shower
  vacuum_clean_bedroom:
    name: Bedroom
    icon: mdi:bed-empty
  vacuum_clean_master_bedroom:
    name: Master Bedroom
    icon: mdi:bed-empty
  vacuum_schedule_1:
    name: Vacuum Schedule 1
    icon: mdi:timetable
  vacuum_schedule_2:
    name: Vacuum Schedule 2
    icon: mdi:timetable

###################################
# Input Text
###################################

input_text:
  vacuum_action:
    name: Vacuum Action URL
    initial: !secret vacuum_action
  vacuum_map:
    name: Vacuum Map URL
    initial: !secret vacuum_map
  vacuum_log:
    name: Vacuum Log Path
    initial: !secret vacuum_log
  vacuum_dir:
    name: Vacuum Dir Path
    initial: !secret vacuum_dir
  vacuum_rooms:
    name: Vacuum Rooms
    max: 255

  vacuum_clean_kitchen:
    name: Kitchen
    initial: !secret vacuum_kitchen
  vacuum_clean_entry:
    name: Entry
    initial: !secret vacuum_entry
  vacuum_clean_hall:
    name: Hall
    initial: !secret vacuum_hall
  vacuum_clean_living_room:
    name: Living Room
    initial: !secret vacuum_living_room
  vacuum_clean_bathroom:
    name: Bathroom
    initial: !secret vacuum_bathroom
  vacuum_clean_bedroom:
    name: Bedroom
    initial: !secret vacuum_bedroom
  vacuum_clean_master_bedroom:
    name: Master Bedroom
    initial: !secret vacuum_master_bedroom

###################################
# Group
###################################

group:
  vacuum_rooms:
    entities:
      - input_boolean.vacuum_clean_kitchen
      - input_boolean.vacuum_clean_entry
      - input_boolean.vacuum_clean_hall
      - input_boolean.vacuum_clean_living_room
      - input_boolean.vacuum_clean_bathroom
      - input_boolean.vacuum_clean_bedroom
      - input_boolean.vacuum_clean_master_bedroom

###################################
# Automation
###################################

automation:
  # Initiate Selective Room Clean
  - alias: Vacuum Clean Rooms
    trigger:
    - platform: event
      event_type: initiate_vacuum_clean
    condition:
      condition: not
      conditions:
        - condition: state
          entity_id: input_text.vacuum_rooms
          state: ''
    action:
      - service: rest_command.vacuum_clean
        data_template:
          payload: >
            {% set rooms = states('input_text.vacuum_rooms') %}
            {% if rooms[-1:] == ',' %}
            {% set rooms = rooms[:-1] %}
            {% endif %}
            {% set rooms = rooms.split(",") %}
            {
              "ordered": 1,
              "pmap_id": "{{ state_attr('sensor.vacuum', 'pmap0_id') | string }}",
              "regions": [{% for id in rooms %}
                {% set room = 'input_text.vacuum_clean_' + id %} {{ states(room) | string }} {%- if not loop.last %},{%- endif %}
                {%- endfor %}
              ]
            }
      - service: input_text.set_value
        data:
          entity_id: input_text.vacuum_rooms
          value: ''
      - service: input_boolean.turn_off
        data:
          entity_id: group.vacuum_rooms

  # Update Vacuum REST Sensor for Location Details
  - alias: Vacuum Update Location
    initial_state: true
    trigger:
      - platform: time_pattern
        seconds: /2
      - platform: event
        event_type: call_service
        event_data:
          domain: rest_command
          service: vacuum_clean
    condition: 
      condition: or
      conditions:
        - condition: template
          value_template: "{{ is_state_attr('sensor.vacuum', 'phase', 'Run') }}"
        - condition: template
          value_template: "{{ is_state_attr('sensor.vacuum', 'phase', 'Sent Home') }}"
        - condition: template
          value_template: "{{ is_state_attr('sensor.vacuum', 'phase', 'Mid Dock') }}"
        - condition: template
          value_template: "{{ is_state_attr('sensor.vacuum', 'phase', 'Final Dock') }}"
    action:
      - service: homeassistant.update_entity
        entity_id: sensor.rest980
  
  # Log Vacuum Location to File
  - alias: Vacuum Log Position
    initial_state: true
    trigger:
        platform: state
        entity_id: sensor.vacuum_location
    condition: 
      condition: or
      conditions:
        - condition: state
          entity_id: sensor.vacuum
          state: 'Clean'
        - condition: state
          entity_id: sensor.vacuum
          state: 'Train'
    action:
      - service: notify.vacuumfile
        data_template: 
          message: "{{ states('sensor.vacuum_location') }}"
               
  # Initialize Blank Vacuum Log File
  - alias: Vacuum Clean Log
    initial_state: true
    trigger:
      - platform: state
        entity_id: sensor.vacuum
        from: 'Ready'
        to: 'Clean'
      - platform: state
        entity_id: sensor.vacuum
        from: 'Ready'
        to: 'Train'
    action:
      - service: shell_command.vacuum_clear_log
      - service: shell_command.vacuum_clear_image
          
  # Update Vacuum Log File with Finished Status
  - alias: Vacuum Notify on Finished Cleaning
    initial_state: true
    trigger:
      - platform: state
        entity_id: sensor.vacuum
        from: 'Clean'
        to: 'Ready'
      - platform: state
        entity_id: sensor.vacuum
        from: 'Train'
        to: 'Ready'
      - platform: state
        entity_id: sensor.vacuum
        from: 'Pending'
        to: 'Ready'
    action:
      - service: notify.vacuumfile
        data_template: 
          message: "Finished"

  # Update Vacuum Log File with Stuck Status
  - alias: Vacuum Notify on Stuck Status
    initial_state: true
    trigger:
      platform: template
      value_template: "{{ is_state_attr('sensor.vacuum', 'phase', 'Stuck') }}"
    action:
      - service: notify.vacuumfile
        data_template: 
          message: "Stuck"
      # DELETE BELOW SECTION IF YOU DONT WANT NOTIFICATIONS
      - delay: 5
      - service: !secret vacuum_notify
        data_template:
          title: "{{ state_attr('sensor.rest980', 'name') }} requires your attention"
          message: "{{ state_attr('sensor.rest980', 'name') }} is stuck."
          # =============
          # NOTICE THIS SECTION IS VALID FOR IOS USERS ONLY!
          data:
            attachment:
              content-type: jpeg
            push:
              category: camera
            entity_id: camera.roomba
          # =============

  # Generate Complete Vacuum Map
  - alias: Vacuum Generate Image after Cleaning
    initial_state: true
    trigger:
      - platform: state
        entity_id: sensor.vacuum
        from: 'Clean'
        to: 'Ready'
        for:
          seconds: 10
      - platform: state
        entity_id: sensor.vacuum
        from: 'Train'
        to: 'Ready'
        for:
          seconds: 10
      - platform: state
        entity_id: sensor.vacuum
        from: 'Pending'
        to: 'Ready'
        for:
          seconds: 10
    action:
      - service: shell_command.vacuum_generate_image
      # DELETE BELOW SECTION IF YOU DONT WANT NOTIFICATIONS
      - delay: 5
      - service: !secret vacuum_notify
        data_template:
          title: "{{ state_attr('sensor.rest980', 'name') }}"
          message: "{{ state_attr('sensor.rest980', '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.roomba
          # =============

  # Maintenance Check
  - alias: Vacuum Maintenance Check
    initial_state: true
    trigger:
      - platform: time_pattern
        minutes: /15
      - platform: state
        entity_id: [
          'sensor.vacuum_maint_clean_brushes',
          'sensor.vacuum_maint_clean_contacts',
          'sensor.vacuum_maint_clean_filter',
          'sensor.vacuum_maint_clean_wheel',
          'sensor.vacuum_maint_clean_bin',
          'sensor.vacuum_maint_replace_brushes',
          'sensor.vacuum_maint_replace_filter',
          'sensor.vacuum_maint_replace_wheel'
        ]
    action:
      - service: >-
          {% set ns = namespace(count = 0) %}
          {% for item in states.sensor if 'sensor.vacuum_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.vacuum_maint_due

  # Add Rooms for Ordered Cleaning
  - alias: Vacuum Add Rooms for Cleaning
    initial_state: true
    trigger:
      platform: state
      entity_id: [
        'input_boolean.vacuum_clean_kitchen',
        'input_boolean.vacuum_clean_entry',
        'input_boolean.vacuum_clean_hall',
        'input_boolean.vacuum_clean_living_room',
        'input_boolean.vacuum_clean_bathroom',
        'input_boolean.vacuum_clean_bedroom',
        'input_boolean.vacuum_clean_master_bedroom'
      ]
      to: 'on'
    action:
      service: input_text.set_value
      data_template:
        entity_id: input_text.vacuum_rooms
        value: |
          {% set room = trigger.entity_id %}
          {% set room = room.replace("input_boolean.vacuum_clean_","") %}
          {% if ((states('input_text.vacuum_rooms') == "unknown") or (states('input_text.vacuum_rooms') == "")) %}
            {{ room }},
          {% else %}
            {{ states('input_text.vacuum_rooms') }}{{ room }},
          {% endif %}

  # Remove Rooms for Ordered Cleaning
  - alias: Vacuum Remove Rooms for Cleaning
    initial_state: true
    trigger:
      platform: state
      entity_id: [
        'input_boolean.vacuum_clean_kitchen',
        'input_boolean.vacuum_clean_entry',
        'input_boolean.vacuum_clean_hall',
        'input_boolean.vacuum_clean_living_room',
        'input_boolean.vacuum_clean_bathroom',
        'input_boolean.vacuum_clean_bedroom',
        'input_boolean.vacuum_clean_master_bedroom'
      ]
      to: 'off'
    action:
      service: input_text.set_value
      data_template:
        entity_id: input_text.vacuum_rooms
        value: |
          {% set room = trigger.entity_id %}
          {% set room = room.replace("input_boolean.vacuum_clean_","") %}
          {% set result = states('input_text.vacuum_rooms') %}
          {% set result = result.replace(room + ",","") %}
          {{ result }}

  # Vacuum Cleaning Schedule 1
  - alias: Vacuum Cleaning Schedule 1
    initial_state: true
    trigger:
      platform: time_pattern
      hours: "6"
    condition: 
      condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.vacuum_schedule_1
          state: 'on'
        - condition: state
          entity_id: input_boolean.vacation
          state: 'off'
    action:
      - service: input_text.set_value
        entity_id: input_text.vacuum_rooms
        data_template: 
          entity_id: input_text.vacuum_rooms
          value: "kitchen"
      - service: automation.trigger
        entity_id: automation.vacuum_clean_rooms

  # Vacuum Cleaning Schedule 2
  - alias: Vacuum Cleaning Schedule 2
    initial_state: true
    trigger:
      platform: time_pattern
      hours: "10"
    condition: 
      condition: and
      conditions:
        - condition: state
          entity_id: input_boolean.vacuum_schedule_2
          state: 'on'
        - condition: state
          entity_id: input_boolean.vacation
          state: 'off'
        - condition: time
          weekday:
            - mon
            - wed
            - fri
    action:
      - service: input_text.set_value
        entity_id: input_text.vacuum_rooms
        data_template: 
          entity_id: input_text.vacuum_rooms
          value: "kitchen,entry,hall,living_room,bathroom,bedroom,master_bedroom"
      - service: automation.trigger
        entity_id: automation.vacuum_clean_rooms

  # Delete Old Vacuum Image Files
  - alias: Vacuum Delete Images
    initial_state: true
    trigger:
      platform: time_pattern
      hours: "00"
    action:
      - service: shell_command.vacuum_delete_images

###################################
# Notify
###################################

notify:
    - name: VacuumFile
      platform: file
      filename: !secret vacuum_log

###################################
# Camera
###################################

camera:
  - platform: generic
    still_image_url: >-
      {{ states('input_text.vacuum_map') }}
    content_type: image/png
    name: Roomba

###################################
# Shell Command
###################################

shell_command:
  vacuum_clear_log: cp /dev/null {{ states('input_text.vacuum_log') }}
  vacuum_clear_image: curl -X GET -s -O /dev/null '{{ states("input_text.vacuum_map") }}?clear=true'
  vacuum_generate_image: curl -X GET -s -O /dev/null '{{ states("input_text.vacuum_map") }}?last=true'
  vacuum_delete_images: find {{ states('input_text.vacuum_dir') }} -regex ".*[0-9]\.png" -type f -mtime +20 -exec rm -f {} \;

image.php

<?php
//error_reporting(E_ALL);
//ini_set('display_errors', 1);

// ADJUST THESE PARAMETERS
$robot_log = 'http://192.168.89.241:3001/vacuum.log'; # Could also be HTTPS, or mop.log
$file_append = ''; # Allows differentiation of files for different floors or robots
$robot_type = 'roomba'; # Select between roomba and braava for different icons
$set_first_coordinate = 3; # Ability to skip initial coordinate(s) if incorrect data logged
$overlay_image = 'floor.png'; # Background Layer
$overlay_walls = false; # Allows overlaying of walls, used in fill mode to cover 'spray'
$walls_image = 'walls.png'; # Walls Image must contain transparent floor
$show_stuck_positions = true; 
$line_thickness = 2; # Default 2, Set to ~60 for Fill Mode
$map_width = 1050; # Ensure overlay and wall images match this size
$map_height = 900; # Ensure overlay and wall images match this size
$x_offset = 220;
$y_offset = 220;
$flip_vertical = false;
$flip_horizontal = false;
$render_status_text = true;
$rotate_angle = 0; # Allows rotating of the robot lines
$x_scale=1.00; # Allows scaling of roomba x lines
$y_scale=1.00; # Allows scaling of roomba y lines
$ha_rest980 = 'https://my.publicdomainname.cz:443/api/states/sensor.rest980'; # sensor.rest980_2, if configured for Mop
$ha_token = 'myTokenIsCopiedHere';
$ha_timezone = 'Europe/Prague'; # Supported Timezones https://www.php.net/manual/en/timezones.php
$ha_text_delimiter = " \n"; # How text is displayed on the map top " \n" --> New Line ## " |" --> Show on one line
//
// Line Color - RGB
// -1 represents gradual increase from 0 to 255 based on number of logged locations
//
$color_red = -1;
$color_green = 255;
$color_blue = -1;
//
// Examples
// red = -1 , green = 255 , blue = -1  ---> Green to White Fade
// red = 0 , green = -1 , blue = 255   ---> Blue to Aqua Fade
// red = 0 , green = 0 , blue = 255    ---> Solid Blue
//
$path_opacity = 0.5; # Opacity of Roomba path --> 0.0 = completely transparent, 1.0 = completely opaque
//
///////////////////////////////////////////////////////////////////

if(isset($_GET['clear'])) {
  @unlink("latest".$file_append.".png");
  die();  
}
if(is_file("latest".$file_append.".png")&&!isset($_GET['last'])) {
  header("Content-Type: image/png");
  echo file_get_contents("latest".$file_append.".png");
  die();
}

$coords = file_get_contents($robot_log."?v=".time());
$coords = str_replace("(", "", $coords);
$coords = str_replace(")", "", $coords);
$coords = explode("\n", $coords);

if (count($coords) < 2) {
  echo "No Coordinates found in file, is it reachable and populated? Log file - $robot_log?";
  die();
}

$date = strtotime(substr($coords[0], 42));

$lastline = $coords[sizeof($coords)-2];
$end = ["Stuck", "Finished"]; // PAUSE also available

array_shift($coords);
array_shift($coords);
array_pop($coords);

function imagelinethick($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
    if ($thick == 1) {
        return imageline($image, $x1, $y1, $x2, $y2, $color);
    }
    $t = $thick / 2 - 0.5;
    if ($x1 == $x2 || $y1 == $y2) {
        return imagefilledrectangle($image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
    }
    $k = ($y2 - $y1) / ($x2 - $x1);
    $a = $t / sqrt(1 + pow($k, 2));
    $points = array(
        round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
        round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
        round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
        round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
    );
    imagefilledpolygon($image, $points, 4, $color);
    return imagepolygon($image, $points, 4, $color);
}

$image = imagecreatetruecolor($map_width,$map_height);
imagesavealpha($image, true);
$black = imagecolorallocatealpha($image,0,0,0, 127);
imagefill($image,0,0,$black);

$robot = imagecreatefrompng($robot_type.'.png');
imagealphablending($robot, false);
imagesavealpha($robot, true);

foreach($coords as $i => $coord) {
  # Skip initial coordinates if needed
  if ($i < $set_first_coordinate) {
    continue;
  }
  $split = explode(",", $coord);
  if(sizeof($split)<2) {
    if(($coord == "Stuck") & ($show_stuck_positions)) {
      $robot_stuck = imagecreatefrompng($robot_type.'_stuck.png');
      imagealphablending($robot_stuck, false);
      imagesavealpha($robot_stuck, true);
      $robot_stuck = imagerotate($robot_stuck, $oldtheta*-1, imageColorAllocateAlpha($robot_stuck, 0, 0, 0, 127));
      imagealphablending($robot_stuck, false);
      imagesavealpha($robot_stuck, true);
      imagecopy($image, $robot_stuck, $oldx-10, $oldy-5, 0, 0, imagesx($robot_stuck), imagesy($robot_stuck));
      imagedestroy($robot_stuck);
    }
    continue;
  }
  
  $part= hexdec("ff");
  $part = round($part * $i/sizeof($coords));

  // Calculate Line Color
  $red = ($color_red === -1 ? $part : $color_red);
  $green = ($color_green === -1 ? $part : $color_green);
  $blue = ($color_blue === -1 ? $part : $color_blue);
  
  $alpha = (1.0 - $path_opacity) * 127;
  $color = imagecolorallocatealpha($image, $red, $green, $blue, $alpha);
  $tmpx = $split[1]+$x_offset;
  $tmpy = $split[0]+$y_offset;
  $theta = $split[2];
  
  // Rotate Calculations
  $x=($tmpx*cos(deg2rad($rotate_angle))+$tmpy*sin(deg2rad($rotate_angle)))*$x_scale;
  $y=(-1*$tmpx*sin(deg2rad($rotate_angle))+$tmpy*cos(deg2rad($rotate_angle)))*$y_scale;
  
  $boxsize=4;
  $shift_y = 2;
  $shift_x = -2;
  
  imagerectangle($image, $x+$shift_x, $y+$shift_y, $x+$boxsize+$shift_x, $y+$boxsize+$shift_y, $color);
  if(isset($oldx) && isset($oldy)) {
    imagelinethick($image, $oldx+($boxsize/2)+$shift_x, $oldy+($boxsize/2)+$shift_y, $x+($boxsize/2)+$shift_x, $y+($boxsize/2)+$shift_y, $color, $line_thickness);
  }
  
  if($i+1==sizeof($coords)) {
    if (sizeof($split)>2) {
      $robot = imagerotate($robot, $theta*-1, imageColorAllocateAlpha($robot, 0, 0, 0, 127));
      imagealphablending($robot, false);
      imagesavealpha($robot, true);
      imagecopy($image, $robot, $x-10, $y-5, 0, 0, imagesx($robot), imagesy($robot));
    }
  }
  
  $oldx = $x;
  $oldy = $y;
  $oldtheta = $theta;
}

if(in_array($lastline, $end)) {
  imagedestroy($robot);
  
  if($lastline == "Stuck") {
    $overlayImage = imagecreatefrompng($robot_type.'_stuck.png');
    imagealphablending($overlayImage, false);
    imagesavealpha($overlayImage, true);
    $color = imagecolorallocate($image, 0, 149, 223);
    $finishedRoomba = imagerotate($overlayImage, $theta*-1, imageColorAllocateAlpha($overlayImage, 0, 0, 0, 127));
    imagelinethick($image, $oldx+($boxsize/2), $oldy+($boxsize/2), $x+($boxsize/2)+3, $y+($boxsize/2)+10, $color, 2);
    imagecopy($image, $finishedRoomba, $oldx-10, $oldy-5, 0, 0, imagesx($finishedRoomba), imagesy($finishedRoomba));
  }
  else if($lastline == "Finished") {
    $overlayImage = imagecreatefrompng($robot_type.'_charging.png');
    imagealphablending($overlayImage, false);
    imagesavealpha($overlayImage, true);
    $color = imagecolorallocate($image, 0, 149, 223);
    $finishedRoomba = imagerotate($overlayImage, $theta*-1, imageColorAllocateAlpha($overlayImage, 0, 0, 0, 127));
    imagelinethick($image, $oldx+($boxsize/2), $oldy+($boxsize/2), $x+($boxsize/2)+3, $y+($boxsize/2)+10, $color, 2);
    imagecopy($image, $finishedRoomba, $oldx-10, $oldy-5, 0, 0, imagesx($finishedRoomba), imagesy($finishedRoomba));
  }
  
}
if($flip_vertical) {
  imageflip( $image, IMG_FLIP_VERTICAL );
}

if($flip_horizontal) {
  imageflip( $image, IMG_FLIP_HORIZONTAL );
}

// Create Final Image
$dest = imagecreatetruecolor($map_width,$map_height);
imagesavealpha($dest, true);
// Create Background Image
$overlayImage = imagecreatefrompng($overlay_image);
// Merge Background Image
imagecopy($dest, $overlayImage, 0, 0, 0, 0, imagesx($overlayImage), imagesy($overlayImage));
imagedestroy($overlayImage);
// Merge Roomba Lines
imagecopy($dest, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));

if ($overlay_walls) {
  // Create Walls Image
  $overlayWalls = imagecreatefrompng($walls_image);
  // Merge Walls Image
  imagecopy($dest, $overlayWalls, 0, 0, 0, 0, imagesx($overlayWalls), imagesy($overlayWalls));
  imagedestroy($overlayWalls);
}

$string = "";

if($lastline == "Finished") {
  $finished=true;
  $status="Finished";
  
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $ha_rest980);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $headers = [
      'Authorization: Bearer '.$ha_token,
      'Content-Type: application/json'
  ];
  curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
  $server_output = curl_exec ($ch);
  curl_close ($ch);
  $data = json_decode($server_output);
  $battery_level = $data->attributes->batPct;
  $string.=$ha_text_delimiter." Battery: ".$battery_level."%";
  
}
else if($lastline == "Stuck"){
  $finished=false;
  $status="Stuck";
}
else {
  $finished=false;
  $status="Running";
}

if ($render_status_text) {
  date_default_timezone_set($ha_timezone);
  $dt = date('H:i:s Y-m-d', $date);
  $txt = " Started: ".$dt.$ha_text_delimiter." Status: ".$status.$string;
  $white = imagecolorallocate($dest, 255, 255, 255);
  $font = "./monaco.ttf";
  imagettftext($dest, 10, 0, 5, 15, $white, $font, $txt);
} 

header("Content-Type: image/png");
imagepng($dest);
if(isset($_GET['last'])) {
  imagepng($dest, "latest".$file_append.".png");
  imagepng($dest, $date.$file_append.".png");
}
imagedestroy($dest);
imagedestroy($robot);
imagedestroy($robot_stuck);
imagedestroy($overlayImage);
exit;

secrets.yaml

###################################
# Secrets
###################################

# Vacuum
vacuum_state: http://192.168.89.241:3000/api/local/info/state
vacuum_action: http://192.168.89.241:3000/api/local/action/
vacuum_verify_ssl: false
vacuum_notify: notify.mobile_app_appIdIsHere # You can also use a notify group here
vacuum_map: http://192.168.89.241:3001/image.php
vacuum_log: /config/vacuum/vacuum.log
vacuum_dir: /config/vacuum
vacuum_kitchen: '{"region_id":"10","type":"rid"}'
vacuum_entry: '{"region_id":"9","type":"rid"}'
vacuum_hall: '{"region_id":"4","type":"rid"}'
vacuum_living_room: '{"region_id":"6","type":"rid"}'
vacuum_bedroom: '{"region_id":"12","type":"rid"}'
vacuum_bathroom: '{"region_id":"8","type":"rid"}'
vacuum_master_bedroom: '{"region_id":"11","type":"rid"}'

And I added this to the main configuration.yaml file

homeassistant:
  packages: !include_dir_merge_named vacuum/

i finally had time to add the 2nd robot, when i did the first one i had allready added the 2nd docker image to the pi’s ha install but just disabled it,

i gave my 2nd bot a static ip and adjusted the config for the 2nd docker image, no problem there

but the 2nd bot does not seem to get an entity like the first one does, first one shows up as sensor.vacuum…

how should the 2nd one show up? do i miss a special config/setup instruction?

edit: looked in github and used the search function here and cant find what extra i need to do to go from a single robot setup to 2 robot setup… ie what to copy from where to where and what to change

EDIT 2: i was comparing vacum.yaml and mop.yaml, i see they are mostly the same so was wondering if i should start by creating a new file based on vacum.yaml and replace rest980 with rest980_2 ?

EDIT 3: adding _2 to sensor.rest980 in vacuum.yaml was easy enough, but what about sensor.vacuum? and input_text.vacuum_action?

of course this a lux problem, having 2 robots are rare. in my case they will be on each floor and i will give them each their floor plan just to make it simple

so far, my secrets.yaml: #################################### Secrets################################ - Pastebin.com
and an attempt at a 2nd vacuum.yaml called vacuum2.yaml: #################################### iRobot Vacuum Package################## - Pastebin.com

another attempt at a seperate vacuum2.yaml: #################################### iRobot Vacuum2 Package################# - Pastebin.com

EDIT4: http://192.168.0.9:3002/api/local/info/state and http://192.168.0.9:3000/api/local/info/state do report different bot names so i assume most is correct, its just the yaml and php files

i’m lost on what to do here, sum up of what i have:

php-nginx Docker Image set to /config/vacuum2 and port 3001
php-nginx_2 Docker Image set to /config/vacuum and port 3003
rest980 Docker Image set to port 3000
rest980_2 Docker Image set to port 3002

the _2 are the ones i have my first bot on and works. as said above i can get both names of the bots so i expect the docker images to be ok, BUT for completeness sake how do i check ?

in \packages i have vacuum.yaml and vacuum2.yaml the contents of vacuum.yaml: #################################### iRobot Vacuum Package################## - Pastebin.com and the content of vacuum2.yaml: #################################### iRobot Vacuum2 Package################# - Pastebin.com

i copied the content of \vacuum to \vacuum2 and the only difference here are in image.php where i have changed $file_append and $ha_rest980 =

/vacuum: ---->>>>>> this is my first bot

$file_append = ‘Solveig’; # Allows differentiation of files for different floors or robots
$ha_rest980 = ‘http://192.168.0.9:8123/api/states/sensor.rest980’;

/vacuum2:

$file_append = ‘Robert’; # Allows differentiation of files for different floors or robots
$ha_rest980 = ‘http://192.168.0.9:8123/api/states/sensor.rest980_2’;

my secrets.yaml is still the same: #################################### Secrets################################ - Pastebin.com

but now my skills to figure out what should go where are up, i did even look at the mop example but got no wiser

hass-addons is a repo, but ha-rest980-roomba you need to manaully install using the steps.

i see you have this sorted.

my implementation of the rest980 sensors is not compatible with the native roomba integration. you need to run one or the other.

you need to follow all the steps outlined in the repo linked in the first post.

there is an issue with the upstream dorita980/rest980 image which is not providing the location data used to generate the map.

the error your seeing

Notice : Undefined offset: -1 in /config/vacuum/image.php on line 57

specifically is calling out that there is no position points in the log file

instead create a folder called packages in your config folder, move the vacuum.yaml file into this packages folder and replace with this

homeassistant:
  packages: !include_dir_named packages/ 

if you are getting the right info from the info/state api url - then your rest980 images should be good

im my exmaples, i didnt create a second folder, just use different image.php file names for the different robots/floors

do you have sensor.vacuum and sensor.vacuum2 in dev tools ?

1 Like

:rotating_light: :rotating_light: :rotating_light:

Please note - location data is currently broken in iRobot FW 3.20.x

This is being tracking in the dorita980 repo.

nope…

will try and see if fix’s things

what about /packages with vacuum.yaml ? do you just create a copy of the file like i attempted?

and just so i get it right, the 2nd image.php file which i just named image2.php shall i just change settings of these lines ?

$vacuum_log
$file_append
$ha_rest980

last one is a copy of the first one and i just added _2 to it so its now:

$ha_rest980 = ‘http://192.168.0.9:8123/api/states/sensor.rest980_2’;

the other 2 are:

$vacuum_log = ‘http://192.168.0.9:3001/vacuum.log’; # Could also be HTTPS
$file_append = ‘Robert’; # Allows differentiation of files for different floors or robots

but i guess the log file name needs to change?

in /packages:

vacuum.yaml: #################################### iRobot Vacuum Package################## - Pastebin.com
vacuum2.yaml: #################################### iRobot Vacuum2 Package################# - Pastebin.com

in /vacuum:

image.php: <?php//error_reporting(E_ALL);//ini_set('display_errors', 1);// ADJUST T - Pastebin.com
image2.php: <?php//error_reporting(E_ALL);//ini_set('display_errors', 1);// ADJUST T - Pastebin.com

secrets.yaml (in config foldder just outside the 2 other folders): #################################### Secrets################################ - Pastebin.com

do everything look ok? i still need to upload the floorplan for each bot and adjust offset and get room info

the dashboard… the vertical stack card: type: vertical-stackcards: - entity: sensor.vacuum2 type: custom:roomb - Pastebin.com

  - input_boolean.vacuum_clean_bedroom
  - input_boolean.vacuum_clean_office

should i replace all .vacuum with .vacuum2 ?

instead create a folder called packages in your config folder, move the vacuum.yaml file into this packages folder and replace with this

Thanks a lot, man, that was it! Now I see it.

1 Like

yes, so I have a vacuum.yaml and mop.yaml for each of the robots

yep, i realised that in my vaccum directory on the repo, i didnt include the mop.php files, but essrntially its a duplication of the image.php will the paramters changed (you will likely need to change the offsets also, etc - dependant on how the robot tracks the data - once we have it again in the firmware!)

yes, in my example, the vacuum.yaml automations write to vacuum.log and mop.yaml writes to mop.log (as listed in the secrets.yaml)

at a high level glance, it seems ok - are you seeing any errors ? other than the map not populating.

this list will need to reflect the rooms you have which are listed in the vacuum2.yaml file (and the names need to be unique from rooms in vacuum.yaml

also - make sure that the group.vacuum_rooms is duplicated and contains only the relevant rooms for each of the robots, as this is used to control the lock/unlock mechanism on the lovelace card

does this make sense?

yes, just me that are insecure on things, but if you upload (when time allows) the mop files i can compare to those and i hope the last bits of the puzzle in my head falls in place

nope no errors, and yes only the maps are missing as i need to generate them

I see that the built-in schedule somehow takes into account vacation. How does that work? How can I tell the system when my vacation is? Maybe I would like to change it to something like nobody’s at home - clean, otherwise don’t clean. And base it on the HA presence detection of people in HA. Would that be possible?

I have an input_boolean on my system which enables/disables vacation mode :wink: nothing fancy unfortunately

yes you can, I use the HA Companion mode app to detect presence of people and you can simply have an automation condition or trigger to execute when, for example, everyone is out

1 Like

Another problem with the map… Where am I supposed to see the map? On the lovelace dashboard provided by you via the lovelace.yaml file? I don’t see it there. But anyway if I open 192.168.89.241:3001/image.php manually, I see just an error message

No Coordinates found in file, is it reachable and populated? Log file - http://192.168.89.241:3001/vacuum.log?

If I download the log, it is empty (and even in the vacuum directory the file has 0 kB).

I suppose that the error message refers to the coordinates that we get in the step 5 and fill to the secrets.yaml file in the step 6. I did that. But I used the default (“global”) secrets.yaml file that I use also for other secerts in HA in general that is stored in /home/secrets.yaml. I simply copied the the first part of your secrets file (because I don’t have a mop) and placed it to my /home/secrets.yaml like this

# Vacuum
vacuum_state: http://192.168.89.241:3000/api/local/info/state
vacuum_action: http://192.168.89.241:3000/api/local/action/
vacuum_verify_ssl: false
vacuum_notify: notify.mobile_app_lukas_iphone # You can also use a notify group here
vacuum_map: http://192.168.89.241:3001/image.php
vacuum_log: /config/vacuum/vacuum.log
vacuum_dir: /config/vacuum
vacuum_kitchen: '{"region_id":"10","type":"rid"}'
vacuum_entry: '{"region_id":"9","type":"rid"}'
vacuum_hall: '{"region_id":"4","type":"rid"}'
vacuum_living_room: '{"region_id":"6","type":"rid"}'
vacuum_bedroom: '{"region_id":"12","type":"rid"}'
vacuum_bathroom: '{"region_id":"8","type":"rid"}'
vacuum_master_bedroom: '{"region_id":"11","type":"rid"}'

Should it be somewhere else? Or is the error message referring to something completely different?

Thank you for the help. After going through the configuration documentation I noticed that I had forgotten to update the secret.yaml since I had listed the IP address for the Roomba and not for Home Assistant. After updating the IP address I am now able to see the sensor.rest980 and sensor.vacuum and there’s information populating on the lovelace GUI.

I am running my first full clean now that the sensor is available. As of now the location sensor is not providing any information besides “n-a”, I’m unable to access http://IP-HA:rest980-Port/apli/local/action and there’s no log being created yet but I’ll wait and see if it populates once the full clean is completed.

I just got the same error. it looks like the rest URL → http://192.168.2.30:3001/api/local/info/state
returning the json object is missing 1 json attribute “pose” (underneath this is what gives us the coordinates)

I’ve had this happen in the past, restarting a clean cycle fixed it in the past, no luck today.
restarted HA no luck, then I even re-downloaded and restarted the docker image koalazak/rest980:latest no luck. viewed the URL http://192.168.2.30:3001/api/local/info/state
in firefox to format json, still not there.

this could be related to a software update that now doesn’t expose x,y,theta data, or we have to soft reboot the iroomba necessary after software update. I have to find which button combinations that was again to reboot it but not reset it. it does look like Roomba software was updated on 11/10/21 to version 3.20.7 in the iphone app.

in the URL the softwareVer json attribute has this as version “lewis+3.20.7+lewis-release-rt421+11”

edit

soft reboot of roomba didn’t work,
and it looks like this issue was mentioned earlier in this thread

probably roomba didn’t push the firmware to everybody at the same time.
we’ll have to wait for a fix if possible to be implemented in dorita980 that the rest980 is based on.

@Syrius

need some more help to get it all going

the 2 bot’s report fine on state… ie http://192.168.0.9:3002/api/local/info/state and http://192.168.0.9:3000/api/local/info/state

but when i click action i get “Not Found” ie on http://192.168.0.9:3000/api/local/action/ and http://192.168.0.9:3002/api/local/action/

when i visit the map link http://192.168.0.9:3003/image.php or http://192.168.0.9:3003/image2.php i get this error

Warning: Undefined variable $robot_log in /config/vacuum/image.php on line 56

Warning: file_get_contents(?v=1638291823): Failed to open stream: No such file or directory in /config/vacuum/image.php on line 56

Warning: Undefined variable $robot_log in /config/vacuum/image.php on line 62
No Coordinates found in file, is it reachable and populated? Log file - ?

there are 2 log files, one is empty and one only have a stuck line in it

Home Assistant notifications (Log started: 2021-11-30T15:48:50.182892+00:00)

Stuck

maintenance also works fine in the dashboard and the 2 bot’s do report different status and their statistics also match up

secrects.yaml: #################################### Secrets################################ - Pastebin.com
image.php: <?php//error_reporting(E_ALL);//ini_set('display_errors', 1);// ADJUST T - Pastebin.com
image2.php: <?php//error_reporting(E_ALL);//ini_set('display_errors', 1);// ADJUST T - Pastebin.com
vacuum.yaml: #################################### iRobot Vacuum Package################## - Pastebin.com
vacuum2.yaml: #################################### iRobot Vacuum2 Package################# - Pastebin.com

@Syrius

Greetings Syrius.

For a while, everything was fine and it worked reliably. Then came something that partially shattered integration. My Roomba i7 doesn’t show me the map right now.
I read this topic, where some have the same problem, but for me the solution did not help me to correct and display the map.
I have add-ons installed
php-nginx Docker Image (3001)
and
rest980 Docker Image (3000)
Roomba i7 does its job normally, Selective room cleaning works flawlessly.
He just doesn’t draw the map.
Sending localized files:
package
image.php
secrets
Could you please check why I don’t have a map?
Thank you very much.

image.php
secrets.yaml
vacuum.yaml

map is broken right now → ref

map is broken right now → ref

map is broken right now → ref

in lovelace? you need to add the robot_action attribute to the camera card to point at the right one

looks like your image.php file is inconsistent - line 6 is $vacuum_log and line 56 is looking for $robot_log

note - the map still isnt working lol