Content of the page:

https://github.com/PA3EFR?tab=repositories

Cluster warnings. Sound with CMD window.

    • pip install playsound

import socket
import re
import winsound  # Voor Windows; gebruik 'playsound' voor andere systemen.

# Configuratie: pas deze waarden aan
HOST = "www.db0erf.de"  # Vervang door de host-IP van het DXCluster van lijst https://www.dxcluster.info/telnet/dxcluster_up.htm
PORT = 41113                    # Vervang door de poort van het DXCluster
CALLSIGN = "PA3EFR"     # Vervang door je callsign of gebruikersnaam
patterns = [r"B/", r"WWFF", r"POTA", r"COTA", r"SOTA", r"BOTA"]

def play_alarm():
    """Speelt een alarmsignaal af met vijf tonen."""
    frequencies = [1500, 1600, 1700, 1800, 1900, 2000, 2200, 2400, 2600, 2800]  # Frequenties in Hertz
    duration = 100  # Duur van elke toon in milliseconden

    for frequency in frequencies:
        winsound.Beep(frequency, duration)

def main():
    # Maak verbinding met het DXCluster
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        print(f"Verbinden met {HOST}:{PORT}...")
        s.connect((HOST, PORT))
        print("Verbonden. Verstuur login...")

        # Verstuur logincommando
        login_command = f"{CALLSIGN}\n"  # Vaak is het gewoon de callsign gevolgd door een newline
        s.sendall(login_command.encode("utf-8"))
        print(f"Ingelogd als: {CALLSIGN}")
        printnr = 0
        try:
            while True:
                # Ontvang data van de socket
                data = s.recv(1024).decode("utf-8", errors="replace")
                data = data.upper()
                if not data:
                    break  # Verbinding is gesloten door de server

                # Toon ontvangen data (optioneel)
                #print(data)


                # Controleer op "B/" in de ontvangen tekst
                for pattern in patterns:
                    if re.search(pattern, data):
                        print(f"Alarm: '{pattern}'")
                        print (data)
                        play_alarm()

                #if re.search(r"\bSOTA\b", data):
                #    print("Alarm: 'B/' gevonden!")
                #    play_alarm()

        except KeyboardInterrupt:
            print("Script gestopt door gebruiker.")
        except Exception as e:
            print(f"Fout opgetreden: {e}")

if __name__ == "__main__":
    main()

 

Maybe you want to have a warning on specific words in the comments. In that case use this flexible Cluster Warning script where the script is asking for terms. Is provided with a space, all cluster texts will get through.

import socket
import re
import winsound # Voor Windows; gebruik 'playsound' voor andere systemen.

# Configuratie: pas deze waarden aan
HOST = "www.db0erf.de"
PORT = 41113
CALLSIGN = "<callsign>"

# Vraag de gebruiker om zoekpatronen
user_input = input("Voer de zoekpatronen in, gescheiden door komma's (Spatie voor alles of Enter voor default: B/, BOTA): ")
patterns = [pattern.strip() for pattern in user_input.split(",")] if user_input else ["B/", "BOTA"]
print(f"Gebruikte zoekpatronen: {patterns}")

def play_alarm():
"""Speelt een alarmsignaal af met tonen."""
frequencies = [1500] # Frequenties in Hertz
duration = 40 # Duur van elke toon in milliseconden

for frequency in frequencies:
winsound.Beep(frequency, duration)

frequencies = [2000]
duration = 100

for frequency in frequencies:
winsound.Beep(frequency, duration)

def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
print(f"Verbinden met {HOST}:{PORT}...")
s.connect((HOST, PORT))
print("Verbonden. Verstuur login...")

login_command = f"{CALLSIGN}\n"
s.sendall(login_command.encode("utf-8"))
print(f"Ingelogd als: {CALLSIGN}")

try:
while True:
data = s.recv(1024).decode("utf-8", errors="replace").upper()
if not data:
break

for pattern in patterns:
if re.search(pattern, data):
print(f"Alarm: '{pattern}'")
print(data)
play_alarm()

except KeyboardInterrupt:
print("Script gestopt door gebruiker.")
except Exception as e:
print(f"Fout opgetreden: {e}")

