PlantLab Blog

Build a Scheduled Plant Health Monitor with Home Assistant

This article includes historical API examples. Read the current API documentation for supported plants and fields.

What You’ll Build

A camera watches your plants on a schedule. Each photo goes to PlantLab for a supported-plant diagnosis, and Home Assistant turns the answer into sensors you can automate against. Something looks off, your phone buzzes with the diagnosis and the photo. Everything’s fine, it stays quiet.

Setup runs about 20 minutes. The cost is a camera you probably already own plus PlantLab’s free tier - three diagnoses a day, no card. No soldering, no standalone Python scripts. A Home Assistant integration and some YAML.

A healthy flowering cannabis plant next to a Home Assistant dashboard card showing PlantLab health, growth stage, and reliability sensors

Your eyes are good. They’re also asleep at 6 AM, and they’ve never once compared today’s leaf color against yesterday’s with any real consistency. A camera bolted to a tent pole doesn’t get distracted and has never talked itself into “it’s probably fine” at 11 PM. That’s the whole pitch.

If you run Node-RED instead of native HA automations, there’s a companion walkthrough for that. This one stays inside Home Assistant.


Prerequisites

Camera positioning: shoot the canopy from above or a slight angle. Overhead gives the best coverage. Avoid shooting through blurple LEDs - the model needs real leaf color, not everything tinted purple. If your lights are blurple, run the check during a lights-off window or use the camera’s white LED.


Step 1: Install the PlantLab Integration

  1. Open HACS in the sidebar
  2. Search PlantLab in Integrations
  3. Click Download
  4. Restart Home Assistant

Manual install

# SSH into your HA machine
cd /config/custom_components/
git clone https://github.com/plantlab-ai/home-assistant-plantlab.git plantlab_temp
mv plantlab_temp/custom_components/plantlab .
rm -rf plantlab_temp

Restart HA after copying.

Configure

  1. Settings > Devices & Services
  2. + Add Integration
  3. Search PlantLab
  4. Paste your API key
  5. Done - you get a PlantLab device with a set of sensors.

No YAML for the integration itself. The config flow handles it.


Step 2: Meet Your New Sensors

After setup you get these entities automatically:

EntityWhat it showsExample state
sensor.plantlab_healthOverall verdictunhealthy
sensor.plantlab_conditionsTop conditionNitrogen Deficiency
sensor.plantlab_pestsTop pestSpider Mites
sensor.plantlab_growth_stageCurrent stagevegetative
sensor.plantlab_nutrient_analysisMulder’s Chart hypothesispotassium_excess
sensor.plantlab_reliability_scoreHow much to trust this call (0-100%)82.0
sensor.plantlab_plant_countPlants detected in the frame1
binary_sensor.plantlab_problemSimple on/offon when something’s wrong

One thing worth flagging up front: the condition and pest sensors report the display name - Nitrogen Deficiency, not nitrogen_deficiency. That matters when you trigger automations on their state. The raw snake_case class_id lives in the sensor’s attributes and in the service response, which is what you’ll actually key automations off of below.

Everything reads “Unknown” until the first diagnosis runs. After a check:


Step 3: The Daily Health Check

Runs once a day, grabs a photo, sends it to PlantLab, and pings you only if there’s a problem. Quiet when fine, loud when not.

automation:
  - alias: "Plant Health Check - Morning"
    trigger:
      - platform: time
        at: "08:00:00"
    action:
      # Capture a snapshot
      - action: camera.snapshot
        target:
          entity_id: camera.grow_tent    # your camera entity
        data:
          filename: /config/www/plant_check.jpg

      # Give the file a moment to write
      - delay:
          seconds: 3

      # Run the diagnosis
      - action: plantlab.diagnose
        data:
          image_path: /config/www/plant_check.jpg
        response_variable: result

      # Alert only if the first plant comes back unhealthy
      - if: >
          {{ result.results | count > 0
             and result.results[0].is_healthy == false }}
        then:
          - action: notify.mobile_app_your_phone
            data:
              title: "Plant Issue Detected"
              message: >
                {{ result.results[0].conditions[0].display_name }}
                detected ({{ (result.results[0].conditions[0].confidence * 100) | round }}% confidence).
                Growth stage: {{ result.results[0].growth_stage }}.
              data:
                image: /local/plant_check.jpg

