    <p> I have set up some LoRaWAN temperature and humidity sensors, and
      am using the Things Stack to collect the data.</p>
    <p> <br>
    </p>
    <p> This gets processed via a web-hook and rendered as a graph.</p>
    <p> <br>
    </p>
    <p> The LoRaWAN Alliance - <a href="https://lora-alliance.org"
        rel="noopener noreferrer" target="_blank">
        https://lora-alliance.org</a> </p>
    <p> <br>
    </p>
    <p> Mastering LoRaWAN - <a
href="https://www.amazon.com/Mastering-LoRaWAN-Comprehensive-Communication-Connectivity-ebook/dp/B0CTRH6MV6"
        rel="noopener noreferrer" target="_blank">
https://www.amazon.com/Mastering-LoRaWAN-Comprehensive-Communication-Connectivity-ebook/dp/B0CTRH6MV6</a>
    </p>
    <p> <br>
    </p>
    <p> The Things Industries - <a
        href="https://thethingsindustries.com" rel="noopener noreferrer"
        target="_blank"> https://thethingsindustries.com</a> </p>
    <h2> <br>
    </h2>
    <h2> server.py</h2>
    <pre class="ql-syntax" spellcheck="false">import json
import sqlite3
import logging
from http.server import BaseHTTPRequestHandler, HTTPServer

rooms = {
    'eui-24e12*********07': 'living-room',
    'eui-24e12*********54': 'hall',
    'eui-24e12*********42': 'downstairs-office',
    'eui-24e12*********35': 'kitchen',
    'eui-24e12*********29': 'conservatory',
    'eui-24e12*********87': 'landing',
    'eui-24e12*********45': 'main-bedroom',
    'eui-24e12*********89': 'upstairs-office',
    'eui-24e12*********38': 'spare-bedroom',
    'eui-24e12*********37': 'playroom'
};

# Configure logging
logging.basicConfig(filename="server_log.txt", level=logging.INFO, format="%(asctime)s - %(message)s")

# Define the web server handler
class MyServerHandler(BaseHTTPRequestHandler):

    # Handle POST requests
    def do_POST(self):
        length = int(self.headers.get('Content-Length'))
        data = self.rfile.read(length).decode('utf-8')

        try:
            # Validate and parse JSON data
            json_data = json.loads(data)
            logging.info(f"Received valid JSON data: {json_data}")

            # Write the data to database
            id = json_data["end_device_ids"]["device_id"]
            room = rooms.get(id)
            readat = json_data["uplink_message"]["rx_metadata"][0]["time"]
            temp = json_data["uplink_message"]["decoded_payload"]["temperature"]
            hum = json_data["uplink_message"]["decoded_payload"]["humidity"]
            conn = sqlite3.connect('data.db')
            sql = """CREATE TABLE IF NOT EXISTS data (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                room TEXT,
                readat DATETIME,
                temp DECIMAL(4,1),
                hum DECIMAL(4,1)
            );"""
            conn.execute(sql)
            sql = "INSERT INTO data (room, readat, temp, hum) VALUES (?, ?, ?, ?)"
            conn.execute(sql, (room, readat, temp, hum))
            conn.commit()
            conn.close()

            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(bytes("Data received and logged!", "utf-8"))

        except json.JSONDecodeError:
            logging.error("Invalid JSON data received.")
            self.send_response(400)  # Bad Request
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(bytes("Invalid JSON format.", "utf-8"))

        except PermissionError:
            logging.error("File write permission denied.")
            self.send_response(500)  # Internal Server Error
            self.send_header("Content-type", "text/html")
            self.end_headers()
            self.wfile.write(bytes("Server error: Unable to write data to file.", "utf-8"))

# Start the server
server_address = ('0.0.0.0', 12345)  # Customize host and port if needed
httpd = HTTPServer(server_address, MyServerHandler)
print("Server started on http://localhost:12345")
httpd.serve_forever()
</pre>
    <h2> <br>
    </h2>
    <h2> process.php</h2>
    <pre class="ql-syntax" spellcheck="false">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;script src="https://cdn.jsdelivr.net/npm/chart.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;canvas id="temp" style="height:50vh"&gt;&lt;/canvas&gt;
