Skip to main content
Literacy Clock using MQTT

Turn Any Device into a Literary Clock with our MQTT Feed

By Making No Comments

The Concept of the Literary Clock

A literary clock is a unique fusion of literature and timekeeping. Every minute of the day is represented by a corresponding quote from a literary work, providing not just the time, but a nugget of wisdom, humor, or beauty from the world of books. This idea transforms the mundane act of checking the time into a delightful literary experience. The initial concept, as outlined by the original Literary Clock Project, involves creating a clock that displays quotes from various literary works for every minute of the day. This concept not only appeals to book lovers but also serves as an artistic and educational piece, bringing literature into everyday life in a novel way. For example, as we type this the time is twenty one minutes to five and the quote from the database, including the book name is:

 

“I was told that in his vest pocket he kept a chronometer instead of a watch. If someone asked him what time it was, he would say, “”A minute and twenty-one seconds to five.””” Book: The Collected Stories

Crowdsourcing the Literary Database

An essential aspect of this project was the crowdsourcing of the literary database. The Literary Clock Project engaged a global community of literature enthusiasts to contribute quotes for every minute of the day. This collaborative effort created a diverse and rich collection of quotes, encompassing a wide range of genres, periods, and authors. Contributors from around the world submitted their favourite passages, transforming this project into a communal celebration of literature. The concept was taken a step further by tjaap who cleaned up the database and ported it onto a Kindle, complete with a full instructable on how to build you own.

The Kindle Literary Clock by tjapp

Porting to MQTT

MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol designed for small sensors and mobile devices optimized for high-latency or unreliable networks. It’s perfect for the Internet of Things (IoT) applications where bandwidth and battery power are at a premium. It is also good for transmitting short text messages to display across multiple devices at the same time, as long as a device is connected to an MQTT broker, it will automatically display messages as they arrive.

As such, as have created a Python script to read the database and publish the time quotes every minute to our MQTT broker on the following address: /personal/ucfnap/timequote (its a little but like tuning a radio, but in the case of MQTT, subscribing to topics).

How It Works:

1. Data Collection: We are using the extensive CSV file containing literary quotes provided by the Kindle Literarty Clock project, where each quote is tagged with a specific minute of the day. To make it work with our script we tidied things up a little.

2. Publishing: This CSV is processed and published via the MQTT feed. Each minute, a new message is sent out containing the current time and the corresponding quote.

3. Subscription: Any device can subscribe to this MQTT feed to receive the quotes in real-time. This could be an e-ink screen, a smart display, or even a mobile application – ie at 10.47 a device would receive the following message:

10.07 am: In a meeting with Rod, Momo and Guy. We are rehearsing the final for the third time, with Rod and Guy taking the parts of the clients, when Rod’s secretary, Lorraine, bursts in. Book: I Don’t Know How She Does It

Applications

E-Ink Screens

Perhaps one of the most elegant implementations of this concept is using e-ink screens. E-ink displays are known for their paper-like readability and low power consumption, making them perfect for a literary clock. For an example of this, you can check out our detailed guide on setting up an e-ink screen with MQTT via our previous project THE: Time Headlines and Environmental Information here.

Literacy Clock using MQTY
Literary Clock using MQTT

Smart Displays and Mobile Apps

Beyond e-ink screens, this feed can be integrated into various smart displays and mobile applications. For example, we have intergrated it into our Home Assistant Dashboard, updating the time with a quote every minute. You could also add it to a HUB75 LED Matrix – below is our example of using an LED Matrix as a general data feed, but by simply changing the MQTT feed, it transforms into a Literary Clock.

 

Setting Up Your Literary Clock

Setting up your device is easy –

1. Choose Your Device: Select a device that can run an MQTT client. This could be an e-ink screen, a Raspberry Pi with a display, or a smartphone.

2. Install an MQTT Libary: There are numerous MQTT libraries available, we mainly use Paho.

3. Subscribe to the Feed: Point your client to the feed /personal/ucfnap/timequote 

Configure your client to display the received messages. Our open MQTT Broker is mqtt.cetools.org on Port 1883