if __name__ == "__main__":
main()

 

 

 

Because the Thin Client uses its HRI-200 as audio control for the WIRES-X, this cluster warning system requires selecting the built-in loudspeaker. Which is known as device 15. You can have a list printed at the start of the program by removing a few #.

  • pip install playsound
  • pip install pyaudio
  • pip install numpy

import socket
import re
import winsound  # Voor Windows; gebruik 'playsound' voor andere systemen.

# Configuratie: pas deze waarden aan
HOST = "www.db0erf.de"  # Vervang door de host-IP van het DXCluster van lijst https://www.dxcluster.info/telnet/dxcluster_up.htm
PORT = 41113                    # Vervang door de poort van het DXCluster
CALLSIGN = "PA3EFR"     # Vervang door je callsign of gebruikersnaam
patterns = [r"B/", r"WWFF", r"POTA", r"COTA", r"SOTA", r"BOTA"]

import pyaudio
import numpy as np

# Initialiseer PyAudio
p = pyaudio.PyAudio()

# Lijst van apparaten
#print("Beschikbare apparaten:")                                     # Deze 4 regels activeren om een lijst van devices te zien
#for i in range(p.get_device_count()):
#    info = p.get_device_info_by_index(i)
#    print(f"{i}: {info['name']}")

# Specificeer apparaat-ID van ingebouwde luidsprekers
default_device_id = 15  # Pas dit aan op basis van bovenstaande lijst


def play_alarm():
    """Speelt een toon af als alarmgeluid."""
    fs = 44100  # Samplefrequentie
    duration = 3  # Duur van de toon in seconden
    frequency = 440.0  # Frequentie van de toon in Hertz

    # Genereer een sinusgolf
    t = np.linspace(0, duration, int(fs * duration), endpoint=False)
    tone = (np.sin(2 * np.pi * frequency * t) * 0.5).astype(np.float32)

    # Initialiseer PyAudio
    p = pyaudio.PyAudio()

    try:
        # Start een audio-stream op het opgegeven apparaat
        stream = p.open(format=pyaudio.paFloat32,
                        channels=1,
                        rate=fs,
                        output=True,
                        output_device_index= default_device_id)

        # Speel de toon af
        stream.write(tone.tobytes())
        stream.stop_stream()
        stream.close()
    finally:
        p.terminate()

def main():
    # Maak verbinding met het DXCluster
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        print(f"Verbinden met {HOST}:{PORT}...")
        s.connect((HOST, PORT))
        print("Verbonden. Verstuur login...")

        # Verstuur logincommando
        login_command = f"{CALLSIGN}\n"  # Vaak is het gewoon de callsign gevolgd door een newline
        s.sendall(login_command.encode("utf-8"))
        print(f"Ingelogd als: {CALLSIGN}")
        printnr = 0
        try:
            while True:
                # Ontvang data van de socket
                data = s.recv(1024).decode("utf-8", errors="replace")
                data = data.upper()
                if not data:
                    break  # Verbinding is gesloten door de server

                # Toon ontvangen data (optioneel)
                #print(data)


                # Controleer op pattern in de ontvangen tekst
                for pattern in patterns:
                    if re.search(pattern, data):
                        print(f"Alarm: '{pattern}'")
                        print (data)
                        play_alarm()

        except KeyboardInterrupt:
            print("Script gestopt door gebruiker.")
        except Exception as e:
            print(f"Fout opgetreden: {e}")

if __name__ == "__main__":
    main()

 

