ShinjiQune
Published © GPL3+

The HANIWA: Timeless yet Cutting-edge Irrigatable Scarecrow

A Pico W hub turning Cloud-AI forecast and Edge-AI security into soil alerts via an ancient statue's eye.

IntermediateFull instructions provided2 days110
The HANIWA: Timeless yet Cutting-edge Irrigatable Scarecrow

Things used in this project

Hardware components

The replica of the HANIWA
I bought this as a souvenir at the Shibayama Kofun Haniwakan, which costed 2,800[yen] (~18[$]). However, I made it priceless guardian of my garden. I don't think they're sold online. If you want one, you could try buying a different unglazed ceramic figurine or buying some clay and making one yourself. If you have the time to come to Japan, I can show you around the area.
×1
Raspberry Pi Pico 2
Raspberry Pi Pico 2
I used Raspberry Pi Pico 2 WH.
×1
Grove - Capacitive Soil Moisture Sensor
Seeed Studio Grove - Capacitive Soil Moisture Sensor
I used B07MC9ZGXL from "DIY Studio."
×1
Resistor 220 ohm
Resistor 220 ohm
×4
LED (generic)
LED (generic)
×3

Software apps and online services

VS Code
Microsoft VS Code
Debian
https://www.debian.org/
OpenCV
OpenCV
Open Weather API
https://openweathermap.org/

Story

Read more

Schematics

Connect LEDs and Moisture Sensor

Photo of the breadboard connecting LEDs and a moisture sensor to Raspberry Pi Pico 2 W.

Code

Test: CMakeLists for USB Serial Communication

C/C++
CMakeLists.txt for Testing USB Serial Communication
cmake_minimum_required(VERSION 3.13)

# Import SDK
include(pico_sdk_import.cmake)

# Using not Pico but Pico 2 (RP2350)
set(PICO_BOARD pico2)

project(haniwa_monitor C CXX ASM)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

pico_sdk_init()

# Define file names
add_executable(haniwa_monitor
    haniwa_monitor.cpp 
)

# Library link to use blinking LED
target_link_libraries(haniwa_monitor
    pico_stdlib
    hardware_adc
)

# Enable USB output, disable UART output
pico_enable_stdio_usb(haniwa_monitor 1)
pico_enable_stdio_uart(haniwa_monitor 0)

# Activate generating .uf2 file
pico_add_extra_outputs(haniwa_monitor)

Make Decision for Watering

Python
Decision making with real-time soil moisture data from the local field Pico and the past, present and future weather data from the OpenWeatherMapAPI
CURRENT_MOISTURE = 4095   # Placeholder for current soil moisture value

def make_decision() -> int:
    """
    Evaluates environmental factors to determine if watering is necessary.
    Returns:
        0: SKIP (Green LED)     - Sufficient moisture or rain detected/predicted.
        1: GO   (Red LED)       - Dry soil and no significant rain expected.
        2: TOO_MUCH  (Blue LED) - Sufficient moisture and more rain predicted.
        3: ERROR (Blue LED)     - System error or data missing, high frequency of blinking.
    """
    
    # use global variable to store the current soil moisture value
    global CURRENT_MOISTURE

    try:
        # 1. Fetch real-time soil moisture from the local sensor data
        with open(MOISTURE_FILE, 'r') as f:
            moisture_data = json.load(f)
            # Default to 100 (wet) to prevent accidental over-watering if file is corrupt
            CURRENT_MOISTURE = int(moisture_data.get("MOISTURE", 100))
        # 2. Analyze weather trends: past, present, and future forecast
        with open(LOG_FILE, 'r') as f:
            rows = list(csv.DictReader(f))
            if not rows:
                return 3 # Error: No historical data found
            # Integrate all rain factors into a single score
            rain_slots = 0
            # Weighted historical/current rain (Past & Present)
            if rows[-1]['weather'] == 'Rain':
                rain_slots += 3
            if len(rows) >= 2 and rows[-2]['weather'] == 'Rain':
                rain_slots += 2
            if len(rows) >= 3 and rows[-3]['weather'] == 'Rain':
                rain_slots += 1
            # Predicted rain intensity (Future)
            rain_slots += int(rows[-1]['rain_slots_24h'])
        # 3. Final Decision Logic
        if CURRENT_MOISTURE >= MOISTURE_LIMIT:
            return 3  # ERROR
        elif rain_slots > RAIN_SLOTS_THRESHOLD and CURRENT_MOISTURE > MOISTURE_THRESHOLD:
            return 2  # TOO_MUCH
        elif rain_slots > RAIN_SLOTS_THRESHOLD or CURRENT_MOISTURE > MOISTURE_THRESHOLD:
            return 0  # SKIP
        else:
            return 1  # GO
    except Exception as e:
        print(f"Decision Error: {e}")
        return 3  # ERROR