Of course you may not want to bother with Raspberry Pi’s or other about with MQTT, or hack a Kindle – in which case, for those looking for a ready-made commercial version, check out the Author Clock.

The Rather Lovely Author Clock

The Author Clock is a beautifully designed literary clock that comes pre-loaded with thousands of quotes from a wide array of literary works. It’s an excellent choice for those who want to enjoy the literary clock experience without the need for a DIY setup.

With our MQTT messages, any device can now be simply converted into a Literary Clock.

MQTT Scroller

Make a Scrolling Hub75 Matrix Display using a Pimoroni Interstate75W and MQTT

By Making, Posts No Comments

There are many tutorials online on using an LED Matrix to display data—many of them require wiring up a screen, external power supplies, or flashing boards. We wanted to highlight a slightly more accessible way to get an LED Matrix—in our case, a Hub 75, 32×64 pixel up and running using an Interstate75W from Pimoroni. The benefit of the Interstate is that it plugs indirectly into the matrix and can power a single screen directly from the board.

Interstate75W

Interstate75W

We wanted a way to display any data we wanted on the screen with the screen lighting up and data scrolling up as it arrives and then turning off. To use this we use MQTT to load our data (a test feed is included in the scripts – which displays Time, News and Environmental Information) – see below for a demo:

 

We also incorporate manual brightness control and reconnecting for the MQTT for message handling, making it easy to update the display from anywhere. Setting up your own MQTT is beyond this post, but its easier than you may think and once you have one it can be used to display any data, from external feeds such as weather apis through to data from systems such as Home Assistant.

Features

  • Scrolling Text Messages: Display messages that scroll across the HUB75 LED matrix.
  • Manual Brightness Control: Adjust the brightness of the display manually.
  • MQTT Integration: Receive and display messages via MQTT.

Hardware Requirements

To get started, you’ll need the following hardware:

Software Requirements

You’ll also need the following software – all available from our GitHub

  • MicroPython
  • Required MicroPython libraries:
    • interstate75
    • mqtt_as
    • uasyncio

Setup

1. Clone the Repository

First, clone the project repository from GitHub, or just download the files directly:

“`sh
git clone https://github.com/yourusername/interstate75w-mqtt-display.git
cd interstate75w-mqtt-display
“`

2. Upload the Code

Next, upload the code to your microcontroller. You can use tools like Thonny or ampy to do this.

3. Configure WiFi and MQTT

Update the config.py file with your WiFi credentials, the MQTT details can also be updated if you have your own server, if not then leave them for our demo feed.

“`python
config = {
‘ssid’: ‘your_wifi_ssid’,
‘wifi_pw’: ‘your_wifi_password’,
‘server’: ‘mqtt_broker_address’,
‘user’: ‘mqtt_user’,
‘password’: ‘mqtt_password’,
‘port’: 1883,
‘keepalive’: 60,
}
“`

Usage

Run the Script

The script will automatically connect to WiFi and the MQTT broker, then start displaying messages – our MQQ feed displays messages approximatly every 3 minutes.

Constants and Initial Setup

The script defines constants for controlling the scrolling text speed, how long the screen says on for after displaying the message and brightness settings. It also initializes the Interstate75W object:

Constants for controlling scrolling text

BACKGROUND_COLOUR = (0, 0, 0) # Black background to turn off the screen
HOLD_TIME = 2.0
BLANK_SCREEN_TIME = 10.0
BUFFER_PIXELS = 2 # Increased buffer to ensure full scroll off
SCROLL_SPEED_LEVEL = 8 # Set the desired scrolling speed level (1 to 10)
SCROLL_SPEED = 1 / SCROLL_SPEED_LEVEL # Convert to a delay in seconds

Brightness settings

brightness = 50 # Initial brightness (0 to 100)

Do let us know if you make one – we would love to see images of your own set up and we hope this made it a little easier for anyone new looking to run an LED matrix using MQTT.

MQTT Weather Dashboard

Enhancing Live Weather Monitoring with MQTT and Chart.js

By data, Data Visualisation, Weather, Weather (Live), Weather Display No Comments

Introduction