&lt;canvas id="hum" style="height:50vh"&gt;&lt;/canvas&gt;
&lt;script&gt;
&lt;?php

    $colors = [
        '#FF9999',  // Light red
        '#FFCC99',  // Light orange
        '#FFFF99',  // Light yellow
        '#CCFF99',  // Light lime green
        '#99FF99',  // Light green
        '#99FFCC',  // Light teal
        '#99FFFF',  // Light sky blue
        '#99CCFF',  // Light blue
        '#9999FF',  // Light violet
        '#CC99FF',  // Light lavender
        '#FF99FF',  // Light pink
        '#FFCCFF', // Light rose
        '#FFD5D5',  // Light salmon
        '#FFDDAA',  // Light peach
        '#FFE0E0',  // Light beige
        '#FFF0F0' // Light ivory
    ];

    $results = [
        'living-room' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'hall' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'downstairs-office' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'kitchen' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'conservatory' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'landing' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'main-bedroom' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'upstairs-office' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'spare-bedroom' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ],
        'playroom' =&gt; [
            'temperature' =&gt; [],
            'humidity' =&gt; []
        ]
    ];

    $labels = [];

    $db = new SQLite3('data.db');
        $sql = "SELECT room, readat, temp, hum FROM data";
        $stmt = $db-&gt;prepare($sql);
        $result = $stmt-&gt;execute();

        while ($row = $result-&gt;fetchArray(SQLITE3_ASSOC)) {
                $where = $row['room'];

                                $tz = 'Europe/London';
                                $dt = new DateTime($row['readat']);
                                $dt-&gt;setTimezone(new DateTimeZone($tz));
                                $when = $dt-&gt;format('d/m/Y, H:i');

                $u = intdiv(date_format(date_create($row['readat']), "U"), 600);
                $temp = number_format($row['temp'], 1);
                $hum = number_format($row['hum'], 1);

        $labels[$u] = "\"$when\"";

                $results[$where]['temperature'][] = $temp;
                $results[$where]['humidity'][] = $hum;
        }

        $stmt-&gt;close();
        $db-&gt;close();

    $c = 0;
    foreach ($results as $key =&gt; $room) {
        $col = $colors[$c];
        $temp_datasets[] = "{ label: \"$key °C\",  data: [ ".implode(",", $room['temperature'])." ], borderColor: \"$col\" }";
        $hum_datasets[] = "{ label: \"$key %\", data: [ ".implode(",", $room['humidity'])." ], borderColor: \"$col\" }";
        $c++;
    }

?&gt;
const data1 = { datasets: [ &lt;?php echo implode(",",$temp_datasets); ?&gt; ], labels: [&lt;?php echo implode(",", $labels); ?&gt;] };
const ctx1 = document.getElementById("temp").getContext("2d");
const options1 = {
  type: "line",
  data: data1,
  options: {
    elements: {
      point:{
        radius: 0
      }
    }
  }
};
const chart1 = new Chart(ctx1, options1);

const data2 = { datasets: [ &lt;?php echo implode(",",$hum_datasets); ?&gt; ], labels: [&lt;?php echo implode(",", $labels); ?&gt;] };
const ctx2 = document.getElementById("hum").getContext("2d");
const options2 = {
  type: "line",
  data: data2,
  options: {
    elements: {
      point:{
        radius: 0
      }
    }
  }
};
const chart2 = new Chart(ctx2, options2);
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
    <h2> <br>
    </h2>
    <h2> Temperature Chart</h2>
    <p> <br>
    </p>
    <a href="hpr4311_image_1.png"><img moz-do-not-send="true"
        src="hpr4311_image_1_tn.png" alt="Graph showing temp over time in
        various rooms" height="201" border="0" width="400"></a>
    <p> <br>
    </p>
    <h2> Humidity Chart</h2>
    <p> <br>
    </p>
    <a href="hpr4311_image_2.png"><img moz-do-not-send="true"
        src="hpr4311_image_2_tn.png" alt="Graph showing humidity over time in
        various rooms" height="201" border="0"
        width="400"></a>
    <p> <br>
    </p>
