{"id":24,"date":"2026-06-07T23:53:42","date_gmt":"2026-06-07T14:53:42","guid":{"rendered":"https:\/\/fideafact.ddns.net\/?page_id=24"},"modified":"2026-06-12T10:44:01","modified_gmt":"2026-06-12T01:44:01","slug":"home-temphumidity","status":"publish","type":"page","link":"https:\/\/fideafact.ddns.net\/?page_id=24","title":{"rendered":"HOME Temp&amp;Humidity"},"content":{"rendered":"    <div class=\"sensor-device-control\">\n        <label for=\"sensorDeviceSelect\">\u5b9f\u6e2c\u30bb\u30f3\u30b5\u30fc<\/label>\n        <select id=\"sensorDeviceSelect\" aria-label=\"\u8868\u793a\u3059\u308b\u5b9f\u6e2c\u30bb\u30f3\u30b5\u30fc\">\n            <option value=\"sensor-1\">\u9001\u4fe1\u6a5f1<\/option>\n        <\/select>\n    <\/div>\n    <div id=\"sensorPeriodButtons\" style=\"display:flex; gap:8px; margin-bottom:12px; flex-wrap:wrap;\">\n        <button type=\"button\" data-period=\"24h\">24\u6642\u9593<\/button>\n        <button type=\"button\" data-period=\"7d\" aria-pressed=\"true\">1\u9031\u9593<\/button>\n        <button type=\"button\" data-period=\"30d\">1\u304b\u6708<\/button>\n    <\/div>\n    <div id=\"sensorChartStatus\" role=\"status\">\u30bb\u30f3\u30b5\u30fc\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3093\u3067\u3044\u307e\u3059...<\/div>\n    <canvas id=\"sensorChart\" width=\"800\" height=\"400\"><\/canvas>\n    <section class=\"sensor-system-summary\" aria-labelledby=\"sensor-system-summary-title\">\n        <h2 id=\"sensor-system-summary-title\">\u6e29\u6e7f\u5ea6\u30c7\u30fc\u30bf\u30b7\u30b9\u30c6\u30e0\u306b\u3064\u3044\u3066<\/h2>\n        <p>\n            \u5ba4\u5185\u306e\u6e29\u5ea6\u30fb\u6e7f\u5ea6\u3092\u5b9a\u671f\u7684\u306b\u6e2c\u5b9a\u3057\u3001Raspberry Pi\u30b5\u30fc\u30d0\u30fc\u3078\u8a18\u9332\u3057\u3066\u3044\u307e\u3059\u3002\n            \u3053\u306e\u30da\u30fc\u30b8\u3067\u306f\u5b9f\u6e2c\u30bb\u30f3\u30b5\u30fc\u3068\u6c17\u8c61\u5e81\u30a2\u30e1\u30c0\u30b9\u5c90\u961c\u306e\u5024\u3092\u91cd\u306d\u3066\u78ba\u8a8d\u3067\u304d\u307e\u3059\u3002\n        <\/p>\n\n        <h3>\u30b0\u30e9\u30d5\u8868\u793a<\/h3>\n        <ul>\n            <li>\u300c\u5b9f\u6e2c\u30bb\u30f3\u30b5\u30fc\u300d\u6b04\u304b\u3089\u8868\u793a\u3059\u308b\u30bb\u30f3\u30b5\u30fc\u7aef\u672b\u3092\u9078\u629e\u3067\u304d\u307e\u3059\u3002<\/li>\n            <li>\u300c24\u6642\u9593\u300d\u300c1\u9031\u9593\u300d\u300c1\u304b\u6708\u300d\u306e\u30dc\u30bf\u30f3\u3067\u8868\u793a\u671f\u9593\u3092\u5207\u308a\u66ff\u3048\u3089\u308c\u307e\u3059\u3002<\/li>\n            <li>\u5b9f\u6e2c\u6e29\u5ea6\u306f\u30ec\u30c3\u30c9\u3001\u5b9f\u6e2c\u6e7f\u5ea6\u306f\u30d6\u30eb\u30fc\u3001\u6c17\u8c61\u5e81\u6e29\u5ea6\u306f\u30d6\u30e9\u30c3\u30af\u3001\u6c17\u8c61\u5e81\u6e7f\u5ea6\u306f\u30b0\u30ea\u30fc\u30f3\u3067\u8868\u793a\u3057\u307e\u3059\u3002<\/li>\n            <li>\u5b9f\u7dda\u304c\u5b9f\u6e2c\u30bb\u30f3\u30b5\u30fc\u3001\u7834\u7dda\u304c\u6c17\u8c61\u5e81\u30a2\u30e1\u30c0\u30b9\u5c90\u961c\u3067\u3059\u3002<\/li>\n        <\/ul>\n    <\/section>\n    <style>\n    .sensor-system-summary {\n        margin-top: 28px;\n        padding: 20px;\n        border: 1px solid #dbe5f1;\n        border-radius: 10px;\n        background: rgba(255, 255, 255, 0.88);\n        line-height: 1.8;\n    }\n    .sensor-system-summary h2,\n    .sensor-system-summary h3 {\n        margin-top: 0;\n        color: #1e3a8a;\n    }\n    .sensor-system-summary h3 {\n        margin-top: 20px;\n        font-size: 1.1em;\n    }\n    .sensor-system-summary ul {\n        padding-left: 1.4em;\n        margin-bottom: 0;\n    }\n    .sensor-device-control {\n        display: flex;\n        align-items: center;\n        gap: 10px;\n        margin-bottom: 12px;\n    }\n    .sensor-device-control label {\n        font-weight: 700;\n    }\n    .sensor-device-control select {\n        min-width: 180px;\n        padding: 7px 10px;\n        border: 1px solid #94a3b8;\n        border-radius: 6px;\n        background: #fff;\n    }\n    <\/style>\n    <script src=\"https:\/\/fideafact.ddns.net\/wp-content\/themes\/blogvista\/assets\/js\/chart.umd.min.js\"><\/script>\n    <script>\n    window.sensorChartInstance = window.sensorChartInstance || null;\n    let currentSensorPeriod = '7d';\n    const jmaDeviceId = 'jma-gifu';\n    const tempColor = '#ef4444';\n    const tempJmaColor = '#111827';\n    const humidityColor = '#2563eb';\n    const humidityJmaColor = '#22c55e';\n\n    function sensorDisplayName(deviceId) {\n        if (deviceId === 'jma-gifu') return '\u6c17\u8c61\u5e81 \u5c90\u961c';\n        if (deviceId === 'sensor-1') return '\u9001\u4fe1\u6a5f1';\n        return deviceId;\n    }\n\n    function formatSensorLabel(rawTime) {\n        const [date, time = ''] = String(rawTime || '').split(' ');\n        return `${date.replaceAll('-', '\/')} ${time.substring(0, 5)}`;\n    }\n\n    function normalizeDeviceId(deviceId) {\n        return String(deviceId || '').toLowerCase();\n    }\n\n    function makeSeries(rows, deviceId, field) {\n        const targetDeviceId = normalizeDeviceId(deviceId);\n        const byTime = new Map(rows\n            .filter(row => normalizeDeviceId(row.device_id) === targetDeviceId)\n            .map(row => [formatSensorLabel(row.time), Number(row[field])]));\n        return Array.from(byTime.entries()).map(([x, y]) => ({ x, y }));\n    }\n\n    function loadSensorChart(period, deviceId) {\n        const status = document.getElementById('sensorChartStatus');\n        const buttons = document.querySelectorAll('#sensorPeriodButtons button');\n        const deviceSelect = document.getElementById('sensorDeviceSelect');\n        currentSensorPeriod = period;\n        deviceId = deviceId || deviceSelect.value || 'sensor-1';\n        const requestDevices = `${deviceId},${jmaDeviceId}`;\n\n        if (status) {\n            status.textContent = '\u30bb\u30f3\u30b5\u30fc\u30c7\u30fc\u30bf\u3092\u8aad\u307f\u8fbc\u3093\u3067\u3044\u307e\u3059...';\n        }\n        buttons.forEach(button => {\n            const selected = button.dataset.period === period;\n            button.setAttribute('aria-pressed', selected ? 'true' : 'false');\n            button.style.fontWeight = selected ? 'bold' : 'normal';\n            button.disabled = selected;\n        });\n\n        fetch(`\/sensor\/data?period=${encodeURIComponent(period)}&device=${encodeURIComponent(requestDevices)}`, { cache: 'no-store' })\n        .then(r => {\n            if (!r.ok) {\n                throw new Error(`HTTP ${r.status}`);\n            }\n            return r.json();\n        })\n        .then(json => {\n            const rows = (Array.isArray(json.data) ? json.data : [])\n                .map(d => ({\n                    device_id: normalizeDeviceId(d.device_id || deviceId),\n                    time: String(d.time || ''),\n                    temperature: Number(d.temperature),\n                    humidity: Number(d.humidity)\n                }))\n                .filter(d => d.time && Number.isFinite(d.temperature) && Number.isFinite(d.humidity));\n            if (rows.length === 0) {\n                throw new Error('\u8868\u793a\u3067\u304d\u308b\u30bb\u30f3\u30b5\u30fc\u30c7\u30fc\u30bf\u304c\u3042\u308a\u307e\u305b\u3093');\n            }\n\n            const labels = [...new Set(rows.map(d => formatSensorLabel(d.time)))].sort();\n            const actualName = sensorDisplayName(deviceId);\n            const ctx = document.getElementById('sensorChart');\n            const status = document.getElementById('sensorChartStatus');\n\n            if (ctx) {\n                const existingChart = Chart.getChart(ctx);\n                if (existingChart) {\n                    existingChart.destroy();\n                }\n\n                window.sensorChartInstance = new Chart(ctx, {\n                    type: 'line',\n                    data: {\n                        labels: labels,\n                        datasets: [\n                            {label: `${actualName} \u6e29\u5ea6 (\u00b0C)`, data: makeSeries(rows, deviceId, 'temperature'), borderColor: tempColor, backgroundColor: tempColor, borderWidth: 2, pointRadius: 0, yAxisID: 'y', tension: 0.1, spanGaps: true},\n                            {label: '\u6c17\u8c61\u5e81 \u5c90\u961c \u6e29\u5ea6 (\u00b0C)', data: makeSeries(rows, jmaDeviceId, 'temperature'), borderColor: tempJmaColor, backgroundColor: tempJmaColor, borderWidth: 2, borderDash: [6, 4], pointRadius: 0, yAxisID: 'y', tension: 0.1, spanGaps: true},\n                            {label: `${actualName} \u6e7f\u5ea6 (%)`, data: makeSeries(rows, deviceId, 'humidity'), borderColor: humidityColor, backgroundColor: humidityColor, borderWidth: 2, pointRadius: 0, yAxisID: 'y1', tension: 0.1, spanGaps: true},\n                            {label: '\u6c17\u8c61\u5e81 \u5c90\u961c \u6e7f\u5ea6 (%)', data: makeSeries(rows, jmaDeviceId, 'humidity'), borderColor: humidityJmaColor, backgroundColor: humidityJmaColor, borderWidth: 2, borderDash: [6, 4], pointRadius: 0, yAxisID: 'y1', tension: 0.1, spanGaps: true}\n                        ]\n                    },\n                    options: {\n                        responsive: true,\n                        animation: false,\n                        parsing: false,\n                        interaction: { mode: 'index', intersect: false },\n                        plugins: { legend: { labels: { usePointStyle: true } } },\n                        scales: {\n                            x: {\n                                type: 'category',\n                                ticks: {\n                                    autoSkip: true,\n                                    maxTicksLimit: 10,\n                                    maxRotation: 45,\n                                    minRotation: 45\n                                }\n                            },\n                            y: {position: 'left', title: {display: true, text: '\u6e29\u5ea6 (\u00b0C)'}},\n                            y1: {position: 'right', title: {display: true, text: '\u6e7f\u5ea6 (%)'}, grid: {drawOnChartArea: false}}\n                        }\n                    }\n                });\n\n                if (status) {\n                    status.textContent = '';\n                }\n            }\n        })\n        .catch(err => {\n            console.error('\u30c7\u30fc\u30bf\u53d6\u5f97\u30a8\u30e9\u30fc:', err);\n            const status = document.getElementById('sensorChartStatus');\n            if (status) {\n                status.textContent = `\u30b0\u30e9\u30d5\u3092\u8868\u793a\u3067\u304d\u307e\u305b\u3093: ${err.message}`;\n            }\n        });\n    }\n\n    document.querySelectorAll('#sensorPeriodButtons button').forEach(button => {\n        button.addEventListener('click', () => loadSensorChart(button.dataset.period, document.getElementById('sensorDeviceSelect').value));\n    });\n\n    document.getElementById('sensorDeviceSelect').addEventListener('change', event => {\n        loadSensorChart(currentSensorPeriod, event.target.value);\n    });\n\n    fetch('\/sensor\/devices', { cache: 'no-store' })\n        .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))\n        .then(json => {\n            const select = document.getElementById('sensorDeviceSelect');\n            const devices = (Array.isArray(json.devices) ? json.devices : [])\n                .filter(device => device.device_id !== jmaDeviceId);\n            if (devices.length) {\n                select.replaceChildren(...devices.map(device => {\n                    const option = document.createElement('option');\n                    option.value = device.device_id;\n                    option.textContent = sensorDisplayName(device.display_name || device.device_id);\n                    return option;\n                }));\n            }\n            loadSensorChart('7d', select.value);\n        })\n        .catch(err => {\n            console.error('\u9001\u4fe1\u6a5f\u4e00\u89a7\u53d6\u5f97\u30a8\u30e9\u30fc:', err);\n            loadSensorChart('7d', 'sensor-1');\n        });\n    <\/script>\n    \n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-24","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/pages\/24","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=24"}],"version-history":[{"count":8,"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/pages\/24\/revisions"}],"predecessor-version":[{"id":151,"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=\/wp\/v2\/pages\/24\/revisions\/151"}],"wp:attachment":[{"href":"https:\/\/fideafact.ddns.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=24"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}