🐟 智能喂鱼器:基于 BMKG 天气的自动喂食系统

中级

这是一个Engineering、Multimodal AI领域的自动化工作流,包含 13 个节点。主要使用 If、Set、Code、Merge、Telegram 等节点。 🐟 智能喂鱼器:基于 BMKG 天气和 Telegram 提醒的自动喂食系统

前置要求
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "ZWdJS9zTlAfcE8QW",
  "meta": {
    "instanceId": "b14f5dd921befc4584084cc386aea593f73c7c2b00b50933075d7967a4d1c502"
  },
  "name": "🐟 智能喂鱼器:基于 BMKG 天气的自动喂食系统",
  "tags": [],
  "nodes": [
    {
      "id": "da547149-76e6-455a-970c-3bbe3aa4c0be",
      "name": "Cron:05:30 & 16:30 WIB",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        528,
        304
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c024b1c2-5fc5-42f8-9f41-5de6c9a39a82",
      "name": "配置",
      "type": "n8n-nodes-base.set",
      "position": [
        720,
        304
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "locationName",
              "name": "locationName",
              "type": "string",
              "value": "Main Pond"
            },
            {
              "id": "lat",
              "name": "lat",
              "type": "string",
              "value": "-6.2000"
            },
            {
              "id": "lon",
              "name": "lon",
              "type": "string",
              "value": "106.8166"
            },
            {
              "id": "bmkgUrlTemplate",
              "name": "bmkgUrlTemplate",
              "type": "string",
              "value": "https://api.bmkg.go.id/publik/prakiraan-weather?adm4={{ADM4}}"
            },
            {
              "id": "bmkgApiKey",
              "name": "bmkgApiKey",
              "type": "string",
              "value": "{{PLACEHOLDER}}"
            },
            {
              "id": "telegramBotToken",
              "name": "telegramBotToken",
              "type": "string",
              "value": "{{PLACEHOLDER}}"
            },
            {
              "id": "telegramChatId",
              "name": "telegramChatId",
              "type": "string",
              "value": "{{PLACEHOLDER}}"
            },
            {
              "id": "esp8266WebhookUrl",
              "name": "esp8266WebhookUrl",
              "type": "string",
              "value": "{{PLACEHOLDER}}"
            },
            {
              "id": "adm4",
              "name": "adm4",
              "type": "string",
              "value": "31.71.03.1001"
            },
            {
              "id": "thresholdProb",
              "name": "thresholdProb",
              "type": "string",
              "value": "60"
            },
            {
              "id": "reducePercent",
              "name": "reducePercent",
              "type": "string",
              "value": "-20"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "e72ef26d-d37a-4d33-a485-cbbb2352554a",
      "name": "构建天气预报 URL",
      "type": "n8n-nodes-base.code",
      "position": [
        896,
        304
      ],
      "parameters": {
        "jsCode": "// Build BMKG API URL with error handling\ntry {\n  const adm4 = $input.item(0).json.adm4;\n  const urlTemplate = $input.item(0).json.bmkgUrlTemplate;\n  \n  if (!adm4 || !urlTemplate) {\n    throw new Error('Missing required parameters: adm4 or urlTemplate');\n  }\n  \n  const url = urlTemplate.replace('{{ADM4}}', adm4);\n  \n  return {\n    json: { \n      url,\n      timestamp: new Date().toISOString(),\n      location: $input.item(0).json.locationName\n    }\n  };\n} catch (error) {\n  return {\n    json: {\n      error: error.message,\n      url: 'https://api.bmkg.go.id/publik/prakiraan-weather?adm4=31.71.03.1001'\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "1e132997-bd59-4dce-b2c8-4b60d92aa1ea",
      "name": "HTTP:BMKG 天气预报",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1072,
        304
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 30000,
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f37bd636-4f38-4300-8909-f16cd91f7423",
      "name": "解析与评分天气(6-12小时)",
      "type": "n8n-nodes-base.code",
      "position": [
        1280,
        304
      ],
      "parameters": {
        "jsCode": "// Enhanced weather parsing with better error handling\ntry {\n  const data = $input.item(0).json;\n  const configData = $('Config').item(0).json;\n  \n  let rain_hours = 0, rain_prob_max6h = 0, rain_prob_avg12h = 0;\n  let items = [];\n  \n  // Handle different API response structures\n  if (data.data && Array.isArray(data.data)) {\n    items = data.data;\n  } else if (data.lokasi && data.lokasi[0] && data.lokasi[0].weather) {\n    items = data.lokasi[0].weather;\n  } else if (Array.isArray(data)) {\n    items = data;\n  }\n  \n  let rain_count = 0, rain_sum = 0, max6h = 0, total = 0;\n  let weather_conditions = [];\n  \n  // Process weather data for next 12 hours (4 periods of 3 hours)\n  for (let i = 0; i < Math.min(items.length, 4); i++) {\n    const item = items[i];\n    let rainProb = 0;\n    \n    // Extract rain probability from different possible fields\n    if (item.hu) rainProb = parseFloat(item.hu) || 0;\n    if (item.rain_prob) rainProb = parseFloat(item.rain_prob) || 0;\n    if (item.weather && item.weather.toLowerCase().includes('rain')) rainProb = 80;\n    if (item.weather_desc && item.weather_desc.toLowerCase().includes('rain')) rainProb = 75;\n    \n    // Estimate probability from weather description\n    if (item.weather_desc) {\n      const desc = item.weather_desc.toLowerCase();\n      if (desc.includes('heavy rain') || desc.includes('thunderstorm')) rainProb = Math.max(rainProb, 85);\n      else if (desc.includes('rain') && desc.includes('thunder')) rainProb = Math.max(rainProb, 80);\n      else if (desc.includes('rain')) rainProb = Math.max(rainProb, 70);\n      else if (desc.includes('clouds') && desc.includes('thick')) rainProb = Math.max(rainProb, 40);\n    }\n    \n    rain_sum += rainProb;\n    if (i < 2 && rainProb > max6h) max6h = rainProb; // First 6 hours (2 periods)\n    if (rainProb > 0) rain_count++;\n    total++;\n    \n    weather_conditions.push({\n      period: i + 1,\n      time: item.local_datetime || item.weatherTime || `Period ${i + 1}`,\n      rain_prob: rainProb,\n      weather: item.weather_desc || item.weather || 'N/A',\n      temp: item.t || item.tempC || 'N/A'\n    });\n  }\n  \n  if (total > 0) {\n    rain_prob_avg12h = Math.round(rain_sum / total);\n    rain_prob_max6h = max6h;\n  }\n  \n  const final_rain_prob = Math.max(rain_prob_avg12h, rain_prob_max6h);\n  const threshold = parseInt(configData.thresholdProb) || 60;\n  const feed_ratio = final_rain_prob >= threshold \n    ? parseInt(configData.reducePercent) || -20\n    : 0;\n  \n  return {\n    json: {\n      rain_prob: final_rain_prob,\n      rain_prob_max6h: rain_prob_max6h,\n      rain_prob_avg12h: rain_prob_avg12h,\n      feed_ratio: feed_ratio,\n      weather_conditions: weather_conditions,\n      analysis_time: new Date().toLocaleString('id-ID', { timeZone: 'Asia/Jakarta' }),\n      location: configData.locationName\n    }\n  };\n  \n} catch (error) {\n  return {\n    json: {\n      error: error.message,\n      rain_prob: 0,\n      rain_prob_max6h: 0,\n      rain_prob_avg12h: 0,\n      feed_ratio: 0,\n      weather_conditions: [],\n      analysis_time: new Date().toLocaleString('id-ID', { timeZone: 'Asia/Jakarta' })\n    }\n  };\n}"
      },
      "typeVersion": 2
    },
    {
      "id": "381e3f06-03ee-4c8c-a2d3-d60798c2ccb7",
      "name": "IF:高降雨概率",
      "type": "n8n-nodes-base.if",
      "position": [
        1472,
        304
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "rain_condition",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.rain_prob }}",
              "rightValue": "={{ $('Config').item(0).json.thresholdProb }}"
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "a089a86b-fe8e-44b9-8c5e-677018c53b97",
      "name": "设置:减少喂食量 20%",
      "type": "n8n-nodes-base.set",
      "position": [
        1680,
        224
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "note",
              "name": "note",
              "type": "string",
              "value": "🌧️ WARNING: High rain probability ({{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob }}%)\\n\\n📉 Reduce feed by 20%\\n⚡ Turn on aerator (15-20 menit/jam)\\n🔍 Check DO & observe fish appetite\\n🐟 Monitor fish health"
            },
            {
              "id": "feed_ratio",
              "name": "feed_ratio",
              "type": "number",
              "value": "={{ $('Parse & Score Weather (6-12h)').item(0).json.feed_ratio }}"
            },
            {
              "id": "action_type",
              "name": "action_type",
              "type": "string",
              "value": "reduce_feed"
            },
            {
              "id": "esp8266_command",
              "name": "esp8266_command",
              "type": "string",
              "value": "FEED_REDUCE_20"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "fb16bf68-b431-417f-9443-3bbb2cdd2f2f",
      "name": "设置:正常喂食量 0%",
      "type": "n8n-nodes-base.set",
      "position": [
        1680,
        384
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "note",
              "name": "note",
              "type": "string",
              "value": "☀️ Weather is relatively safe ({{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob }}%)\\n\\n🐟 Normal feed - no reduction\\n✅ Continue monitoring water quality\\n📊 Monitor pond parameters routinely"
            },
            {
              "id": "feed_ratio",
              "name": "feed_ratio",
              "type": "number",
              "value": 0
            },
            {
              "id": "action_type",
              "name": "action_type",
              "type": "string",
              "value": "normal_feed"
            },
            {
              "id": "esp8266_command",
              "name": "esp8266_command",
              "type": "string",
              "value": "FEED_NORMAL"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "33f96e3a-8a4b-4260-93d8-75b803fe1d76",
      "name": "合并分支",
      "type": "n8n-nodes-base.merge",
      "position": [
        1920,
        304
      ],
      "parameters": {
        "mode": "chooseBranch"
      },
      "typeVersion": 2.1
    },
    {
      "id": "74cacc40-b5b7-4939-949c-eaeabef1223f",
      "name": "ESP8266 喂鱼器控制",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2128,
        304
      ],
      "parameters": {
        "url": "={{ $('Config').item(0).json.esp8266WebhookUrl }}",
        "method": "POST",
        "options": {
          "timeout": 10000
        },
        "sendBody": true,
        "sendHeaders": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "command",
              "value": "={{ $json.esp8266_command }}"
            },
            {
              "name": "feed_ratio",
              "value": "={{ $json.feed_ratio }}"
            },
            {
              "name": "rain_prob",
              "value": "={{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob }}"
            },
            {
              "name": "timestamp",
              "value": "={{ new Date().toISOString() }}"
            },
            {
              "name": "location",
              "value": "={{ $('Config').item(0).json.locationName }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "User-Agent",
              "value": "n8n-bmkg-feeder/1.0"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "8cf99c87-530a-4180-b0f6-4bd53043f07e",
      "name": "Telegram:发送报告",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2352,
        304
      ],
      "webhookId": "bc9aff86-c2b7-429a-ba93-0c6bcee94c3d",
      "parameters": {
        "text": "=🐟 **{{ $('Config').item(0).json.locationName }}** - **Automatic Feeding Schedule**\n\n📅 **Time**: {{ new Date().toLocaleString('id-ID', { timeZone: 'Asia/Jakarta' }) }}\n\n🌤️ **BMKG Weather Analysis:**\n• 12-hour rain probability: **{{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob }}%**\n• 6-hour rain probability: **{{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob_max6h }}%**\n• 12-hour average: **{{ $('Parse & Score Weather (6-12h)').item(0).json.rain_prob_avg12h }}%**\n\n🎯 **Feeding Decision:**\n• Feed ratio: **{{ $json.feed_ratio }}%**\n• ESP8266 Status: {{ $('ESP8266 Fish Feeder Control').item(0).json ? '✅ Sent' : '❌ Failed' }}\n\n{{ $json.note }}\n\n---\n*Powered by BMKG API + ESP8266 + n8n*\n*Next feeding: {{ new Date(Date.now() + (new Date().getHours() < 12 ? (16 - new Date().getHours()) * 3600000 : (29 - new Date().getHours()) * 3600000)).toLocaleString('id-ID', { timeZone: 'Asia/Jakarta' }) }}*",
        "chatId": "={{ $('Config').item(0).json.telegramChatId }}",
        "additionalFields": {
          "parse_mode": "Markdown",
          "disable_notification": false
        }
      },
      "credentials": {
        "telegramApi": {
          "id": "5UNbimarOaH1QxRo",
          "name": "Telegram account 2"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "8cb2be2c-6718-4d50-a896-fe472f037e22",
      "name": "活动记录器",
      "type": "n8n-nodes-base.code",
      "position": [
        2576,
        304
      ],
      "parameters": {
        "jsCode": "// Log activity and prepare summary\nconst weatherData = $('Parse & Score Weather (6-12h)').item(0).json;\nconst feedData = $input.item(0).json;\nconst configData = $('Config').item(0).json;\nconst esp8266Response = $('ESP8266 Fish Feeder Control').item(0).json;\n\nconst logEntry = {\n  timestamp: new Date().toISOString(),\n  location: configData.locationName,\n  rain_probability: weatherData.rain_prob,\n  feed_ratio: feedData.feed_ratio,\n  action_type: feedData.action_type,\n  esp8266_status: esp8266Response ? 'success' : 'failed',\n  weather_summary: weatherData.weather_conditions?.slice(0, 2) || [],\n  bmkg_analysis_time: weatherData.analysis_time\n};\n\nreturn {\n  json: logEntry\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "901a9809-bdc3-4616-a17a-5abf5f465902",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 464,
        "height": 5312,
        "content": "# 📝 N8N 工作流安装指南 - 自动喂鱼器"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "b4a60651-e4ef-4bfd-813c-919f7a46d219",
  "connections": {
    "Config": {
      "main": [
        [
          {
            "node": "Build Forecast URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Branches": {
      "main": [
        [
          {
            "node": "ESP8266 Fish Feeder Control",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Forecast URL": {
      "main": [
        [
          {
            "node": "HTTP: Forecast BMKG",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP: Forecast BMKG": {
      "main": [
        [
          {
            "node": "Parse & Score Weather (6-12h)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Normal Feed 0%": {
      "main": [
        [
          {
            "node": "Merge Branches",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Set: Reduce Feed 20%": {
      "main": [
        [
          {
            "node": "Merge Branches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Telegram: Send Report": {
      "main": [
        [
          {
            "node": "Activity Logger",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cron: 05:30 & 16:30 WIB": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: High Rain Probability": {
      "main": [
        [
          {
            "node": "Set: Reduce Feed 20%",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set: Normal Feed 0%",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ESP8266 Fish Feeder Control": {
      "main": [
        [
          {
            "node": "Telegram: Send Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Score Weather (6-12h)": {
      "main": [
        [
          {
            "node": "IF: High Rain Probability",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

这是一个中级难度的工作流,适用于Engineering、Multimodal AI等场景。适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量13
分类2
节点类型8
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

作者
Tegar karunia ilham

Tegar karunia ilham

@tegarkaruniailham

Helping business owners & marketers automate their processes with n8n. Specialist in custom workflows, API integrations, and template development. 📈 100+ successful automation projects 🔧 Premium n8n templates available 💡 Free consultation for custom automation Book a consultation for your business digital transformation!"

外部链接
在 n8n.io 上查看 →

分享此工作流