From Heathkit vacuum tubes to first-gen Arduino LEDs, electronics has been my lifelong passion. Though not my profession, I've journeyed from analog circuits to microcontrollers, hacking Arduinos since they appeared. This blog shares that journey—the successes and struggles. If you're a fellow hobbyist, a curious beginner, or just love to tinker, you're in the right place. Welcome!
Saturday, August 16, 2025
Tune your audio system to your room
Thursday, August 14, 2025
What saved info I use to configure Google gemini
Google's Gemini AI engine allows you to configure how Gemini interprets your prompts and how you want it to behave. I've seen a huge improvement in the responses I am getting after adding an eclectic set of information. To learn how this information is used see "How is this information is used".
In order to set your own saved info, click on"Settings and Help" at the bottom of the left pane. You can then click the [+ Add] button to add a new information section.
Here are my information sections I've created as an example of the type of information you might want to include.
- I love listening to science oriented podcasts.
- I currently program in python. I use pycharm professional as my IDE. I subscribe to the ai addon. I also subscribe to Google one with the ai option. Always adhere to industry standards for python. Follow all python PEP recommendations at https://peps.python.org/. Add type hints to all generated code. Create a test for all generated code using the unittest suite. I am an intermediate level programmer. You are an advanced level programmer.
- I prefer middle eastern and Asian cuisine. I enjoy spicy and hot flavors.
- I am an avid audiophile. My favorite transducer are of the planar magnetic variety. Magnepan speakers and hifiman headphones. My primary source today is streaming on tidal. I enjoy all genres.
- I have 56 years of programming experience. I have experience with assembler, basic, fortran, cobol, C, C++ and python. I wrote my first program, PDP8 assembler, at the age of 10 (1969). I currently enjoy python.
Friday, August 8, 2025
How to call c functions from python and how to write and prepare the c functions.
Calling C Functions from Python: The Underlying Concept
To call C functions from Python, the general approach involves compiling your C code into a shared library, which Python can then dynamically load and interact with.
- Rationale: C is frequently used for computationally intensive libraries due to its thin abstraction layer from hardware and low overhead, allowing for efficient implementations of algorithms and data structures. Many higher-level languages, including Python, support calling C library functions for performance-critical or hardware-interacting aspects.
- Mechanism: Python is designed to be extended with C code, and it's also possible to embed a scripting language like Python within a C program. When extending Python, you instruct it to dynamically load a C library or module, making the C entry points visible as functions within the Python environment. This process requires the ability to transfer data between C's and Python's distinct type systems. C extensions are commonly used in the Python ecosystem.
Writing and Preparing C Functions for Python Interoperability
To prepare your C functions for use with Python, you will typically write them as part of a shared library.
-
- C is a general-purpose, imperative procedural language. It forms the foundation for much of GNU/Linux software, including the kernel itself. The information about C is also applicable to C++ programs, as C++ is largely a superset of C, and C language APIs are the common interface (lingua franca) in GNU/Linux.
- C source files contain declarations and function definitions. These are typically saved with
.cor.h(for header files) extensions.
-
Defining C Functions
- C functions typically accept parameters and can return values. For instance, pthreads functions in GNU/Linux use
void*for parameters and return types to allow for generic data passing to and from threads. - For C++ Code: If you are writing the C functions in C++ that will be part of a shared library and accessed from other languages (like Python), you should declare those functions and variables with the
extern "C"linkage specifier. This is crucial because it prevents the C++ compiler from "mangling" function names, ensuring that the function's name remains as you defined it (e.g.,fooinstead of a complex, encoded name). A C compiler does not mangle names.
- C functions typically accept parameters and can return values. For instance, pthreads functions in GNU/Linux use
-
Compiling into a Shared Library
- The GNU C Compiler (GCC) is the standard compiler for C code in Linux.
- Step 1: Compile Source Files to Position Independent Code (PIC): The first step is to compile your C source files into object code that is "position-independent." This is necessary for shared libraries. You use the
-coption for compilation only and the-fPICoption for position-independent code generation.- Example command:
gcc -c -fPIC your_c_file.c. This will generateyour_c_file.o.
- Example command:
- Step 2: Create the Shared Library: Once you have your object files (
.o), you link them together to create the shared library, which typically has a.soextension (for "shared object"). Use the-sharedoption for this.- Example command:
gcc -shared -o libyourlibrary.so your_c_file.o.
- Example command:
- Making the Library Discoverable: For your Python program to find and load this shared library at runtime, it must be located in a directory that the system's dynamic linker searches.
- You can set the
LD_LIBRARY_PATHenvironment variable to include the directory containing your shared library. - Alternatively, you can place the shared library in a standard system library directory, such as
/libor/usr/lib, and then runldconfigto update the dynamic linker's cache.
- You can set the
-
Crucial Considerations for Robust and Secure C Code When writing C functions, especially those intended for interoperability, adhering to robust and secure programming practices is vital:
- Error Handling:
- Always check the return values of system calls and library functions to detect failures. A return value of
-1often indicates failure for system calls. - In case of failure, the
errnovariable (from<errno.h>) will contain an error number, which can be converted to a descriptive error string usingstrerror(). Theperror()function can also be used to print a generic error message. - For functions that allocate resources (like memory or file descriptors), ensure they are deallocated if an error occurs later in the function.
- Always check the return values of system calls and library functions to detect failures. A return value of
- Memory Management:
- C programs manage memory through static allocation, automatic allocation (on the stack), and dynamic allocation (on the heap). When dynamically allocating memory using functions like
malloc()orrealloc(), always check if the returned pointer isNULL, which indicates allocation failure. Abort the program if memory allocation fails. - Remember to
free()dynamically allocated memory when it's no longer needed to prevent memory leaks.
- C programs manage memory through static allocation, automatic allocation (on the stack), and dynamic allocation (on the heap). When dynamically allocating memory using functions like
- Input Validation and Buffer Overflow Prevention:
- Avoid "dangerous" C library functions that do not perform bounds checking, such as
strcpy(),strcat(),sprintf(), andgets(). These are common sources of buffer overflow vulnerabilities. - Use safer alternatives that allow you to specify buffer sizes, like
strncpy(),strncat(),snprintf(), andfgets(). Be aware thatstrncpy()andstrncat()have specific semantics (e.g.,strncpy()may not null-terminate if the source is too long). Consider usingstrlcpy()andstrlcat()if available, as they offer a less error-prone interface. - Be extremely cautious with the
scanf()family of functions, particularly when using%swithout specifying a maximum length, as this can lead to buffer overruns.
- Avoid "dangerous" C library functions that do not perform bounds checking, such as
- Compiler Warnings and Static Analysis:
- Always compile your C code with as many compiler warnings enabled as possible. For GCC, use at least the
-Walland-pedanticflags. Strive to eliminate all warnings, as they often indicate potential bugs or risky constructions. - Use ANSI prototypes in separate header (
.h) files for all your functions to ensure type correctness across your program. - For functions that take format strings (like
printf()-style functions), GCC's__attribute__((format(printf, parameter_number_of_format_string, first_variadic_parameter_number)))can be used in your header files to enable the compiler to check format string accuracy.
- Always compile your C code with as many compiler warnings enabled as possible. For GCC, use at least the
- Error Handling:
-
Including Header Files
- Ensure you include all necessary header files for the functions and system calls you use. Common headers include:
<stdio.h>for standard I/O functions likeprintf().<stdlib.h>for general utilities likemalloc()andexit().<unistd.h>for many process manipulation functions and low-level I/O.<sys/types.h>,<sys/stat.h>,<fcntl.h>for file operations and system calls.<signal.h>for signal handling.<pthread.h>for POSIX threads functions.
- Ensure you include all necessary header files for the functions and system calls you use. Common headers include:
By following these guidelines, you can effectively write and prepare C functions that can be called from Python, leveraging the strengths of both languages.
Once I have my shared library of compiled c code, how do I include it in my python program and call it?
To include your shared library of compiled C code in a Python program and call functions from it, you'll need to leverage Python's capabilities for dynamic loading. This process involves ensuring your C code is correctly compiled into a shared library, making that library discoverable by the system, and then using Python's interface to dynamically load it and call its functions.
Here's a detailed breakdown:
1. Compiling and Preparing Your C Code (Recap)
Firstly, ensure your C (or C++) code is compiled into a shared library, often referred to as a shared object (.so) file on GNU/Linux systems.
- Compile to Position-Independent Code (PIC): The C source files that will form your shared library must be compiled into position-independent code. This is essential because the library's functions may be loaded at different memory addresses in different programs. You achieve this using the
-fPICoption with the GNU C Compiler (GCC).- Example:
gcc -c -fPIC your_c_file.c.
- Example:
- Create the Shared Library: After compiling your source files into object files (
.o), you link them together to create the shared library. You use the-sharedoption for this. Shared libraries typically have a.soextension and their names almost always begin withlib.- Example:
gcc -shared -o libyourlibrary.so your_c_file.o another_c_file.o.
- Example:
extern "C"for C++ Functions: If you are writing your functions in C++ that are intended to be accessed from other languages like Python, you must declare those functions and variables with theextern "C"linkage specifier. This is crucial because C++ compilers "mangle" function names (encoding extra information like parameter types into the name), which would prevent Python from finding the function by its original name. A C compiler, however, does not mangle names. A common practice is to wrap declarations in header files with#ifdef __cplusplus extern "C" { #endifand#ifdef __cplusplus } #endif.
2. Making the Shared Library Discoverable
For your Python program to load and use your compiled C shared library, the system's dynamic linker needs to be able to find it at runtime.
LD_LIBRARY_PATHEnvironment Variable: One common solution is to set theLD_LIBRARY_PATHenvironment variable. This variable is a colon-separated list of directories that the system searches for shared libraries before looking in the standard directories. This is particularly useful during development or for non-standard library installations.- Example:
export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH(if the library is in the current directory).
- Example:
- Standard System Directories and
ldconfig: Alternatively, you can place your shared library in one of the standard system library directories, such as/libor/usr/lib. After placing a new library in these directories, you should runldconfigto update the dynamic linker's cache, which helps the system find libraries efficiently. -Wl,-rpathLinker Option: Another approach is to embed the library's path directly into your executable at link time using the-Wl,-rpathoption. This tells the system where to search for required shared libraries when the program is run.
3. Including and Calling from Python
Python, being extensible with C code, provides mechanisms to dynamically load shared libraries and call functions defined within them [576, 577, previous turn]. While the provided sources do not explicitly name a specific Python module for this (like ctypes), they extensively describe the underlying C-level functions that Python would use: dlopen and dlsym.
The general concept is as follows:
- Dynamic Loading: Python will perform an action equivalent to the
dlopenfunction. Thedlopenfunction opens a shared library, returning a handle that can then be used to refer to the loaded library. This allows code to be loaded at runtime without being explicitly linked at compile time. - Symbol Resolution: Once the library is loaded, Python needs to find the specific function you wish to call. This is analogous to the
dlsymfunction in C, which takes the library handle and the name of the function (or variable) as a string and returns its address. - Function Call: With the address of the C function obtained, Python can then invoke it.
Conceptual Steps in a Python Program:
Although the source material doesn't provide direct Python code examples for dlopen/dlsym wrappers, the process would conceptually involve:
- Importing a Module for Foreign Function Interface: Python has modules (like
ctypesin a real-world scenario, but not mentioned in sources) designed for calling C functions. You would import such a module. - Loading Your Shared Library: You would then call a function (conceptually like
dlopen) to load yourlibyourlibrary.sofile. This would provide a handle to the loaded library. - Defining the C Function Signature: To properly call a C function, Python needs to know its signature (return type and argument types). This is crucial for correctly passing data between Python and C's distinct type systems. Python's foreign function interface (FFI) modules handle this by allowing you to specify the C function's prototype.
- Accessing and Calling the Function: You would then access the C function by its name (conceptually like
dlsym) through the loaded library handle. Once accessed and its signature defined, you can call it just like a regular Python function, passing Python objects as arguments. The FFI module handles the conversion of Python types to C types and vice versa.
Example (Conceptual Python Usage):
Assuming you have libmyutility.so containing a function int add(int a, int b); compiled as described above:
# Conceptual Python code (not actual syntax for a specific Python module like ctypes,
# but illustrates the underlying dlopen/dlsym principle as described in sources)
# Step 1: Load the shared library (conceptually using dlopen)
# In reality, a Python FFI module would abstract this.
my_library_handle = python_load_library("libmyutility.so")
if my_library_handle:
# Step 2: Access the C function (conceptually using dlsym)
# You would also need to specify argument types and return type
add_function = python_get_function(my_library_handle, "add")
# Step 3: Call the C function
result = add_function(10, 20)
print(f"The result of add(10, 20) is: {result}")
# Step 4: Unload the library when done (conceptually using dlclose)
python_unload_library(my_library_handle)
else:
print("Failed to load the library.")
This approach allows you to leverage the performance and low-level capabilities of C from the flexibility and high-level environment of Python.
Wednesday, August 6, 2025
How to effectively start a Python script that relies on a virtual environment from a cron job.
Understanding the Challenge with Cron Jobs and Virtual Environments
When a cron job executes a command, it typically runs in a very minimal environment, often without the full set of environment variables that are present in your interactive shell session (the shell you use when you log in). This is crucial because Python virtual environments rely on setting specific environment variables, particularly modifying the PATH variable, to ensure that the correct Python interpreter and its associated packages are used.
In a typical interactive session, you activate a virtual environment using a command like source /path/to/venv/bin/activate. This command modifies the current shell's environment variables to point to the virtual environment's Python interpreter and its installed libraries. However, a cron job executes commands in a new, non-interactive shell for each scheduled task, and this shell doesn't automatically inherit the environment changes made by your interactive session's activate script.
If you simply try to run your Python script (e.g., python /path/to/your_script.py) in a cron job without properly addressing the virtual environment, the cron job's shell will likely use the system's default Python interpreter, which will not have access to the packages installed in your virtual environment. This would lead to "module not found" errors or unexpected behavior.
The Solution: Explicitly Calling the Virtual Environment's Python
The most robust way to ensure your Python script runs with its virtual environment from a cron job is to explicitly call the Python interpreter within that virtual environment using its absolute path. This bypasses the need to source the activation script, as the virtual environment's Python interpreter is self-contained and knows how to find its own packages.
Here are the steps and examples:
Step 1: Identify the Absolute Paths
You need to know the full, absolute path to:
- Your Python script: For example,
/home/youruser/my_project/my_script.py. - Your virtual environment's Python interpreter: If your virtual environment is located at
/home/youruser/my_project/venv, then the Python interpreter inside it would typically be/home/youruser/my_project/venv/bin/python.
Step 2: Choose Your Cron Job Method
You have two primary ways to set up the cron job: directly in the crontab file, or by using a wrapper shell script. The wrapper script approach is generally recommended for better logging, error handling, and readability.
Method A: Direct Entry in Crontab (Simplest for single commands)
You can add the command directly to your crontab file. To edit your crontab, you typically use the command crontab -e.
The format for a crontab entry is minute hour day_of_month month day_of_week user command. Since you are likely running this under your own user, the "user" field will be implied if you edit your personal crontab (using crontab -e), but it's explicitly required in /etc/crontab.
Here's an example crontab entry that runs your script daily at 9:00 AM:
0 9 * * * /home/youruser/my_project/venv/bin/python /home/youruser/my_project/my_script.py >> /home/youruser/my_project/cron.log 2>&1
Let's break this down:
0 9 * * *: This specifies the schedule (0 minutes past 9 AM, every day of the month, every month, every day of the week)./home/youruser/my_project/venv/bin/python: This is the absolute path to the Python interpreter within your virtual environment. This is key to ensuring the correct environment is used./home/youruser/my_project/my_script.py: This is the absolute path to your Python script.>> /home/youruser/my_project/cron.log 2>&1: This redirects both standard output and standard error to a log file [This is general shell practice for cron jobs, as they don't have an interactive terminal to show output directly]. This is highly recommended for debugging, as cron jobs run silently unless there's an issue or output is redirected.
Method B: Using a Wrapper Shell Script (Recommended)
For more complex scripts, or if you need to perform additional setup (like changing directories or setting specific environment variables) before running your Python script, a wrapper shell script is a cleaner and more robust approach.
Step 2.1: Create the Wrapper Shell Script
Create a new file, for example, run_my_python_job.sh, in a suitable location (e.g., /home/youruser/scripts/).
#!/bin/bash
# --- Configuration ---
# Absolute path to your Python virtual environment
VENV_PATH="/home/youruser/my_project/venv"
# Absolute path to your Python script
SCRIPT_PATH="/home/youruser/my_project/my_script.py"
# Absolute path for the log file
LOG_FILE="/home/youruser/my_project/cron.log"
# --- Script Execution ---
# Optional: Change to the script's directory (useful if your script
# relies on relative paths for other files)
cd "$(dirname "$SCRIPT_PATH")"
# Execute the Python script using the virtual environment's Python interpreter
# All output (stdout and stderr) is appended to the log file.
"$VENV_PATH/bin/python" "$SCRIPT_PATH" >> "$LOG_FILE" 2>&1
# Optional: You could also explicitly activate the virtual environment
# if your script implicitly relies on other environment variables set by 'activate'.
# This would typically be placed before the Python execution line.
# source "$VENV_PATH/bin/activate"
# python "$SCRIPT_PATH" >> "$LOG_FILE" 2>&1
# deactivate # 'deactivate' is not strictly necessary for non-interactive scripts
Step 2.2: Make the Wrapper Script Executable
You must give the shell script execute permissions.
chmod +x /home/youruser/scripts/run_my_python_job.sh
Step 2.3: Schedule the Wrapper Script in Crontab
Now, add an entry to your crontab that calls this wrapper script:
0 9 * * * /home/youruser/scripts/run_my_python_job.sh
Important Considerations:
- Absolute Paths: Always use absolute (full) paths for everything in cron jobs: the Python interpreter, your script, and any log files or other resources your script needs to access. The cron environment's
PATHvariable is often very limited. - Logging: Redirecting output (
>> /path/to/logfile.log 2>&1) is crucial. Without it, you won't see anyprintstatements or error messages if your script fails. This is your primary debugging tool for cron jobs. - Permissions: Ensure your script, the virtual environment, and the log file locations have the correct read/write/execute permissions for the user that the cron job will run as.
- Environment Variables within Script: If your Python script itself relies on custom environment variables that are not set by the virtual environment activation (e.g., API keys, database connection strings), you will need to explicitly set these within your wrapper script or directly in the cron command line using
VAR_NAME=value /path/to/command. Variables set withexportin a parent shell are inherited by child processes.
By following these steps, you can reliably run your Python scripts from cron jobs, ensuring they execute within their intended virtual environments.
Friday, July 18, 2025
Using AI to retrieve genre and synopsis from book titles
Programmer Taps AI to Tame Sprawling Digital Book Collection
TORONTO – A local programmer has developed a novel approach to a uniquely 21st-century problem: how to bring order to a sprawling personal library of several thousand e-book files. By integrating Google's Gemini AI, he has created a system to automatically retrieve the genre and synopsis for each title, transforming a disorganized collection into a meticulously cataloged digital library.
The project began with the challenge of files named with inconsistent author and title information. Using the epub-metadata library for Python, the developer first built a tool to reliably extract the title from each file. The next step was to enrich this basic data. "I wanted to be able to use AI and Google to get the genre and synopsis," he stated, outlining his goal.
To accomplish this, he turned to Google's AI Studio to acquire an API key, granting his script access to the powerful Gemini Pro model. In a proof-of-concept script, he demonstrated how the AI could be queried with a book's title to return structured metadata. The system is designed for a streamlined workflow; the Python script securely accesses the API key from an environment variable, a standard practice for managing credentials in software development.
This innovative solution showcases the growing accessibility of advanced artificial intelligence, allowing individuals to build powerful, custom tools to manage personal data and automate complex organizational tasks. The programmer's initial tests have proven successful, paving the way for the full-scale cataloging of his extensive collection.
Title: the emperors new mind
"""
Google Books API Integration Script
This script uses Google's Gemini AI API to retrieve genre and synopsis information
for books based on their titles. It constructs a query using the book title and
the Google Books URL format, then uses the Gemini AI model to generate a response
with genre and synopsis information.
Requirements:
- Google Gemini API key set as environment variable 'GEMINI_API_KEY'
- google-generativeai package installed
Usage:
python test_google_book_api.py
(The script will prompt for a book title)
"""
import pprint # For pretty-printing the API response
from google import genai # Google's Generative AI library
# The client gets the API key from the environment variable `GEMINI_API_KEY`.
# Google Books URL format: https://www.google.ca/books/edition/title
def aibook(title):
"""
Query the Gemini AI model for book genre and synopsis information.
This function creates a Gemini AI client, constructs a prompt with the book title
and desired output format, then returns the generated text response.
Args:
title (str): The title of the book to query
Returns:
str: The formatted response text containing genre and synopsis information
Example output format:
Genre: Fiction, Science Fiction, Dystopian
Synopsis: A story about...
"""
# Initialize the Gemini AI client
client = genai.Client()
# Generate content using the Gemini model with a formatted prompt
response = client.models.generate_content(
model="gemini-2.5-flash", # Using the Gemini 2.5 Flash model for fast responses
contents=f"""
book url https://www.google.ca/books/edition/{title}/
provide the genre and synopsis of the book
use the format
line 1 Genre: genre1, genre2, etc
line 2 Synopsys: the synopsys
"""
)
# Return the text portion of the response
return response.text
# Execute only if run as a script, not when imported as a module
if __name__ == "__main__":
# Prompt the user for a book title, pass it to the aibook function,
# and pretty-print the results
pprint.pp(aibook(input('Title: ')))
Saturday, July 5, 2025
You Are Not Expected to Understand This - How 26 Lines of Code Changed the World
Saturday, June 28, 2025
Raspberry Pi Weather Clock
I have a 2 line by 16 character LCD display with a I2C interface. I wanted to use it as a clock and date display which also could display the current weather.
![]() |
| Date and Time Display |
![]() |
| Weather Display |
![]() |
| Rapsberry Pi 5 clock/weather |
You will need an API key to open weather openweatherapi. Put your api key in an .env file as openweathermapapikey=yourkey. The python script uses the dotenv library to read this and use your API key. This is a free service with usage limitations. You will need to change the parameters to the open weather API to use you city and units.
#!/usr/bin/env python3"""lcd_weatherclock.py - A program that displays a clock on an LCD screenand shows weather information for 10 seconds when a button is pressed."""from LCD import LCDimport datetimeimport timeimport requestsimport osfrom dotenv import load_dotenvfrom gpiozero import Buttonimport threading# Load environment variables from .env fileload_dotenv()# Initialize LCDlcd = LCD(2, 0x27, True)lcd.clear()# Initialize button with debouncebutton = Button(21, pull_up=True, bounce_time=0.2) # 200ms debounce time# Global variablesdisplay_mode = "clock" # Can be "clock" or "weather"weather_timer = Nonedef get_etobicoke_weather():"""Get current weather for Etobicoke, Ontario, Canada using OpenWeatherMap API"""try:# OpenWeatherMap API endpoint for current weatherurl = "https://api.openweathermap.org/data/2.5/weather"# Get APP ID for weather apiOPENWEATHERMAPAPIKEY = os.getenv("openweathermapapikey")# Parameters for Etobicoke, Ontario, Canadaparams = {"q": "Etobicoke,Ontario,CA","units": "metric", # For Celsius"appid": OPENWEATHERMAPAPIKEY # API key}# Make the API requestresponse = requests.get(url, params=params)# Check if the request was successfulif response.status_code == 200:# Parse the JSON responseweather_data = response.json()# Extract relevant informationtemperature = weather_data["main"]["temp"]condition = weather_data["weather"][0]["main"]humidity = weather_data["main"]["humidity"]return {"temperature": temperature,"condition": condition,"humidity": humidity}else:return {"error": f"API Error: {response.status_code}"}except Exception as e:return {"error": f"Error: {str(e)}"}def display_weather(lcd):"""Display weather data on the LCD"""global display_mode# Set display mode to weatherdisplay_mode = "weather"# Show loading messagelcd.clear()lcd.message("Weather...")# Get weather dataweather_data = get_etobicoke_weather()lcd.clear()if "error" in weather_data:lcd.message("Weather Error")lcd.message(weather_data["error"][:16], 2) # Truncate to fit LCD widthelse:# Format and display weather informationtemp_str = f"Temp: {weather_data['temperature']:.1f}C"cond_str = f"{weather_data['condition']} {weather_data['humidity']}%"lcd.message(temp_str)lcd.message(cond_str, 2)# Set a timer to switch back to clock after 10 secondsglobal weather_timerif weather_timer:weather_timer.cancel()weather_timer = threading.Timer(10.0, switch_to_clock)weather_timer.start()def switch_to_clock():"""Switch display back to clock mode"""global display_modedisplay_mode = "clock"# The main loop will update the display on the next iterationdef display_clock(lcd, force_update=False):"""Display clock on the LCD"""now = datetime.datetime.now()currenttime = now.strftime("%H:%M:%S")currentdate = now.strftime("%d-%m-%Y")# Only update if time/date changed or force update is requestedif force_update or currenttime != display_clock.lasttime or currentdate != display_clock.lastdate:lcd.clear()lcd.message('%02d:%02d:%02d' % (now.hour, now.minute, now.second), 1)lcd.message('%02d-%02d-%04d' % (now.day, now.month, now.year), 2)display_clock.lasttime = currenttimedisplay_clock.lastdate = currentdate# Initialize static variables for display_clock functiondisplay_clock.lasttime = ''display_clock.lastdate = ''def button_pressed():"""Function called when button is pressed"""global display_modeif display_mode == "clock":display_weather(lcd)# Assign callback function to button press eventbutton.when_pressed = button_pressed# Display welcome messagelcd.message("Weather Clock")lcd.message("Starting...", 2)time.sleep(2)lcd.clear()# Main looptry:while True:if display_mode == "clock":display_clock(lcd)time.sleep(0.1)except KeyboardInterrupt:# Clean up on exitif weather_timer:weather_timer.cancel()lcd.clear()print("\nExiting program")