Viewing real-time data from a personal weather station such as a Davis Vantage Pro, a Tempest, or an EcoWhitt device can be complex. However, the majority of systems that process weather data, such as Weather Display, Weewx, or CumlusMx, all have the ability to output MQTT data. This data can be used to display a real-time graph of the data, keeping you engaged with the latest weather updates, and supplemented with any other data which is MQTT-based.

With this in mind, we’ve developed a live weather monitoring dashboard as an illustrative example. This dashboard uses MQTT for real-time data updates and Chart.js for dynamic visualization. We’ve also included a visual indicator for connection status and a brief pulse effect to notify when new data arrives, enhancing the user experience.

MQTT Weather Dashboard

You can view it live at: https://finchamweather.co.uk/weathergraph.htm

The data populates as the page loads – we could of course back load it via a database link, but the aim was to simply use MQTT and have a graphing system that streams in data, its a work in progress but here is how we got it working:

Setting Up the Environment

Before we dive into the code, ensure you have the following libraries included in your HTML:

  • Paho MQTT: for MQTT protocol handling – our MQTT feed is open to use as a test, replace this with your own MQTT details in the main code.
  • Chart.js: for creating dynamic charts
  • Chart.js adapter for date-fns: for handling time scales in charts

Initial HTML Setup

We’ll start by setting up the basic HTML structure. This includes elements for displaying the connection status, forecast, weather statistics, and the weather chart.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <title>Live Weather Graph</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        #mqttStatus {
            margin-bottom: 20px;
            text-align: left;
            font-size: 1.2em;
        }
        .dot {
            height: 20px;
            width: 20px;
            border-radius: 50%;
            display: inline-block;
        }
        .green {
            background-color: green;
        }
        .red {
            background-color: red;
        }
        .orange {
            background-color: orange;
        }
        .pulse-once {
            animation: pulse-once 1s;
        }
        @keyframes pulse-once {
            0% { transform: scale(1); }
            50% { transform: scale(1.2); }
            100% { transform: scale(1); }
        }
        #forecast {
            margin-bottom: 20px;
            text-align: left;
            font-size: 1.2em;
            font-weight: bold;
        }
        #stats {
            display: flex;
            justify-content: center;
            gap: 20px;
            margin-bottom: 20px;
            font-size: 1.2em;
            font-weight: bold;
        }
        #stats div {
            padding: 10px 20px;
            border: 1px solid #ccc;
            border-radius: 8px;
            box-shadow: 2px 2px 12px #aaa;
            background-color: #f9f9f9;
        }
        canvas {
            border: 1px solid #ccc;
            box-shadow: 2px 2px 12px #aaa;
        }
    </style>
</head>
<body>
    <div id="mqttStatus"><span id="connectionDot" class="dot red"></span> mqtt: disconnected</div>
    <div id="forecast">Forecast: Loading...</div>
    <div id="stats">
        <div id="maxWindSpeed">Max Wind Speed: 0 mph</div>
        <div id="maxTemp">Max Temperature: 0 °C</div>
        <div id="minTemp">Min Temperature: 0 °C</div>
        <div id="maxPressure">Max Pressure: 0 mbar</div>
        <div id="minPressure">Min Pressure: 0 mbar</div>
    </div>
    <canvas id="weatherChart" width="800" height="400"></canvas>
</body>
</html>

Connecting to MQTT

Next, we set up the MQTT connection. The MQTT client will connect to the broker, subscribe to the necessary topics, and handle messages when they arrive.

// MQTT connection settings
var mqtt;
var reconnectTimeout = 2000;
var host = "mqtt.cetools.org";
var port = location.protocol === 'https:' ? 8081 : 8080;
var options = {
    timeout: 3,
    onSuccess: onConnect,
    onFailure: onFailure,
    useSSL: location.protocol === 'https:',
};
var clientID = "clientID" + parseInt(Math.random() * 100);

function updateConnectionStatus(status) {
    const dot = document.getElementById("connectionDot");
    if (status === "connected") {
        dot.className = "dot green";
        document.getElementById("mqttStatus").innerHTML = `<span class="dot green" id="connectionDot"></span> mqtt: connected`;
    } else if (status === "disconnected") {
        dot.className = "dot red";
        document.getElementById("mqttStatus").innerHTML = `<span class="dot red" id="connectionDot"></span> mqtt: disconnected`;
    } else if (status === "reconnecting") {
        dot.className = "dot orange";
        document.getElementById("mqttStatus").innerHTML = `<span class="dot orange" id="connectionDot"></span> mqtt: reconnecting`;
    }
}