One structural thing to know: PlantLab returns one result per detected plant under results. result.results[0] is the first plant, which is why the health check, the condition, and the confidence all read off results[0] and not the top level. More on multi-plant in a moment.

Why morning? You catch overnight problems before the lights-on period drives symptoms further, and the first hour of the light cycle gives a neutral photo without heat-droop.

Skip the file - snapshot from the camera directly

Don’t want files on disk? Point the service at the camera entity:

      - action: plantlab.diagnose
        data:
          entity_id: camera.grow_tent
        response_variable: result

Simpler, but you lose the photo to attach to the notification. Your call.


Step 4: Add It to Your Dashboard

A basic card for last-check results:

type: vertical-stack
cards:
  - type: picture-entity
    entity: camera.grow_tent
    name: Grow Tent
    show_state: false

  - type: entities
    title: Plant Health
    entities:
      - entity: sensor.plantlab_health
        name: Status
      - entity: sensor.plantlab_conditions
        name: Condition
      - entity: sensor.plantlab_pests
        name: Pests
      - entity: sensor.plantlab_growth_stage
        name: Growth Stage
      - entity: sensor.plantlab_reliability_score
        name: Reliability
      - entity: binary_sensor.plantlab_problem
        name: Problem?

Nothing fancy, but it’s one screen. Make it prettier if you like - I’m an engineer, not a designer.


Step 5: The Fun Part

Gate on confidence and reliability

Every diagnosis carries a per-condition confidence and a plant-level reliability_score - a 0-1 trust signal for the whole call. You don’t want a push for every marginal detection. Extend the if from Step 3:

      - if: >
          {{ result.results | count > 0
             and result.results[0].is_healthy == false
             and result.results[0].conditions[0].confidence > 0.75
             and result.results[0].reliability_score > 0.7 }}
        then:
          - action: notify.mobile_app_your_phone
            data:
              title: "Confirmed Issue"
              message: >
                {{ result.results[0].conditions[0].display_name }}
                at {{ (result.results[0].conditions[0].confidence * 100) | round }}% confidence.

Alert with full context on a specific condition

Read the class_id straight off the response - it’s the stable snake_case identifier, unlike the display-name sensor state - and put the full picture in the notification so you can decide what to do:

      # inside the same daily-check automation, after the diagnose call
      - if: >
          {{ result.results | count > 0
             and result.results[0].conditions[0].class_id == 'calcium_deficiency'
             and result.results[0].conditions[0].confidence > 0.8 }}
        then:
          - action: notify.mobile_app_your_phone
            data:
              title: "Calcium deficiency flagged"
              message: >
                {{ result.results[0].conditions[0].display_name }} at
                {{ (result.results[0].conditions[0].confidence * 100) | round }}% confidence,
                severity {{ result.results[0].conditions[0].severity }}.
                Check the plant and decide the response.

The confidence floor is yours to set. Some conditions are worth a second check before you act on them - the alert carries everything you need to make that call yourself.

Handle more than one plant

If your camera sees the whole tent, results has an entry per plant and sensor.plantlab_plant_count tells you how many. Loop instead of assuming results[0]:

      - repeat:
          for_each: "{{ result.results }}"
          sequence:
            - if: "{{ repeat.item.is_healthy == false }}"
              then:
                - action: notify.mobile_app_your_phone
                  data:
                    title: "Plant Issue Detected"
                    message: >
                      {{ repeat.item.conditions[0].display_name }}
                      ({{ (repeat.item.conditions[0].confidence * 100) | round }}%).

