I setup my Landroid via MQTT before this stuff really picked up, so I’ve done a lot of manual work (template sensors) that’s eclipsed by these projects. I’ll share it here anyway, in case it’s useful to anyone else. I used https://github.com/orangutanoide/landroid_mosquitto_bridge to get the MQTT setup data, and bridged it to my own mosquitto instance.
mosquitto topic config (see elsewhere for full bridge info, my MAC address redacted):
# topic to subscribe in remote (aws) server
# move under 'landroid/' namespace locally for easier ACLs
topic PRM100/XXXXXXXXXXXX/commandOut in 0 landroid/ ""
topic PRM100/XXXXXXXXXXXX/commandIn out 0 landroid/ ""
sensors (MAC address redacted here, too):
## This is the base sensor that provides all the data from MQTT; everything
## else templates off of this one via the 'cfg' and 'dat' attributes.
## Timestamp is used as the base value to force this sensor to update
## whenever a new message is published, triggering all downstream templates.
## state_topic uses the landroid/ prefix as configured in the mosquitto bridge.
- platform: mqtt
name: Landroid
state_topic: landroid/PRM100/XXXXXXXXXXXX/commandOut
value_template: >-
{{ strptime(value_json["cfg"]["dt"] + " " + value_json["cfg"]["tm"], "%d/%m/%Y %H:%M:%S") }}
json_attributes_topic: landroid/PRM100/XXXXXXXXXXXX/commandOut
json_attributes_template: "{{ value_json | tojson }}"
- platform: template
sensors:
landroid_status:
friendly_name: Landroid status
icon_template: mdi:robot-mower-outline
value_template: >-
{% set mapper = {
0: "Idle",
1: "Home",
2: "Start sequence",
3: "Leaving home",
4: "Follow wire",
5: "Searching home",
6: "Searching wire",
7: "Mowing",
8: "Lifted",
9: "Trapped",
10: "Blade blocked",
11: "Debug",
12: "Remote control",
30: "Going home",
31: "Zone training",
32: "Border Cut",
33: "Searching zone",
34: "Pause" } %}
{% set state = state_attr("sensor.landroid", "dat").ls %}
{{ mapper[state] if state in mapper else 'Unknown: ' ~ state }}
attribute_templates:
statno: "{{ state_attr('sensor.landroid', 'dat').ls | int }}"
locked: "{{ state_attr('sensor.landroid', 'dat').lk | int }}"
fw_ver: "{{ state_attr('sensor.landroid', 'dat').fw }}"
mac: "{{ state_attr('sensor.landroid', 'dat').mac }}"
sn: "{{ state_attr('sensor.landroid', 'cfg').sn }}"
pitch: "{{ state_attr('sensor.landroid', 'dat').dmp[0] }}"
roll: "{{ state_attr('sensor.landroid', 'dat').dmp[1] }}"
yaw: "{{ state_attr('sensor.landroid', 'dat').dmp[2] }}"
updated: "{{ states('sensor.landroid') }}"
landroid_error:
friendly_name: Landroid error
value_template: >-
{% set mapper = {
0: "No error",
1: "Trapped",
2: "Lifted",
3: "Wire missing",
4: "Outside wire",
5: "Rain Delay",
6: "Close door to mow",
7: "Close door to go home",
8: "Blade motor blocked",
9: "Wheel motor blocked",
10: "Trapped timeout",
11: "Upside down",
12: "Battery low",
13: "Reverse wire",
14: "Charge error",
15: "Timeout finding home",
16: "Mower locked",
17: "Battery temperature too high/low" } %}
{% set state = state_attr("sensor.landroid", "dat").le %}
{{ mapper[state] if state in mapper else 'Unknown: ' ~ state }}
attribute_templates:
errno: "{{ state_attr('sensor.landroid', 'dat').le | int }}"
landroid_battery_charge:
friendly_name: Landroid charge
unit_of_measurement: '%'
value_template: "{{ state_attr('sensor.landroid', 'dat').bt.p | int }}"
device_class: battery
landroid_battery_voltage:
friendly_name: Landroid battery voltage
unit_of_measurement: 'V'
value_template: "{{ state_attr('sensor.landroid', 'dat').bt.v | float(2) }}"
device_class: voltage
landroid_battery_temperature:
friendly_name: Landroid battery temperature
unit_of_measurement: '°C'
value_template: "{{ state_attr('sensor.landroid', 'dat').bt.t | float(0) }}"
device_class: temperature
landroid_battery_cycle:
friendly_name: Landroid battery cycle
value_template: "{{ state_attr('sensor.landroid', 'dat').bt.nr | int }}"
unit_of_measurement: 'cycles'
landroid_total_distance:
friendly_name: Landroid total distance
value_template: "{{ state_attr('sensor.landroid', 'dat').st.d }}"
unit_of_measurement: 'm'
landroid_total_work_time:
friendly_name: Landroid total work time
value_template: "{{ state_attr('sensor.landroid', 'dat').st.wt }}"
unit_of_measurement: 'min'
landroid_total_blade_time:
friendly_name: Landroid total blade time
value_template: "{{ state_attr('sensor.landroid', 'dat').st.b }}"
unit_of_measurement: 'min'
landroid_wifi_rsi:
friendly_name: Landroid WiFi RSI
unit_of_measurement: 'dBm'
value_template: "{{ state_attr('sensor.landroid', 'dat').rsi | int }}"
device_class: signal_strength
landroid_rain_delay:
friendly_name: Landroid Rain Delay
unit_of_measurement: 'min'
value_template: "{{ state_attr('sensor.landroid', 'cfg').rd | int }}"
landroid_rain_delay_countdown:
friendly_name: Landroid Rain Countdown
unit_of_measurement: 'min'
value_template: "{{ state_attr('sensor.landroid', 'dat').rain.cnt | int }}"
friendly_name: Landroid Rain estimated resume
device_class: timestamp
value_template: >-
{% if states('sensor.landroid_rain_delay_countdown')|int > 0 %}
{{ ( (as_timestamp(states('sensor.landroid'))/60)|int * 60 +
( state_attr('sensor.landroid', 'dat').rain.cnt ) * 60
) | timestamp_local }}
{% else %}
{{ states.sensor.landroid_rain_delay_countdown.last_changed }}
{% endif %}
landroid_zone_index:
friendly_name: Landroid zone index
value_template: "{{ state_attr('sensor.landroid', 'dat').lz|int }}"
landroid_current_zone:
friendly_name: Landroid current zone
value_template: >-
{% set mzv = state_attr('sensor.landroid', 'cfg').mzv %}
{% set lz = states('sensor.landroid_zone_index')|int %}
{{ mzv[lz]+1 }}
landroid_next_zone:
friendly_name: Landroid next zone
value_template: >-
{% set mzv = state_attr('sensor.landroid', 'cfg').mzv %}
{% set mzv_mod = (mzv or [0]) | length %}
{% set lz = (states('sensor.landroid_zone_index')|int + 1) % mzv_mod %}
{{ mzv[lz]+1 }}
landroid_pitch:
friendly_name: Landroid pitch
value_template: "{{ state_attr('sensor.landroid', 'dat').dmp[0] | float(1) }}"
unit_of_measurement: '°'
landroid_roll:
friendly_name: Landroid roll
value_template: "{{ state_attr('sensor.landroid', 'dat').dmp[1] | float(1)}}"
unit_of_measurement: '°'
landroid_yaw:
friendly_name: Landroid yaw
value_template: >-
{% set yaw = state_attr('sensor.landroid', 'dat').dmp[2] %}
{% if yaw > 180 %}{{ (yaw - 360)|round(1) }}{% else %}{{ yaw|round(1) }}{% endif %}
unit_of_measurement: '°'
landroid_next_start:
friendly_name: Landroid next cycle start
device_class: timestamp
value_template: >-
{%- set ns = namespace(done=false) %}
{%- set earliest_start = [as_timestamp(now()), as_timestamp(states('sensor.landroid_rain_delay_resume'))] | max %}
{%- for day_offset in range(0, 7) %}
{%- set day = now().isoweekday()+day_offset %}
{%- for sched in [state_attr('sensor.landroid', 'cfg').sc.d, state_attr('sensor.landroid', 'cfg').sc.dd] %}
{%- if sched %}
{%- set numdays = 7 %}
{%- for pass in range(0, (sched|list|length/7)|int) %}
{%- set current = sched[day % numdays + pass * 7] %}
{%- set date = ((as_timestamp(now())+86400*day_offset)|timestamp_local).split()[0] %}
{%- set start = strptime(date ~ " " ~ current[0], "%Y-%m-%d %H:%M") %}
{%- set end = (as_timestamp(start)+current[1]*60)|timestamp_local %}
{%- if not ns.done and current[1] != 0 %}
{%- set cmp_time = end %}
{%- if as_timestamp(cmp_time) > earliest_start %}
{%- set ns.done = true %}
{{ start }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}
landroid_next_end:
friendly_name: Landroid next cycle end
device_class: timestamp
value_template: >-
{%- set ns = namespace(done=false) %}
{%- set earliest_start = [as_timestamp(now()), as_timestamp(states('sensor.landroid_rain_delay_resume'))] | max %}
{%- for day_offset in range(0, 7) %}
{%- set day = now().isoweekday()+day_offset %}
{%- for sched in [state_attr('sensor.landroid', 'cfg').sc.d, state_attr('sensor.landroid', 'cfg').sc.dd] %}
{%- if sched %}
{%- set numdays = 7 %}
{%- for pass in range(0, (sched|list|length/7)|int) %}
{%- set current = sched[day % numdays + pass * 7] %}
{%- set date = ((as_timestamp(now())+86400*day_offset)|timestamp_local).split()[0] %}
{%- set start = strptime(date ~ " " ~ current[0], "%Y-%m-%d %H:%M") %}
{%- set end = (as_timestamp(start)+current[1]*60)|timestamp_local %}
{%- if not ns.done and current[1] != 0 %}
{%- set cmp_time = end %}
{%- if as_timestamp(cmp_time) > earliest_start %}
{%- set ns.done = true %}
{{ end }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}
landroid_next_edge:
friendly_name: Landroid next border cut
device_class: timestamp
value_template: >-
{%- set ns = namespace(done=false) %}
{%- set earliest_start = [as_timestamp(now()), as_timestamp(states('sensor.landroid_rain_delay_resume'))] | max %}
{%- for day_offset in range(0, 7) %}
{%- set day = now().isoweekday()+day_offset %}
{%- for sched in [state_attr('sensor.landroid', 'cfg').sc.d, state_attr('sensor.landroid', 'cfg').sc.dd] %}
{%- if sched %}
{%- set numdays = 7 %}
{%- for pass in range(0, (sched|list|length/7)|int) %}
{%- set current = sched[day % numdays + pass * 7] %}
{%- set date = ((as_timestamp(now())+86400*day_offset)|timestamp_local).split()[0] %}
{%- set start = strptime(date ~ " " ~ current[0], "%Y-%m-%d %H:%M") %}
{%- set end = (as_timestamp(start)+current[1]*60)|timestamp_local %}
{%- if not ns.done and current[2] > 0 %}
{%- set cmp_time = end %}
{%- if as_timestamp(cmp_time) > earliest_start %}
{%- set ns.done = true %}
{{ start }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- endif %}
{%- endfor %}
{%- endfor %}
binary_sensors:
- platform: template
sensors:
landroid_battery_charging:
friendly_name: Landroid battery charging
value_template: "{{ state_attr('sensor.landroid', 'dat').bt.c | int == 1 }}"
device_class: battery_charging
landroid_rain_sensed:
friendly_name: Landroid Rain sensor
value_template: "{{ state_attr('sensor.landroid', 'dat').rain.s | int > 0 }}"
device_class: moisture
edit: battery charging (dat.bt.c
) is 0 for not charging, 1 for charging, and 2 for not charging because battery temperature is out of range, so only check against 1.