function pulseDot() {
    const dot = document.getElementById("connectionDot");
    dot.classList.add("pulse-once");
    setTimeout(() => {
        dot.classList.remove("pulse-once");
    }, 1000); // Duration of the pulse-once animation
}

function onFailure(message) {
    console.log("Connection Attempt to Host " + host + " Failed: ", message.errorMessage);
    updateConnectionStatus("disconnected");
    setTimeout(MQTTconnect, reconnectTimeout);
}

function onConnect() {
    console.log("Connected ");
    updateConnectionStatus("connected");
    mqtt.subscribe("personal/ucfnaps/downhamweather/loop");
    mqtt.subscribe("personal/ucfnaps/eink/met");
}

function MQTTconnect() {
    console.log("Connecting to " + host + " on port " + port);
    updateConnectionStatus("reconnecting");
    mqtt = new Paho.MQTT.Client(host, port, clientID);
    mqtt.onMessageArrived = onMessageArrived;
    mqtt.onConnectionLost = function(responseObject) {
        if (responseObject.errorCode !== 0) {
            console.log("Connection Lost: " + responseObject.errorMessage);
            updateConnectionStatus("disconnected");
            setTimeout(MQTTconnect, reconnectTimeout);  // Attempt to reconnect
        }
    };
    mqtt.connect(options);
}

window.onload = function() {
    MQTTconnect();
}

Handling Incoming Messages

When messages arrive, we process the data and update the chart. We also update the connection dot to pulse briefly, indicating new data has been received.

let lastUpdate = Date.now();  // Initialize to current time
let firstUpdate = true;  // Flag to ensure first update happens immediately

let maxWindSpeed = 0;
let maxTemp = -Infinity;
let minTemp = Infinity;
let maxPressure = -Infinity;
let minPressure = Infinity;

function updateWindSpeed(windSpeed, timestamp) {
    weatherChart.data .labels.push(timestamp);
    weatherChart.data.datasets[0].data.push(windSpeed);

    // Update max wind speed
    if (windSpeed > maxWindSpeed) {
        maxWindSpeed = windSpeed;
        document.getElementById('maxWindSpeed').innerText = `Max Wind Speed: ${maxWindSpeed} mph`;
    }

    // Limit the number of data points to keep the chart responsive
    if (weatherChart.data.labels.length > 1440) { // Assuming 1 data point per minute, keep 24 hours of data
        weatherChart.data.labels.shift();
        weatherChart.data.datasets[0].data.shift();
    }

    weatherChart.update();
}

function updateOtherMetrics(temperature, solarRadiation, rainAmount, pressure, timestamp) {
    weatherChart.data.datasets[1].data.push({x: timestamp, y: temperature});
    weatherChart.data.datasets[2].data.push({x: timestamp, y: solarRadiation});
    weatherChart.data.datasets[3].data.push({x: timestamp, y: rainAmount > 0 ? rainAmount : null});
    weatherChart.data.datasets[4].data.push({x: timestamp, y: pressure});

    // Update max and min temperature
    if (temperature > maxTemp) {
        maxTemp = temperature;
        document.getElementById('maxTemp').innerText = `Max Temperature: ${maxTemp} °C`;
    }
    if (temperature < minTemp) { minTemp = temperature; document.getElementById('minTemp').innerText = `Min Temperature: ${minTemp} °C`; } // Update max and min pressure if (pressure > maxPressure) {
        maxPressure = pressure;
        document.getElementById('maxPressure').innerText = `Max Pressure: ${maxPressure} mbar`;
    }
    if (pressure < minPressure) { minPressure = pressure; document.getElementById('minPressure').innerText = `Min Pressure: ${minPressure} mbar`; } // Limit the number of data points to keep the chart responsive if (weatherChart.data.labels.length > 1440) { // Assuming 1 data point per minute, keep 24 hours of data
        weatherChart.data.datasets[1].data.shift();
        weatherChart.data.datasets[2].data.shift();
        weatherChart.data.datasets[3].data.shift();
        weatherChart.data.datasets[4].data.shift();
    }

    weatherChart.update();
}