The per-plant sensors always track the first plant, so the loop is how you cover a multi-plant frame. If you’d rather diagnose each plant cleanly, give each its own camera and its own automation.

Ramp up monitoring when something’s wrong

automation:
  - alias: "Increase Checks When Unhealthy"
    trigger:
      - platform: state
        entity_id: binary_sensor.plantlab_problem
        to: "on"
    action:
      - action: automation.turn_on
        target:
          entity_id: automation.plant_health_check_afternoon

Create a second check (afternoon, maybe a different angle) that’s normally disabled and only wakes up when a problem is flagged. More eyes when it matters, silence otherwise.


Troubleshooting

ProblemLikely causeFix
is_cannabis: false, plant count 0Camera angle, blurple lights, or a lens capAdjust position, use white light or flash, check the camera feed
No notificationTemplate not matching the new results[] shapeTest in Developer Tools > Template with a real result first
401 UnauthorizedInvalid API keyRe-enter in Settings > Devices & Services > PlantLab > Configure
Sensors stuck “Unknown”No diagnosis run yetCall plantlab.diagnose manually in Developer Tools > Actions
Rate limit (429)More than 3 checks/day on free tierSpace out your requests

What the API Actually Returns

Here’s the full response - everything inside result in your automations. Note the top-level is_cannabis (image-wide) versus the per-plant fields nested in results:

schema_version: "3.0.0"
success: true
is_cannabis: true
cannabis_confidence: 0.97
results:
  - bbox: { x0: 0, y0: 0, x1: 1, y1: 1, normalized: true }
    is_healthy: false
    health_confidence: 0.87
    growth_stage: vegetative
    growth_stage_confidence: 0.89
    conditions:
      - class_id: nitrogen_deficiency
        display_name: Nitrogen Deficiency
        confidence: 0.85
    pests:
      - class_id: spider_mites
        display_name: Spider Mites
        confidence: 0.72
    reliability_score: 0.82
    mulders_hypotheses:
      - excess: potassium_excess
        explains:
          - nitrogen_deficiency
        evidence: 0.85
        evidence_count: 1

reliability_score is a 0-1 trust signal for that plant’s diagnosis - higher means PlantLab is more confident the call is right. Route on it for automations that should only fire on high-trust results.

mulders_hypotheses is the nutrient antagonism read. Here it’s flagging that the nitrogen-deficiency symptoms might trace back to a potassium excess locking out nitrogen uptake, rather than an actual shortage - so piling on more nitrogen could make it worse. That’s the kind of thing that saves a week of chasing the wrong fix.

Each plant in a multi-plant frame gets its own entry with its own bbox (normalized [0,1] canopy box), so you always know which plant a diagnosis belongs to.


FAQ

How many checks per day on the free tier?

Three. One morning, one evening, one spare for when you’re feeling paranoid - enough for a home grow. Read your current account limits in the dashboard.

Can I use any camera?

If HA can snapshot from it, PlantLab can diagnose from it. Tested with Frigate, Wyze RTSP, ESP32-CAM, Reolink, and Tapo. Phone photos work too - drop the image in /config/www/ and point the service at the path.

Does this work for tomatoes?

No. PlantLab will look at your tomato and politely tell you it’s not cannabis.

It says healthy but I can see a problem. Now what?

Trust your eyes. The AI catches things you haven’t noticed yet - it’s not there to override what you already see. Early symptoms, odd lighting, and blurry photos all cut accuracy. Cannabis detection and the overall health check are the most reliable parts; specific condition labels, especially lookalike nutrient deficiencies, are harder and keep improving with each retrain.

Can I run this offline?

Not yet - cloud only. On-premise is on the roadmap for air-gapped facilities.


PlantLab diagnoses 30 cannabis conditions - nutrient deficiencies, pests, diseases, and environmental stress - from a single photo. The Home Assistant integration is open source at github.com/plantlab-ai/home-assistant-plantlab. Try it free at plantlab.ai.