BenziAhamed / alfred-battery

An Alfred app workflow to display battery levels of your Apple devices
53 stars 8 forks source link

Couldn't working on Apple M1 chips. #11

Open syhily opened 3 years ago

syhily commented 3 years ago

This scripts cause error. echo "$INFO" | grep MaxCapacity | awk '{printf $3; exit}'

image

Here is a sample battery info from Apple M1

→ ioreg -l -n AppleSmartBattery -r
+-o AppleSmartBattery  <class AppleSmartBattery, id 0x100000710, registered, matched, active, busy 0 (1 ms), retain 8>
    {
      "PostChargeWaitSeconds" = 120
      "built-in" = Yes
      "AppleRawAdapterDetails" = ({"IsWireless"=No,"AdapterID"=0,"FamilyCode"=18446744073172697098,"UsbHvcHvcIndex"=0,"Watts"=15,"UsbHvcMenu"=({"Index"=0,"MaxC$
      "CurrentCapacity" = 100
      "PackReserve" = 133
      "DeviceName" = "bq20z451"
      "PostDischargeWaitSeconds" = 120
      "OCVData" = {}
      "LPEMData" = {}
      "CarrierMode" = {"CarrierModeLowVoltage"=3600,"CarrierModeHighVoltage"=4100,"CarrierModeStatus"=0}
      "TimeRemaining" = 0
      "ChargerConfiguration" = 2904
      "IOGeneralInterest" = "IOCommand is not serializable"
      "IOReportLegend" = ({"IOReportChannels"=((7167869599145487988,6460407809,"BatteryCycleCount")),"IOReportGroupName"="Battery","IOReportChannelInfo"={"IORe$
      "AtCriticalLevel" = No
      "BatteryCellDisconnectCount" = 0
      "UpdateTime" = 1613812945
      "Amperage" = 0
      "AppleRawCurrentCapacity" = 5103
      "AbsoluteCapacity" = 0
      "AvgTimeToFull" = 65535
      "ExternalConnected" = Yes
      "ExternalChargeCapable" = Yes
      "AppleRawBatteryVoltage" = 12874
      "BootVoltage" = 0
      "BatteryData" = {"Ra03"=0,"Ra10"=0,"CellWom"=(0,0),"RaTableRaw"=(<0000006e005f0073007d008e006b006e00730079007b0070008500c9015e0224>,<000000600051005d0066$
      "BatteryInstalled" = Yes
      "IOReportLegendPublic" = Yes
      "Serial" = "F5D05015H8RKGGJB4"
      "AppleRawExternalConnected" = Yes
      "KioskMode" = {"KioskModeLastHighSocHours"=0,"KioskModeFullChargeVoltage"=0,"KioskModeMode"=0,"KioskModeHighSocSeconds"=0,"KioskModeHighSocDays"=0}
      "NominalChargeCapacity" = 5103
      "FullyCharged" = Yes
      "FullPathUpdated" = 1613812645
      "ManufacturerData" = <00000000100200012400000003353339033031300341544c024d000000000000>
      "ChargingOverride" = 0
      "BatteryInvalidWakeSeconds" = 30
      "ChargerData" = {"ChargingCurrent"=0,"NotChargingReason"=4194305,"ChargerStatus"=45194,"ChargingVoltage"=4292,"VacVoltageLimit"=4350,"ChargerID"=18446744$
      "BootPathUpdated" = 1613301880
      "DesignCycleCount9C" = 1000
      "AdapterDetails" = {"IsWireless"=No,"AdapterID"=0,"FamilyCode"=18446744073172697098,"UsbHvcHvcIndex"=0,"Watts"=15,"UsbHvcMenu"=({"Index"=0,"MaxCurrent"=7$
      "MaxCapacity" = 100
      "InstantAmperage" = 0
      "GasGaugeFirmwareVersion" = 1
      "AdapterInfo" = 0
      "Location" = 0
      "Temperature" = 3052
      "AvgTimeToEmpty" = 65535
      "BestAdapterIndex" = 0
      "DesignCapacity" = 5103
      "IsCharging" = No
      "PermanentFailureStatus" = 0
      "Voltage" = 12874
      "UserVisiblePathUpdated" = 1613812945
      "CycleCount" = 16
      "AppleRawMaxCapacity" = 5158
      "VirtualTemperature" = 3200
    }

Change this line 8 to MAX_CAPACITY=$(echo "$INFO" | grep '"MaxCapacity" = ' | awk '{printf $3; exit}') fixed the issue.

BenziAhamed commented 3 years ago

Thank you for reporting, and sharing a fix.

It is getting to a point where subtle changes in ioreg's output across OS versions (and now, chip versions?) is making it hard to maintain a fully compatible version of this workflow. Scraping off of ioreg's output was not really a robust solution to start with IMO.

I will keep this issue open for (1) visibility and (2) to see I can come up with a better approach.

h-jia commented 3 years ago

Thanks sharing @syhily Since line 58 also occurs errors on M1. Also with the temperature:

image
zenopopovici commented 3 years ago

Here's the fix for x86 Big sur

#!/usr/bin/env bash

INFO=$(ioreg -l -n AppleSmartBattery -r)

# Charge and time remaining
CURRENT_CAPACITY=$(echo "$INFO" | grep CurrentCapacity | awk '{printf $3; exit}')
MAX_CAPACITY=$(echo "$INFO" | grep MaxCapacity | awk '{printf $3; exit}')
CHARGE=$((CURRENT_CAPACITY * 100 / MAX_CAPACITY))
CELLS=$(python -c "f='●'*($CHARGE/10) + '○'*(10-$CHARGE/10); print f")
STATUS_INFO=Draining...

CHARGING=$(echo "$INFO" | grep -i ischarging | awk '{printf("%s", $3)}')
TIME_TO_EMPTY=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%s", $3)}')
TIME_LEFT=Calculating…

if [ "$TIME_TO_EMPTY" -lt 15000 ]; then
    TIME_LEFT=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%i:%.2i", $3/60, $3%60)}')
fi

if [ "$CHARGING" == Yes ]; then
    TIME_FULL=$(echo "$INFO" | grep -i AvgTimeToFull | tr '\n' ' | ' | awk '{printf("%i:%.2i", $3/60, $3%60)}')
    TIME_INFO=$(echo "$TIME_FULL" until full)
    STATUS_INFO=Charging...
    BATT_ICON=charging.png
else
    FULLY_CHARGED=$(echo "$INFO" | grep -i FullyCharged | awk '{printf("%s", $3)}')
    EXTERNAL=$(echo "$INFO" | grep -i ExternalConnected | awk '{printf("%s", $3)}')
    if [ "$FULLY_CHARGED" == Yes ]; then
        if [ "$EXTERNAL" == Yes ]; then
            TIME_INFO="On AC power"
            STATUS_INFO="Fully Charged"
            BATT_ICON=power.png
        else
            TIME_INFO=$TIME_LEFT
            BATT_ICON=full.png
        fi
    else
        TIME_INFO=$TIME_LEFT
        BATT_ICON=critical.png
        if [ "$CHARGE" -gt 80 ]; then
            BATT_ICON=full.png
        elif [ "$CHARGE" -gt 50 ]; then
            BATT_ICON=medium.png
        elif [ "$CHARGE" -gt 10 ]; then
            BATT_ICON=low.png
        fi
    fi
fi

TRACKPAD_ICON=trackpad.png
# trackpad
TrackpadPercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Trackpad' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#TrackpadPercent} = 0 ]
then
    TrackpadTitle="Not connected"
else
    TrackpadSlug=$(python -c "f='●'*($TrackpadPercent/10) + '○'*(10-$TrackpadPercent/10);print f")
    TrackpadTitle="$TrackpadPercent% $TrackpadSlug"
fi

MOUSE_ICON=mouse.png
# mouse
MousePercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Mouse' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#MousePercent} = 0 ]
then
    MouseTitle="Not connected"
else
    MouseSlug=$(python -c "f='●'*($MousePercent/10) + '○'*(10-$MousePercent/10);print f")
    MouseTitle="$MousePercent% $MouseSlug"
fi

KEYBOARD_ICON=keyboard.png
# keyboard
KeyboardPercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Keyboard' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#KeyboardPercent} = 0 ]
then
    KeyboardTitle="Not connected"
else
    KeyboardSlug=$(python -c "f='●'*($KeyboardPercent/10) + '○'*(10-$KeyboardPercent/10);print f")
    KeyboardTitle="$KeyboardPercent% $KeyboardSlug"
fi

# Temperature
TEMPERATURE=$(echo "$INFO" | grep Temperature | tail -1 | awk '{printf ("%.1f", $3/10-273)}')

# Cycle count
CYCLE_COUNT=$(echo "$INFO" | grep -e '"CycleCount" =' | awk '{printf ("%i", $3)}')

# Battery health
DESIGN_CAPACITY=$(echo "$INFO" | grep DesignCapacity | tail -1 | awk '{printf $3; exit}')
HEALTH=$((MAX_CAPACITY * 100 / DESIGN_CAPACITY))

if [ "$HEALTH" -gt 100 ]; then
    HEALTH=100
fi

# Serial
SERIAL=$(echo "$INFO" | grep -o 'SerialString.*$' | tail -1 | awk '{printf $1}' | grep SerialString |  awk -F '\\"Watts' '{print $1""}' | tr -d "SerialString" | tr -d '"' | tr -d '=' | tr -d ',')

# Battery age
MANUFACTURE_DATE=$(echo "$INFO" | grep ManufactureDate | awk '{printf ("%i", $3)}')
day=$((MANUFACTURE_DATE&31))
month=$(((MANUFACTURE_DATE>>5)&15))
year=$((1980+(MANUFACTURE_DATE>>9)))
AGE=$(python -c "from datetime import date as D; d1=D.today(); d2=D($year, $month, $day); print ( (d1.year - d2.year)*12 + d1.month - d2.month )")

# Alfred feedback
cat << EOB
<?xml version="1.0"?>
<items>
  <item>
    <title>$MouseTitle</title>
    <subtitle>Magic Mouse 2</subtitle>
    <icon>icons/$MOUSE_ICON</icon>
  </item>
  <item>
    <title>$KeyboardTitle</title>
    <subtitle>Magic Keyboard 2</subtitle>
    <icon>icons/$KEYBOARD_ICON</icon>
  </item>
  <item>
    <title>$TrackpadTitle</title>
    <subtitle>Magic TrackPad 2</subtitle>
    <icon>icons/$TRACKPAD_ICON</icon>
  </item>
  <item>
    <title>$CHARGE% $CELLS</title>
    <subtitle>$STATUS_INFO</subtitle>
    <icon>icons/$BATT_ICON</icon>
  </item>
  <item>
    <title>$TIME_INFO</title>
    <subtitle>Time Left</subtitle>
    <icon>icons/clock.png</icon>
  </item>
  <item>
    <title>$TEMPERATURE °C</title>
    <subtitle>Temperature</subtitle>
    <icon>icons/temp.png</icon>
  </item>
  <item>
    <title>$CYCLE_COUNT</title>
    <subtitle>Charge Cycles Completed</subtitle>
    <icon>icons/cycles.png</icon>
  </item>
  <item>
    <title>$HEALTH%</title>
    <subtitle>Health</subtitle>
    <icon>icons/health.png</icon>
  </item>
  <item>
    <title>$SERIAL</title>
    <subtitle>Serial</subtitle>
    <icon>icons/serial.png</icon>
  </item>
  <item>
    <title>$AGE months</title>
    <subtitle>Age</subtitle>
    <icon>icons/age.png</icon>
  </item>
</items>
EOB
zenopopovici commented 2 years ago

Here is the configuration for M1. Battery age is impossible to calculate, it seems they don't follow the standard anymore.

#!/usr/bin/env bash

INFO=$(ioreg -l -n AppleSmartBattery -r)

# Charge and time remaining
CURRENT_CAPACITY=$(echo "$INFO" | grep AppleRawCurrentCapacity | awk '{printf $3; exit}')
MAX_CAPACITY=$(echo "$INFO" | grep \"AppleRawMaxCapacity\" | tail -1 | awk '{printf $3; exit}')
CHARGE=$((CURRENT_CAPACITY * 100 / MAX_CAPACITY))
CELLS=$(python -c "f='●'*($CHARGE/10) + '○'*(10-$CHARGE/10); print f")
STATUS_INFO=Draining...

CHARGING=$(echo "$INFO" | grep -i ischarging | awk '{printf("%s", $3)}')
TIME_TO_EMPTY=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%s", $3)}')
TIME_LEFT=Calculating…

if [ "$TIME_TO_EMPTY" -lt 65536 ]; then
    TIME_LEFT=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%i:%.2i", $3/60/60, $3%60)}')
fi

if [ "$CHARGING" == Yes ]; then
    TIME_FULL=$(echo "$INFO" | grep -i AvgTimeToFull | tr '\n' ' | ' | awk '{printf("%i:%.2i", $3/60, $3%60)}')
    TIME_INFO=$(echo "$TIME_FULL" until full)
    STATUS_INFO=Charging...
    BATT_ICON=charging.png
else
    FULLY_CHARGED=$(echo "$INFO" | grep -i FullyCharged | awk '{printf("%s", $3)}')
    EXTERNAL=$(echo "$INFO" | grep -i ExternalConnected | awk '{printf("%s", $3)}')
    if [ "$FULLY_CHARGED" == Yes ]; then
        if [ "$EXTERNAL" == Yes ]; then
            TIME_INFO="On AC power"
            STATUS_INFO="Fully Charged"
            BATT_ICON=power.png
        else
            TIME_INFO=$TIME_LEFT
            BATT_ICON=full.png
        fi
    else
        TIME_INFO=$TIME_LEFT
        BATT_ICON=critical.png
        if [ "$CHARGE" -gt 80 ]; then
            BATT_ICON=full.png
        elif [ "$CHARGE" -gt 50 ]; then
            BATT_ICON=medium.png
        elif [ "$CHARGE" -gt 10 ]; then
            BATT_ICON=low.png
        fi
    fi
fi

TRACKPAD_ICON=trackpad.png
# trackpad
TrackpadPercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Trackpad' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#TrackpadPercent} = 0 ]
then
    TrackpadTitle="Not connected"
else
    TrackpadSlug=$(python -c "f='●'*($TrackpadPercent/10) + '○'*(10-$TrackpadPercent/10);print f")
    TrackpadTitle="$TrackpadPercent% $TrackpadSlug"
fi

MOUSE_ICON=mouse.png
# mouse
MousePercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Mouse' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#MousePercent} = 0 ]
then
    MouseTitle="Not connected"
else
    MouseSlug=$(python -c "f='●'*($MousePercent/10) + '○'*(10-$MousePercent/10);print f")
    MouseTitle="$MousePercent% $MouseSlug"
fi

KEYBOARD_ICON=keyboard.png
# keyboard
KeyboardPercent=`ioreg -c AppleDeviceManagementHIDEventService | grep 'Magic Keyboard' -A8 | grep BatteryPercent | sed 's/[a-z,A-Z, ,|,\",=]//g' | tail -1 | awk '{print $1}'`
if [ ${#KeyboardPercent} = 0 ]
then
    KeyboardTitle="Not connected"
else
    KeyboardSlug=$(python -c "f='●'*($KeyboardPercent/10) + '○'*(10-$KeyboardPercent/10);print f")
    KeyboardTitle="$KeyboardPercent% $KeyboardSlug"
fi

# Temperature
TEMPERATURE=$(echo "$INFO" | grep Temperature | tail -1 | awk '{printf ("%.1f", $3/10-273)}')

# Cycle count
CYCLE_COUNT=$(echo "$INFO" | grep -e '"CycleCount" =' | awk '{printf ("%i", $3)}')

# Battery health
HEALTH=$((CURRENT_CAPACITY * 100 / MAX_CAPACITY))

if [ "$HEALTH" -gt 100 ]; then
    HEALTH=100
fi

# Serial
SERIAL=$(echo "$INFO" | grep -o 'SerialString.*$' | tail -1 | awk '{printf $1}' | grep SerialString |  awk -F '\\"Watts' '{print $1""}' | tr -d "SerialString" | tr -d '"' | tr -d '=' | tr -d ',')

# Battery age
# MANUFACTURE_DATE=$(echo "$INFO" | grep -o 'ManufactureDate.*$' | awk -F '\\"ISS' '{print $1""}' | tr -d "ManufactureDate" | tr -d '"' | tr -d '=' | tr -d ',')
# day=$((MANUFACTURE_DATE&31))
# month=$(((MANUFACTURE_DATE>>5)&15))
# year=$((1980+(MANUFACTURE_DATE>>9)))
# AGE=$(python -c "from datetime import date as D; d1=D.today(); d2=D($year, $month, $day); print ( (d1.year - d2.year)*12 + d1.month - d2.month )")

# Alfred feedback
cat << EOB
<?xml version="1.0"?>
<items>
  <item>
    <title>$MouseTitle</title>
    <subtitle>Magic Mouse 3</subtitle>
    <icon>icons/$MOUSE_ICON</icon>
  </item>
  <item>
    <title>$KeyboardTitle</title>
    <subtitle>Magic Keyboard 3</subtitle>
    <icon>icons/$KEYBOARD_ICON</icon>
  </item>$
  <item>
    <title>$TrackpadTitle</title>
    <subtitle>Magic TrackPad 3</subtitle>
    <icon>icons/$TRACKPAD_ICON</icon>
  </item>
  <item>
    <title>$CHARGE% $CELLS</title>
    <subtitle>$STATUS_INFO</subtitle>
    <icon>icons/$BATT_ICON</icon>
  </item>
  <item>
    <title>$TIME_INFO</title>
    <subtitle>Time Left</subtitle>
    <icon>icons/clock.png</icon>
  </item>
  <item>
    <title>$TEMPERATURE °C</title>
    <subtitle>Temperature</subtitle>
    <icon>icons/temp.png</icon>
  </item>
  <item>
    <title>$CYCLE_COUNT</title>
    <subtitle>Charge Cycles Completed</subtitle>
    <icon>icons/cycles.png</icon>
  </item>
  <item>
    <title>$HEALTH%</title>
    <subtitle>Health</subtitle>
    <icon>icons/health.png</icon>
  </item>
  <item>
    <title>$SERIAL</title>
    <subtitle>Serial</subtitle>
    <icon>icons/serial.png</icon>
  </item>
</items>
EOB

# <item>
#     <title>$AGE months</title>
#   <subtitle>Age</subtitle>
#   <icon>icons/age.png</icon>
#   </item>
adwaraki commented 2 years ago

I have fixed the issues for Apple Silicon and x86. Hopefully this fixes it for some time in the future. I don't have an x86 MBP any more to test this. There is a pull request pending and I don't know when the author will look into it, but if you are interested, you can look at the PR repo and copy the shell script to get it working again.

Screenshot

benknoble commented 1 year ago

A version on BigSur with fixes for DesignCapacity and Serial format changes:

#!/usr/bin/env bash

INFO=$(ioreg -l -n AppleSmartBattery -r)

# Charge and time remaining
CURRENT_CAPACITY=$(echo "$INFO" | grep CurrentCapacity | awk '{printf $3; exit}')
MAX_CAPACITY=$(echo "$INFO" | grep MaxCapacity | awk '{printf $3; exit}')
CHARGE=$((CURRENT_CAPACITY * 100 / MAX_CAPACITY))
CELLS=$(python -c "f='●'*($CHARGE/10) + '○'*(10-$CHARGE/10); print f")
STATUS_INFO=Draining...

CHARGING=$(echo "$INFO" | grep -i ischarging | awk '{printf("%s", $3)}')
TIME_TO_EMPTY=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%s", $3)}')
TIME_LEFT=Calculating…

if [ "$TIME_TO_EMPTY" -lt 15000 ]; then
    TIME_LEFT=$(echo "$INFO" | grep -i AvgTimeToEmpty | awk '{printf("%i:%.2i", $3/60, $3%60)}')
fi

if [ "$CHARGING" == Yes ]; then
    TIME_FULL=$(echo "$INFO" | grep -i AvgTimeToFull | tr '\n' ' | ' | awk '{printf("%i:%.2i", $3/60, $3%60)}')
    TIME_INFO="$TIME_FULL until full"
    STATUS_INFO=Charging...
    BATT_ICON=charging.png
else
    FULLY_CHARGED=$(echo "$INFO" | grep -i FullyCharged | awk '{printf("%s", $3)}')
    EXTERNAL=$(echo "$INFO" | grep -i ExternalConnected | awk '{printf("%s", $3)}')
    if [ "$FULLY_CHARGED" == Yes ]; then
        if [ "$EXTERNAL" == Yes ]; then
            TIME_INFO="On AC power"
            STATUS_INFO="Fully Charged"
            BATT_ICON=power.png
        else
            TIME_INFO=$TIME_LEFT
            BATT_ICON=full.png
        fi
    else
        TIME_INFO=$TIME_LEFT
        BATT_ICON=critical.png
        if [ "$CHARGE" -gt 80 ]; then
            BATT_ICON=full.png
        elif [ "$CHARGE" -gt 50 ]; then
            BATT_ICON=medium.png
        elif [ "$CHARGE" -gt 10 ]; then
            BATT_ICON=low.png
        fi
    fi
fi

# Temperature
TEMPERATURE=$(echo "$INFO" | grep Temperature | awk '{printf ("%.1f", $3/10-273)}')

# Cycle count
CYCLE_COUNT=$(echo "$INFO" | grep -e '"CycleCount" =' | awk '{printf ("%i", $3)}')

# Battery health
DESIGN_CAPACITY=$(echo "$INFO" | grep '"DesignCapacity" =' | awk '{printf $3; exit}')
HEALTH=$((MAX_CAPACITY * 100 / DESIGN_CAPACITY))

if [ "$HEALTH" -gt 100 ]; then
    HEALTH=100
fi

# Serial
SERIAL=$(echo "$INFO" | grep '"Serial" = ' | awk '{printf ("%s", $3)}' | tr -d '"')

# Battery age
MANUFACTURE_DATE=$(echo "$INFO" | grep ManufactureDate | awk '{printf ("%i", $3)}')
day=$((MANUFACTURE_DATE&31))
month=$(((MANUFACTURE_DATE>>5)&15))
year=$((1980+(MANUFACTURE_DATE>>9)))
AGE=$(python -c "from datetime import date as D; d1=D.today(); d2=D($year, $month, $day); print ( (d1.year - d2.year)*12 + d1.month - d2.month )")

# Alfred feedback
cat << EOB
<?xml version="1.0"?>
<items>
  <item>
    <title>$CHARGE% $CELLS</title>
    <subtitle>$STATUS_INFO</subtitle>
    <icon>icons/$BATT_ICON</icon>
  </item>
  <item>
    <title>$TIME_INFO</title>
    <subtitle>Time Left</subtitle>
    <icon>icons/clock.png</icon>
  </item>
  <item>
    <title>$TEMPERATURE °C</title>
    <subtitle>Temperature</subtitle>
    <icon>icons/temp.png</icon>
  </item>
  <item>
    <title>$CYCLE_COUNT</title>
    <subtitle>Charge Cycles Completed</subtitle>
    <icon>icons/cycles.png</icon>
  </item>
  <item>
    <title>$HEALTH%</title>
    <subtitle>Health</subtitle>
    <icon>icons/health.png</icon>
  </item>
  <item>
    <title>$SERIAL</title>
    <subtitle>Serial</subtitle>
    <icon>icons/serial.png</icon>
  </item>
  <item>
    <title>$AGE months</title>
    <subtitle>Age</subtitle>
    <icon>icons/age.png</icon>
  </item>
</items>
EOB