function updateForecast(forecast) {
    document.getElementById('forecast').innerText = `Forecast: ${forecast}`;
}

function onMessageArrived(message) {
    console.log("Message Arrived: " + message.destinationName + " : " + message.payloadString);
    if (message.destinationName === "personal/ucfnaps/downhamweather/loop") {
        const data = JSON.parse(message.payloadString);
        const windSpeed = data['windSpeed_mph'];  // Adjust this key according to your data structure
        const temperature = data['outTemp_C'];  // Adjust this key according to your data structure
        const solarRadiation = data['radiation_Wpm2'];  // Adjust this key according to your data structure
        const rainAmount = data['dayRain_mm'];  // Adjust this key according to your data structure
        const pressure = data['pressure_mbar'];  // Adjust this key according to your data structure

        const nowTimestamp = new Date();

        // Update wind speed every time
        updateWindSpeed(windSpeed, nowTimestamp);

        if (firstUpdate || Date.now() - lastUpdate >= 60000) {
            // Update other metrics every minute
            updateOtherMetrics(temperature, solarRadiation, rainAmount, pressure, nowTimestamp);
            lastUpdate = Date.now();
            firstUpdate = false;  // Ensure subsequent updates follow the interval
        }

        // Pulse the dot when new data arrives
        pulseDot();
    } else if (message.destinationName === "personal/ucfnaps/eink/met") {
        const forecast = message.payloadString;
        updateForecast(forecast);
    }
}

Chart.js Setup

Now, let’s configure Chart.js to visualize the weather data. We will use multiple datasets to display wind speed, temperature, solar radiation, rain amount, and pressure.

// Chart.js setup
const ctx = document.getElementById('weatherChart').getContext('2d');
const weatherChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: [],  // Time labels
        datasets: [{
            label: 'Wind Speed (mph)',
            data: [],
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 3,
            fill: false,
            yAxisID: 'y-axis-1',
            tension: 0.1
        },
        {
            label: 'Temperature (°C)',
            data: [],
            borderColor: 'rgba(255, 99, 132, 1)',
            borderWidth: 3,
            fill: false,
            yAxisID: 'y-axis-2',
            tension: 0.1
        },
        {
            label: 'Solar Radiation (W/m²)',
            data: [],
            borderColor: 'rgba(255, 206, 86, 1)',
            borderWidth: 3,
            fill: false,
            yAxisID: 'y-axis-3',
            tension: 0.1
        },
        {
            label: 'Rain Amount (mm)',
            data: [],
            borderColor: 'rgba(54, 162, 235, 1)',
            borderWidth: 3,
            fill: false,
            yAxisID: 'y-axis-4',
            tension: 0.1
        },
        {
            label: 'Pressure (mbar)',
            data: [],
            borderColor: 'rgba(153, 102, 255, 1)',
            borderWidth: 3,
            fill: false,
            yAxisID: 'y-axis-5',
            tension: 0.1
        }]
    },
    options: {
        responsive: true,
        plugins: {
            legend: {
                position: 'top',
            },
            title: {
                display: true,
                text: 'Live Weather Data'
            },
            decimation: {
                enabled: true,
                algorithm: 'lttb',
                samples: 100,  // Adjust this value as needed for performance
            },
        },
        scales: {
            x: {
                type: 'time',
                time: {
                    unit: 'minute'
                },
                title: {
                    display: true,
                    text: 'Time'
                }
            },
            'y-axis-1': {
                type: 'linear',
                position: 'left',
                beginAtZero: true,
                title: {
                    display: true,
                    text: 'Wind Speed (mph)'
                }
            },
            'y-axis-2': {
                type: 'linear',
                position: 'right',
                beginAtZero: true,
                title: {
                    display: true,
                    text: 'Temperature (°C)'
                },
                grid: {
                    drawOnChartArea: false
                }
            },
            'y-axis-3': {
                type: 'linear',
                position: 'right',
                beginAtZero: true,
                title: {
                    display: true,
                    text: 'Solar Radiation (W/m²)'
                },
                grid: {
                    drawOnChartArea: false
                }
            },
            'y-axis-4': {
                type: 'linear',
                position: 'right',
                beginAtZero: true,
                title: {
                    display: true,
                    text: 'Rain Amount (mm)'
                },
                grid: {
                    drawOnChartArea: false
                }
            },
            'y-axis-5': {
                type: 'linear',
                position: 'right',
                beginAtZero: true,
                title: {
                    display: true,
                    text: 'Pressure (mbar)'
                },
                grid: {
                    drawOnChartArea: false
                }
            }
        },
        interaction: {
            intersect: false,
            mode: 'nearest',
        },
        elements: {
            line: {
                cubicInterpolationMode: 'monotone',
            },
        },
    }
});