Voor het toplicht gebruiken we een Arduino. Hierbij de stappen van het instellen van deze Arduino naar een andere call.

  1. Installeer de Arduino IDE op je PC/laptop (dit is een applicatie om Arduino's te kunnen programmeren).
  2. Installeer Digispark (Default - 16.5 MHz) van https://raw.githubusercontent.com/digistump/arduino-boards-index/refs/heads/master/package_digistump_index.json (Preference verwijzing)
  3. Zet de schakelaar achterop de rotor klok in de middenstand en sluit alleen de USB-A to USB-A kabel aan de rotorklok aan. Steek deze nog niet in de laptop.
  4. Start Arduino en pas de Sketch.ino (zie hieronder) aan naar de nieuwe call (regel 134, bold highlight)
  5. Druk linksboven in de IDE op het pijltje naar rechts (upload) en wacht tot onderin het scherm de melding komt om "binnen 60 seconden het device aan te sluiten". Druk binnen deze 60 seconden de USB-A to USB-A kabel in de laptop.
  6. Na 4 seconden is de INO-file geupload en klaar voor gebruik. USB-A to USB-A kabel mag nu verwijderd worden.
  7. Check met de schakelaar naar beneden of de call juist wordt geseind. 

//
// Simple Arduino Morse Beacon
// Written by Mark VandeWettering K6HX
// Email: This email address is being protected from spambots. You need JavaScript enabled to view it.
// 
// This code is so trivial that I'm releasing it completely without 
// restrictions.  If you find it useful, it would be nice if you dropped
// me an email, maybe plugged my blog @ https://brainwagon.org or included
// a brief acknowledgement in whatever derivative you create, but that's
// just a courtesy.  Feel free to do whatever.
//


struct t_mtab { char c, pat; } ;

struct t_mtab morsetab[] = {
    {'.', 106},
  {',', 115},
  {'?', 76},
  {'/', 41},
  {'A', 6},
  {'B', 17},
  {'C', 21},
  {'D', 9},
  {'E', 2},
  {'F', 20},
  {'G', 11},
  {'H', 16},
  {'I', 4},
  {'J', 30},
  {'K', 13},
  {'L', 18},
  {'M', 7},
  {'N', 5},
  {'O', 15},
  {'P', 22},
  {'Q', 27},
  {'R', 10},
  {'S', 8},
  {'T', 3},
  {'U', 12},
  {'V', 24},
  {'W', 14},
  {'X', 25},
  {'Y', 29},
  {'Z', 19},
  {'1', 62},
  {'2', 60},
  {'3', 56},
  {'4', 48},
  {'5', 32},
  {'6', 33},
  {'7', 35},
  {'8', 39},
  {'9', 47},
  {'0', 63}
} ;

#define N_MORSE  (sizeof(morsetab)/sizeof(morsetab[0]))

#define SPEED  (8)
#define DOTLEN  (1200/SPEED)
#define DASHLEN  (3*(1200/SPEED))

const int LEDpin = 3;    // the pin that the LED is attached to


void
dash()
{
  digitalWrite(LEDpin, HIGH) ;
  delay(DASHLEN);
  digitalWrite(LEDpin, LOW) ;
  delay(DOTLEN) ;
}

void
dit()
{
  digitalWrite(LEDpin, HIGH) ;
  delay(DOTLEN);
  digitalWrite(LEDpin, LOW) ;
  delay(DOTLEN);
}

void
send(char c)
{
  int i ;
  if (c == ' ') {
    Serial.print(c) ;
    delay(7*DOTLEN) ;
    return ;
  }
  for (i=0; i<N_MORSE; i++) {
    if (morsetab[i].c == c) {
      unsigned char p = morsetab[i].pat ;
      Serial.print(morsetab[i].c) ;

      while (p != 1) {
          if (p & 1)
            dash() ;
          else
            dit() ;
          p = p / 2 ;
      }
      delay(4*DOTLEN) ;
      return ;
    }
  }
  /* if we drop off the end, then we send a space */
  Serial.print("?") ;
}

void
sendmsg(char *str)
{
  while (*str)
    send(*str++) ;
  Serial.println("");
}

void setup() {
  pinMode(LEDpin, OUTPUT) ;
  digitalWrite(LEDpin, LOW) ;
  Serial.begin(9600) ;
  Serial.println("Simple Arduino Morse Beacon v0.0") ;
  Serial.println("Written by Mark VandeWettering <This email address is being protected from spambots. You need JavaScript enabled to view it.>") ;
  Serial.println("Check out my blog @ https://brainwagon.org") ;
  Serial.println("") ;
}

void loop() {
  sendmsg("PA3EFR/J") ;
  delay(3000) ;
}

 

 

Voor het vergelijken van een ADIF file (format: *.adi) en een Excel file (format: *.xslx) heb ik een script gemaakt om vast te stellen of een callsign en een bijbehorende datum in beide files voorkomt. Het resultaat wordt in de terminal weergegeven en als output file (format: *.xlsx) weggeschreven. Alle files moeten in dezelfde directory worden samengevoegd.

To compare an ADIF file (format: *.adi) and an Excel file (format: *.xslx) I created a script to determine whether a callsign and an associated date appear in both files. The result is displayed in the terminal and written as an output file (format: *.xlsx). All files must be merged into the same directory.

pip install tabulate

import pandas as pd
import re
from tabulate import tabulate
from datetime import datetime

def read_adif(file_path):
"""Leest een ADIF-bestand en extraheert relevante velden."""
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()

records = []
qsos = content.strip().split('<eor>')
for qso in qsos:
call = re.search(r'<call:(\d+)>([^<]*)', qso)
date = re.search(r'<qso_date:(\d+)>([^<]*)', qso)
time = re.search(r'<time_on:(\d+)>([^<]*)', qso)
band = re.search(r'<band:(\d+)>([^<]*)', qso)
mode = re.search(r'<mode:(\d+)>([^<]*)', qso)

if call:
# Verkrijg de pure callsign zonder prefix of suffix
raw_call = call.group(2).strip()

# Verwijder prefix of suffix (maximaal 2 tekens voor en na de '/')
clean_call = re.sub(r'^[a-zA-Z0-9]{1,2}/|/([a-zA-Z0-9]{1,2})$', '', raw_call)

# Converteer de datum naar het juiste formaat "YYYYMMDD"
qso_date = date.group(2).strip() if date else ''
if qso_date:
try:
# Als de datum in "YYYY-MM-DD HH:MM:SS" formaat is, zet het dan om naar "YYYYMMDD"
qso_date_obj = datetime.strptime(qso_date, "%Y-%m-%d %H:%M:%S")
qso_date = qso_date_obj.strftime("%Y%m%d") # Zet het om naar "YYYYMMDD"
except ValueError:
pass # Als de conversie mislukt, blijf de originele datum behouden

records.append({
'call': clean_call,
'qso_date': qso_date,
'time_on': time.group(2).strip() if time else '',
'band': band.group(2).strip() if band else '',
'mode': mode.group(2).strip() if mode else ''
})

return pd.DataFrame(records)

def read_xlsx(file_path, sheet_name='Hunter'):
"""Leest een Excel-bestand en haalt relevante kolommen op zonder kolomnamen."""
df = pd.read_excel(file_path, sheet_name=sheet_name, dtype=str, header=None)
df = df.iloc[:, [2, 4, 5, 6, 7]] # Kolommen C, E, F, G, H
df.columns = ['call', 'qso_date', 'time_on', 'band', 'mode']

# Zet de date om van "YYYY-MM-DD HH:MM:SS" naar "YYYYMMDD"
df['qso_date'] = pd.to_datetime(df['qso_date'], format='%Y-%m-%d %H:%M:%S', errors='coerce').dt.strftime('%Y%m%d')

# Verwijder prefix en suffix van de call (maximaal 2 tekens voor en na de '/')
df['call'] = df['call'].apply(
lambda x: re.sub(r'^[a-zA-Z0-9]{1,2}/|/([a-zA-Z0-9]{1,2})$', '', str(x)) if isinstance(x, str) else '')

return df.dropna()

def compare_files(adif_file, xlsx_file, output_file):
"""Vergelijkt de ADIF- en XLSX-bestanden en slaat de overeenkomsten op."""
df_adif = read_adif(adif_file)
df_xlsx = read_xlsx(xlsx_file)

# Merge op basis van zowel 'call' als 'qso_date'
matched_df = df_adif.merge(df_xlsx, on=['call', 'qso_date'], how='inner')


# Tel het aantal records in het matched DataFrame
num_records = len(matched_df)
print(f"\nAantal overeenkomsten gevonden: {num_records}")

# Druk de matched DataFrame af in tabelvorm met tabulate
print("\nGecombineerde gegevens (tabelvorm):")
print(tabulate(matched_df, headers='keys', tablefmt='pretty', showindex=False))
print(f"\nAantal overeenkomsten gevonden: {num_records}")
# Opslaan naar een nieuwe Excel-file
matched_df.to_excel(output_file, index=False)
print(f"\nResultaten opgeslagen in {output_file}")

# Bestanden invoeren
adif_file = input("Voer de naam van het outputbestand in [default=ADIF.adi]: ") or "ADIF.adi"
xlsx_file = input("Voer de naam van het outputbestand in [default=WWBOTA.xlsx]: ") or "WWBOTA.xlsx"
output_file = input("Voer de naam van het outputbestand in [default=OUTPUT.xlsx]: ") or "OUTPUT.xlsx"


compare_files(adif_file, xlsx_file, output_file)

 

Voor het POTA award van PA heb ik een tweetal scripts geschreven: een sorteer script om het laatste volgnummer te vinden en een toewijzing van tekst aan een JPG.

import pandas as pd

# Bestandspad
file_name = "AwardGrantsOverview.xlsx"

# Inlezen van de Excel-bestand
data = pd.read_excel(file_name)

# Controleren of de benodigde kolommen bestaan
required_columns = ['Color', 'Type', 'Number']
if not all(col in data.columns for col in required_columns):
raise ValueError(f"De Excel file moet de volgende kolommen bevatten: {', '.join(required_columns)}")

# Sorteren op Color, Type en Number
data_sorted = data.sort_values(by=['Color', 'Type', 'Number'], ascending=[True, True, True])

# Groeperen op Color en Type en het hoogste nummer vinden
grouped = data_sorted.groupby(['Color', 'Type'])
highest_numbers = grouped['Number'].max()

# Printen van de resultaten
print("Hoogste nummers voor elke Color-Type combinatie:\n")
for (color, type_), number in highest_numbers.items():
print(f"\t{color}, {type_}, Hoogste Number: {number}")

# Optioneel: opslaan van de gesorteerde data in een nieuw Excel-bestand
data_sorted.to_excel("AwardGrantsOverview.xlsx", index=False)
print("\n\n> Gesorteerde data is opgeslagen in 'AwardGrantsOverview.xlsx'.")
from PIL import Image, ImageDraw, ImageFont
import os
from datetime import datetime
from openpyxl import load_workbook

import Sort

# Function to validate input with repeated prompts
def get_valid_input(prompt, valid_options):
while True:
value = input(prompt).strip().lower()
if value in valid_options:
return value
print(f"Invalid input. Choose from {', '.join(valid_options)}.")


# Function to draw text with a border (outline)
def draw_text_with_border(draw, text, position, font, text_color, border_color, border_thickness):
# Draw border by rendering text multiple times around the original position
x, y = position
for offset_x in range(-border_thickness, border_thickness + 1):
for offset_y in range(-border_thickness, border_thickness + 1):
# Skip the center position (to avoid drawing the text multiple times in the exact same spot)
if offset_x == 0 and offset_y == 0:
continue
# Draw the border (outline)
draw.text((x + offset_x, y + offset_y), text, font=font, fill=border_color)

# Draw the main text over the border (in the desired text color)
draw.text(position, text, font=font, fill=text_color)


# Ask for input details
color_input = get_valid_input("\nEnter the color (options: b for Bronze, s for Silver, g for Gold): ", ["b", "s", "g"])

# Map the color input to the full color name
if color_input == "b":
color = "bronze"
elif color_input == "s":
color = "silver"
elif color_input == "g":
color = "gold"

# Ask for the activator/hunter option
type_option = get_valid_input("\n\t Is it an activator (a) or a hunter (h)? (options: a, h): ", ["a", "h"])

# Map single-letter input to full words
type_option = "activator" if type_option == "a" else "hunter"

name = input("\n\t Enter the name to be added (e.g., 'Erwin - PA3EFR'): ").strip()
serial_number = input("\n\t\t\t\t\t Enter the serial number: ").strip()

# Get today's date
today_date = datetime.now().strftime("%Y-%m-%d")

# Construct the correct input file path based on color and activator/hunter
input_directory = color.capitalize() # Directory name: Bronze, Silver, Gold
input_image_name = f"{color.capitalize()}{type_option.capitalize()}.jpg"
input_image_path = os.path.join(input_directory, input_image_name)

# Check if the file exists
if not os.path.exists(input_image_path):
print(f"The file '{input_image_path}' does not exist. Please check the name and try again.")
exit()

# Create the output file name
sanitized_name = name.replace(" ", "-").replace("/", "-")
output_pdf_name = f"{serial_number}_{color}_{type_option}_{sanitized_name}.pdf"
output_pdf_path = os.path.join(input_directory, output_pdf_name)

try:
# Open the image
img = Image.open(input_image_path)

# Create a drawing object
draw = ImageDraw.Draw(img)

# Choose a font and size (adjust the path if necessary)
font_size_name = int(150) # Fixed font size
font_size_number = int(100) # Fixed font size
font_size_date = int(80) # Fixed font size

try:
font_name = ImageFont.truetype("Bodoni Bd BT Bold.ttf", font_size_name)
font_number = ImageFont.truetype("arial.ttf", font_size_number)
font_date = ImageFont.truetype("arial.ttf", font_size_date)
except IOError:
# Fallback to a default font if arial.ttf is not available
font_name = ImageFont.load_default()
font_number = ImageFont.load_default()
font_date = ImageFont.load_default()

# Calculate the position of the name
text_name_bbox = draw.textbbox((0, 0), name, font=font_name) # (left, top, right, bottom)
text_name_width = text_name_bbox[2] - text_name_bbox[0]
name_position = (
200, # pix from left
650 # pix from the top (140 pixels at 300 DPI)
)

# Calculate the position of the serial number
text_number_bbox = draw.textbbox((0, 0), serial_number, font=font_number) # (left, top, right, bottom)
text_number_width = text_number_bbox[2] - text_number_bbox[0]
number_position = (
3200, # pixels from the left edge
505 # pix from the top (140 pixels at 300 DPI)
)

# Calculate the position of the date
text_date_bbox = draw.textbbox((0, 0), today_date, font=font_date) # (left, top, right, bottom)
date_position = (
3005, # pixels from the left edge
396 # pix from the top (140 pixels at 300 DPI)
)

# Add the name with a white border and black text
draw_text_with_border(draw, name, name_position, font_name, (0, 0, 0), (255, 255, 255), 5) # Border thickness = 5

# Add the serial number with a white border and black text
draw_text_with_border(draw, serial_number, number_position, font_number, (0, 0, 0), (255, 255, 255), 5)

# Add the date with a white border and red text
draw_text_with_border(draw, today_date, date_position, font_date, (0, 0, 0), (255, 255, 255), 5)

# Convert the image to RGB (necessary for PDF export)
if img.mode in ("RGBA", "P"): # Check if conversion is needed
img = img.convert("RGB")

# Save the image as a PDF in the correct directory
img.save(output_pdf_path, "PDF")
print(f"\n\n> The image with added text has been saved as '{output_pdf_path}'.")

# Now add the information to the Excel file
try:
# Load the existing workbook
workbook = load_workbook("AwardGrantsOverview.xlsx")
sheet = workbook.active

# Add a new row with the data
sheet.append([color.capitalize(), type_option.capitalize(), name, serial_number, output_pdf_name, today_date])

# Save the changes to the Excel file
workbook.save("AwardGrantsOverview.xlsx")
print("> The data has been added to 'AwardGrantsOverview.xlsx'.")

except Exception as e:
print(f"An error occurred while updating the Excel file: {e}")

except Exception as e:
print(f"An error occurred: {e}")

 

 

Er kwam een behoefte om de informatie van een excel sheet aan te vullen met fotos en beschrijvingen. Het volgende script maakt van een XLSx werkblad per rij een DOCx pagina met aan het hoofd van die pagina, de informatie van de rij uit het XLSx bestand. Default input is PABOTA_lijst.xlsx, default output is Nieuwe_description.docx

There was a need to supplement the information in an Excel sheet with photos and descriptions. The following script turns an XLSx worksheet into a DOCx page per row, with the information of the row from the XLSx file at the head of that page. Default input is PABOTA_list.xlsx, default output is Nieuw_description.docx

 

from openpyxl import load_workbook
from docx import Document
from docx.shared import Pt


# Functie om een pagina-einde toe te voegen
def add_page_break(doc):
doc.add_page_break()


# Excel bestand lezen
def read_xlsx(file_path):
wb = load_workbook(file_path)
sheet = wb.active
rows = list(sheet.iter_rows(values_only=True))
return rows


# Word-document maken
def create_word_from_xlsx(excel_file, word_file):
rows = read_xlsx(excel_file)
doc = Document()

for row in rows:
# Voeg rijgegevens toe als koptekst op de pagina
header = doc.add_paragraph()
header.alignment = 1 # Centreren
run = header.add_run(" | ".join(str(cell) for cell in row if cell is not None))
run.bold = True
run.font.size = Pt(14)

# Pagina-einde als er meer rijen zijn
add_page_break(doc)

# Opslaan van het Word-document
doc.save(word_file)

# Standaardbestanden instellen
default_excel = "PABOTA_lijst.xlsx"
default_word = "Nieuwe_description.docx"

# Bestandsnamen opvragen, maar standaardwaarden aanbieden
excel_bestand = input(f"Geef het pad van het Excel-bestand op ({default_excel} als standaard): ") or default_excel
word_bestand = input(f"Geef de naam van het uitvoerbestand op ({default_word} als standaard): ") or default_word

# Uitvoeren
create_word_from_xlsx(excel_bestand, word_bestand)
print(f"\n\nWord-document '{word_bestand}' is succesvol aangemaakt.\n\n")

 

 

With the given Lifetime Excel sheet from the WWBOTA organisation I needed to count the unique PABOTA numbers for the PABOTA award. The script reads column D to select rows with PABOTA and then check the unique bunker numbers.

import pandas as pd


def count_unique_b_words(file_path):
# Laad het Excel-bestand en selecteer het werkblad 'HUNTER'
df = pd.read_excel(file_path, sheet_name='Hunter', engine='openpyxl')

# Print de inhoud van cellen C3 en C7
cell_C3 = df.iloc[1, 2] # C3 -> rij 2, kolom 2 (0-gebaseerde index)
cell_C7 = df.iloc[5, 2] # C7 -> rij 6, kolom 2

print(f"\n\tDit script leest het logboek WWBOTA van \t\t{cell_C3}")
print(f"\n\tHet aantal gewerkte WWBOTA stations zonder PABOTA is \t{cell_C7}")

# Filter de rijen vanaf rij 10 (index 9) waarin kolom D het woord "PABOTA" bevat
df_filtered = df.iloc[9:][df.iloc[9:, 3] == "PABOTA"] # Kolom D heeft index 3

# Selecteer unieke waarden in kolom B (index 1) en tel deze
unique_count = df_filtered.iloc[:, 1].nunique()

# Selecteer unieke waarden in kolom C (index 2) die voldoen aan de voorwaarde
unique_c_values = df_filtered.iloc[:, 2].dropna().unique()
# Converteer lijst naar een string zonder brackets
unique_c_values_str = ', '.join(map(str, unique_c_values))

return unique_count, unique_c_values_str


def count_unique_a_words(file_path):
# Laad het werkblad 'Activator'
df_activator = pd.read_excel(file_path, sheet_name='Activator', engine='openpyxl')

# Selecteer unieke waarden in kolom B (index 1) vanaf rij 11 (index 10)
unique_activator_count = df_activator.iloc[10:, 1].nunique()

return unique_activator_count


# Gebruik het script
file_path = "WWBOTA-Lifetime-Hunter-Tracker-1_0.xlsx" # Vervang dit met het juiste pad naar je Excel-bestand

result, unique_c_values_str = count_unique_b_words(file_path)

print(f"\n\tAantal Nederlandse bunkers die ik heb gejaagd is \t{result}\n")

result_activator = count_unique_a_words(file_path)
print(f"\tAantal Nederlandse bunkers door mijzelf geactiveerd is \t{result_activator}\n\n")

print(f"\n\tCalls die de gelogde PABOTA bunkers hebben geactiveerd:\t{unique_c_values_str}\n\n")