def decision_to_advice(decision) -> str:
    """Convert decision code to human-readable advice."""
    if decision == 0:
        return "SKIP"
    elif decision == 1:
        return "GO"
    elif decision == 2:
        return "TOO_MUCH"
    else:
        return "ERROR"

Test: LEDs and Moisture Sensor

C/C++
The code for testing my handmade circuit with LEDs and a soil moisture sensor
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"

// Define constants
const uint LED_RED   = 13;
const uint LED_GREEN = 14;
const uint LED_BLUE  = 15;
const uint SENSOR_VCC = 22;

void blink_led(uint gpio, int duration_sec) {
    // 1 cycle = 1 second (0.5s ON + 0.5s OFF)
    // Simply loop 'duration_sec' times to blink for that many seconds.
    for (int i = 0; i < duration_sec; i++) {
        gpio_put(gpio, 1); // ON
        sleep_ms(500);
        gpio_put(gpio, 0); // OFF
        sleep_ms(500);
    }
}

int main() {
    // Initialize all of the SDK
    stdio_init_all();

    // Initialize LED pins
    const uint leds[] = {LED_RED, LED_GREEN, LED_BLUE};
    for (uint led : leds) {
        gpio_init(led);
        gpio_set_dir(led, GPIO_OUT);
    }

    // Initialize Soil Moisture Sensor pins
    gpio_init(SENSOR_VCC);
    gpio_set_dir(SENSOR_VCC, GPIO_OUT);

    // Initialize Analog-to-Digital Converter
    adc_init();
    adc_gpio_init(26);
    adc_select_input(0);

    while(true){
        // VCC ON
        gpio_put(SENSOR_VCC, 1);
        sleep_ms(50);

        // Print data to the PC terminal
        uint16_t reading = adc_read();
        printf("Soil Moisture: %d\n", reading);

        // VCC OFF
        gpio_put(SENSOR_VCC, 0);

        // Blinking LEDs of RGB
        blink_led(LED_RED, 3);
        blink_led(LED_GREEN, 3);
        blink_led(LED_BLUE, 3);
    }

}

SharedMemory

Python
Explain using shared memory for a real-time backend processing
from multiprocessing import shared_memory

# The unique identifier for our inter-process communication
SHM_NAME = "memories_of_haniwa_garden"
SHM_SIZE = 8 # Enough space for our status flags

# Logic to initialize the shared memory segment
try:
  # 'DrWadachi' (Main Service) creates the memory block
  shm = shared_memory.SharedMemory(name=SHM_NAME, create=True, size=SHM_SIZE)
  print("Shared Memory created.")
  except FileExistsError:
  # If the block exists from a previous run, we simply attach to it
  shm = shared_memory.SharedMemory(name=SHM_NAME)
  print("Attached to existing Shared Memory.")

# Now, any process can access the data via shm.buf
# e.g., shm.buf[0] = 1 (Person detected!)

haniwa-hybrid

Timeless yet Cutting-edge Terracotta Irrigation and Smart Scarecrow Hybrid System

Credits

ShinjiQune
7 projects • 1 follower
Ph. D. in Engineering, Optics, 3D Holograms. Designer for High Power Laser Manufacturing Systems. Project Leader regarding Metal AM Monitor.

Comments