Conclusion

By integrating MQTT and Chart.js, it is possible to create a dynamic and real-time weather monitoring dashboard. The connection status indicator provides immediate feedback on the connection state, and the pulsing effect when new data arrives enhances user experience by visually notifying them of updates.

This setup can be further extended by adding more datasets, customizing the chart’s appearance, or integrating additional sensors. The data of course couple be from any feed, but real-time weather monitoring provides a good example of how IoT and web technologies can be combined to create realtime dashboards.

Creating Art Like Weather Forecast Images with DALL·E 3 and API Data

By Art, Making, Weather

Introduction

The Met Office, as the national meteorological service for the United Kingdom, provides valuable weather data through its API called DataPoint. This API caters to a wide range of users, including professionals, scientists, students, and amateur developers. One of its notable features is the availability of text-based regional weather forecasts.

However, traditional text-to-image systems often struggle with accurately representing descriptive language. Users often find themselves navigating the complexities of prompt engineering to achieve their desired visual output. However, things are rapidly chaning with the use of AI and OpenAI’s latest release, DALL·E 3, simplifies this process by generating images that align with the provided text.

In this blog post, we’ll explore how to combine Met Office weather forecasts with DALL·E 3 via its API using Python. Our goal? To create captivating landscape imagery that reflects the weather conditions described in the forecast. Our images are then uploaded to our webserver, using FTP for viewing online. If you simply want to create an image, then you can leave the FTP section out.

The Workflow

  1. Met Office Data Retrieval:
    • We fetch the weather forecast data from the Met Office DataPoint API. Specifically, we focus on today’s weather conditions. We are using the UK metoffice, but it could be any weather api, from any country, that returns forecast text.
  2. Creating the Image Prompt:
    • We construct an image prompt that encapsulates the essence of the weather. Our prompt includes the landscape type (e.g., “rural Norfolk landscape”) and the specific weather details obtained from the Met Office. The landscape type can be edited accordingly
  3. DALL·E 3 Image Generation:
    • Leveraging OpenAI’s DALL·E 3 model, we generate an image based on the provided prompt. The image should realistically depict cloud formations, sunlight, precipitation, and wind effects, all while capturing the mood suggested by the weather.
  4. FTP Upload:
    • Finally, we upload the generated image to an FTP server for public access.

We run the script every 12 hours (ours runs on a Raspberry Pi) with the images archived on the websever – the gallery below shows some of the images from the last few months:

The full code can be seen below, with the latest version available via our GitHub repository.

# Import necessary libraries
import ftplib
import requests
from PIL import Image
import io
from bs4 import BeautifulSoup
from datetime import datetime

# Get Met Office Data and Strip Today/Tonight Text
url = 'http://datapoint.metoffice.gov.uk/public/data/txt/wxfcs/regionalforecast/xml/512?key=YOURMETOFFICEAPIKEY'
document = requests.get(url)
soup = BeautifulSoup(document.content, "lxml-xml")

# Extract today's weather forecast
todayraw = soup.find_all("Paragraph", attrs={'title': 'Today:'})
todaystr = str(todayraw)
today = (todaystr.replace('[<Paragraph title="Today:">', '').replace('</Paragraph>', '').replace(']', ''))

# Set up OpenAI API key
from openai import OpenAI
client = OpenAI(api_key='YourOpenAIAPIKey')

# FTP server details
ftp_server = 'YourFTPServer'
ftp_username = 'FTPUserName'
ftp_password = 'FTPPassword'

# Specify the type of Norfolk landscape (e.g., rural, coastal, urban)
landscape_type = "rural Norfolk landscape"  # Change this as per your preference

# Create the image prompt
image_prompt = (
    f"A photorealistic single, cohesive scene image of a {landscape_type}, showcasing the following weather conditions: {today}. "
    "The image should realistically depict elements like cloud formations, sunlight or lack thereof, any precipitation, and wind effects. "
    "It should convey the atmosphere and mood suggested by the weather, with appropriate lighting and color tones. No numerical data or text should be included, just a pure visual representation of the weather in the landscape."
)

# Generate an image using OpenAI's DALL·E
def generate_image(prompt):
    response = client.images.generate(prompt=prompt, n=1, model="dall-e-3", quality="standard", style="vivid", size="1792x1024")
    image_url = response.data[0].url
    return image_url

# Function to generate a datestamp
def get_datestamp():
    return datetime.now().strftime("%Y%m%d%H%M%S")

# Modified FTP upload function
def upload_to_ftp(image_url, remote_path):
    with ftplib.FTP(ftp_server) as ftp:
        ftp.login(user=ftp_username, passwd=ftp_password)
        response = requests.get(image_url)
        image = Image.open(io.BytesIO(response.content))
        datestamp = get_datestamp()
        original_image = io.BytesIO()
        image.save(original_image, format='JPEG')
        original_image.seek(0)
        ftp.storbinary(f'STOR {remote_path}_{datestamp}.jpeg', original_image)
        resized_image = image.resize((1792, 1024))
        jpeg_image = io.BytesIO()
        resized_image.save(jpeg_image, format='JPEG')
        jpeg_image.seek(0)
        ftp.storbinary('STOR public_html/image.jpeg', jpeg_image)
        resized_image = image.resize((800, 480))
        jpeg_image = io.BytesIO()
        resized_image.save(jpeg_image, format='JPEG')
        jpeg_image.seek(0)
        ftp.storbinary('STOR public_html/image_eink.jpeg', jpeg_image)

# Generate the image and upload it
image_url = generate_image(image_prompt)
upload_to_ftp(image_url, 'public_html/image.jpeg')

Breaking down the steps in the code –

  1. Importing Libraries: We start by importing necessary Python libraries for HTTP requests, image processing, FTP interaction, and data parsing.
  2. Fetching Weather Data: The script retrieves weather data from the Met Office using an API key. It extracts relevant information using BeautifulSoup and cleans up the output to get the weather forecast for today.
  3. OpenAI API Key: The OpenAI API key is set up to use the DALL·E model for image generation.
  4. FTP Server Details: FTP server credentials (server address, username, and password) are provided for image uploads.
  5. Weather Details and Landscape Type: The weather description obtained earlier is stored in weather_details . A landscape type (e.g., “rural Norfolk landscape”) is specified.
  6. Image Prompt Creation: The image_prompt  is constructed by combining weather details and landscape type. It describes the desired image.
  7. Image Generation with DALL·E: The  generatrate_image function uses DALL·E to create and return an image based on the prompt.
  8. Datestamp Generation: A datestamp is generated for archiving purposes.
  9. FTP Image Upload: The upload_to_ftp function connects to the FTP server, downloads the generated image, and uploads it to specific directories – this is optional, only of use if you are hosting your images.
  10. Running the Script: We run the script every 12 hours, using a cron job on a Raspberry Pi. We additional send it to our iPhone and our FrameTV, so the latest image is viewable either as a widget or on screen

Finally we also display it on our iPad, using the FrameIT app – this auto updates the image when a new one is uploaded to the web server.

 

Dall-E 3 image on an iPad using Frame-IT

Dall-E 3 image on an iPad using Frame-IT

Do let us know if you create you own AI based weather images using data inputs – it would be interesting to see how different landscapes and counties compare.

Close Menu

About Salient

The Castle
Unit 345
2500 Castle Dr
Manhattan, NY

T: +216 (0)40 3629 4753
E: hello@themenectar.com