Compare commits
10 commits
b551a09b7e
...
c5c0081976
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c0081976 | ||
|
|
e4792d157e | ||
|
|
2bc38e42ac | ||
|
|
b0e92b7647 | ||
|
|
0a8de6215a | ||
|
|
84bc0ec7db | ||
|
|
f75cc6f475 | ||
|
|
ac8ecbdda4 | ||
|
|
334b5f22eb | ||
|
|
cc56f81cc1 |
5 changed files with 6343 additions and 45 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
# ignore our binary Excel file
|
||||||
|
*.xlsx
|
||||||
|
|
@ -22,7 +22,7 @@ pd.set_option("display.max_rows", 150)
|
||||||
|
|
||||||
|
|
||||||
# point to our various input files - adjust this to your environment
|
# point to our various input files - adjust this to your environment
|
||||||
data_dir = '/home/ed/syncthing/ham-radio/ARES-RACES/Nets--PNW_ARES_DMR_Weekly_Net'
|
data_dir = '/home/syncthing/ham-radio/ARES-RACES/Nets--PNW_ARES_DMR_Weekly_Net'
|
||||||
filename = 'Check_In_Data.xlsx'
|
filename = 'Check_In_Data.xlsx'
|
||||||
data_file = os.path.join(data_dir, filename)
|
data_file = os.path.join(data_dir, filename)
|
||||||
output_file = os.path.join(data_dir, "PNW_Digital_ARES_EMCOMM_Weekly_Net_Check_In_Form.txt")
|
output_file = os.path.join(data_dir, "PNW_Digital_ARES_EMCOMM_Weekly_Net_Check_In_Form.txt")
|
||||||
|
|
@ -88,18 +88,18 @@ checkins_df["Date"] = pd.to_datetime(checkins_df["Date"]).dt.strftime('%Y-%m-%d'
|
||||||
callinfo_df = pd.read_excel(xls, "Call_Data", dtype=str)
|
callinfo_df = pd.read_excel(xls, "Call_Data", dtype=str)
|
||||||
#print("callinfo_df:")
|
#print("callinfo_df:")
|
||||||
#print(callinfo_df)
|
#print(callinfo_df)
|
||||||
hh_dir_df = pd.read_excel(xls, "Hamshack_Hotline", dtype=str)
|
hoip_dir_df = pd.read_excel(xls, "hoip_subscribers", dtype=str)
|
||||||
#print("hh_dir_df:")
|
#print("hoip_dir_df:")
|
||||||
#print(hh_dir_df)
|
#print(hoip_dir_df)
|
||||||
|
|
||||||
# build dictionary of HH phone numbers
|
# build dictionary of HH phone numbers
|
||||||
hh_phone_dict = {"Callsign" : "HH Number"}
|
hoip_phone_dict = {"Callsign" : "HH Number"}
|
||||||
for i,row in hh_dir_df.iterrows():
|
for i,row in hoip_dir_df.iterrows():
|
||||||
callsign = row.iloc[1]
|
callsign = row.iloc[1]
|
||||||
hh_num = row.iloc[7]
|
hoip_num = row.iloc[0]
|
||||||
# we only add the hh number first seen for a unique callsign
|
# we only add the hoip number first seen for a unique callsign
|
||||||
if callsign not in hh_phone_dict.keys():
|
if callsign not in hoip_phone_dict.keys():
|
||||||
hh_phone_dict.update({callsign : hh_num})
|
hoip_phone_dict.update({callsign : hoip_num})
|
||||||
|
|
||||||
# fill in any missing data (NAN's) with default text
|
# fill in any missing data (NAN's) with default text
|
||||||
callinfo_df.fillna({'Name' : ''}, inplace=True)
|
callinfo_df.fillna({'Name' : ''}, inplace=True)
|
||||||
|
|
@ -127,27 +127,27 @@ for i,row in checkins_df.iterrows():
|
||||||
# build dictionary of call info indexed by callsign
|
# build dictionary of call info indexed by callsign
|
||||||
call_data_dict = {}
|
call_data_dict = {}
|
||||||
for i,row in callinfo_df.iterrows():
|
for i,row in callinfo_df.iterrows():
|
||||||
# row of data is call, name, state, district, county, affiliation, hh_num
|
# row of data is call, name, state, district, county, affiliation, hoip_num
|
||||||
# dictionary becomes: { call: [name[0], state[1], district[2], county[3], affiliation[4]], hh_num[5]}
|
# dictionary becomes: { call: [name[0], state[1], district[2], county[3], affiliation[4]], hoip_num[5]}
|
||||||
callsign = row.iloc[0]
|
callsign = row.iloc[0]
|
||||||
if callsign in hh_phone_dict.keys():
|
if callsign in hoip_phone_dict.keys():
|
||||||
hh_num = hh_phone_dict[callsign]
|
hoip_num = hoip_phone_dict[callsign]
|
||||||
else:
|
else:
|
||||||
hh_num = ""
|
hoip_num = ""
|
||||||
call_data_dict.update({callsign:[row.iloc[1],row.iloc[2],row.iloc[3],row.iloc[4],row.iloc[5],hh_num]})
|
call_data_dict.update({callsign:[row.iloc[1],row.iloc[2],row.iloc[3],row.iloc[4],row.iloc[5],hoip_num]})
|
||||||
|
|
||||||
# build checkin_form_dict dictionary
|
# build checkin_form_dict dictionary
|
||||||
checkin_form_dict = blank_checkin_form_dict
|
checkin_form_dict = blank_checkin_form_dict
|
||||||
for call in call_data_dict.keys():
|
for call in call_data_dict.keys():
|
||||||
#print("Looking at:", call)
|
#print("Looking at:", call)
|
||||||
#if call in hh_phone_dict.keys():
|
#if call in hoip_phone_dict.keys():
|
||||||
#print("Call "+call+" has HH VOIP #"+hh_phone_dict[call])
|
#print("Call "+call+" has HOIP #"+hoip_phone_dict[call])
|
||||||
call_state = call_data_dict[call][1]
|
call_state = call_data_dict[call][1]
|
||||||
call_dist = call_data_dict[call][2]
|
call_dist = call_data_dict[call][2]
|
||||||
call_county = call_data_dict[call][3]
|
call_county = call_data_dict[call][3]
|
||||||
call_affil = call_data_dict[call][4]
|
call_affil = call_data_dict[call][4]
|
||||||
call_hh_num = call_data_dict[call][5]
|
call_hoip_num = call_data_dict[call][5]
|
||||||
#print("State, Dist, Cnty, Affil, HH Num: ", call_state, call_dist, call_county, call_affil, call_hh_num)
|
#print("State, Dist, Cnty, Affil, HH Num: ", call_state, call_dist, call_county, call_affil, call_hoip_num)
|
||||||
|
|
||||||
# sanity check the data - error if not found in dictionary keys...
|
# sanity check the data - error if not found in dictionary keys...
|
||||||
# print("Verifying at: ", call, call_state, call_dist, call_county)
|
# print("Verifying at: ", call, call_state, call_dist, call_county)
|
||||||
|
|
@ -218,8 +218,8 @@ with open(output_file,"w") as outfile:
|
||||||
checkin_str = ""
|
checkin_str = ""
|
||||||
if checkin_count > 0:
|
if checkin_count > 0:
|
||||||
outfile.write(" "+call+", "+call_name+checkin_str)
|
outfile.write(" "+call+", "+call_name+checkin_str)
|
||||||
if call in hh_phone_dict.keys():
|
if call in hoip_phone_dict.keys():
|
||||||
outfile.write(", HH VOIP #"+hh_phone_dict[call])
|
outfile.write(", HOIP #"+hoip_phone_dict[call])
|
||||||
if call_affil != "":
|
if call_affil != "":
|
||||||
outfile.write(", "+call_affil+"\n")
|
outfile.write(", "+call_affil+"\n")
|
||||||
else:
|
else:
|
||||||
|
|
@ -237,8 +237,8 @@ with open(output_file,"w") as outfile:
|
||||||
checkin_str = ""
|
checkin_str = ""
|
||||||
if checkin_count > 0:
|
if checkin_count > 0:
|
||||||
outfile.write(" "+call+", "+call_data_dict[call][0]+checkin_str)
|
outfile.write(" "+call+", "+call_data_dict[call][0]+checkin_str)
|
||||||
if call in hh_phone_dict.keys():
|
if call in hoip_phone_dict.keys():
|
||||||
outfile.write(", HH VOIP #"+hh_phone_dict[call])
|
outfile.write(", HOIP #"+hoip_phone_dict[call])
|
||||||
affil = call_data_dict[call][4]
|
affil = call_data_dict[call][4]
|
||||||
if affil != '':
|
if affil != '':
|
||||||
outfile.write(", "+affil+"\n")
|
outfile.write(", "+affil+"\n")
|
||||||
|
|
@ -263,8 +263,8 @@ with open(output_file,"w") as outfile:
|
||||||
checkin_count = 0
|
checkin_count = 0
|
||||||
checkin_str = ""
|
checkin_str = ""
|
||||||
outfile.write(" "+call+", "+call_data_dict[call][0]+checkin_str)
|
outfile.write(" "+call+", "+call_data_dict[call][0]+checkin_str)
|
||||||
if call in hh_phone_dict.keys():
|
if call in hoip_phone_dict.keys():
|
||||||
outfile.write(", HH VOIP #"+hh_phone_dict[call])
|
outfile.write(", HOIP #"+hoip_phone_dict[call])
|
||||||
affil = call_data_dict[call][4]
|
affil = call_data_dict[call][4]
|
||||||
if affil != '':
|
if affil != '':
|
||||||
outfile.write(", "+affil+"\n")
|
outfile.write(", "+affil+"\n")
|
||||||
|
|
@ -337,14 +337,21 @@ for net_day in net_day_list:
|
||||||
with open(output_file,"w") as outfile:
|
with open(output_file,"w") as outfile:
|
||||||
|
|
||||||
# Opening text
|
# Opening text
|
||||||
outfile.write("\n ")
|
headertext = '\n'.join([
|
||||||
outfile.write("PNW Digital ARES & EMCOMM Check-In Net - "+str(num_checkins)+" check-ins on "+net_day+"\n")
|
'PNW Digital ARES & EmComm Check-In Net - '+str(num_checkins)+
|
||||||
outfile.write("\n")
|
' check-ins on '+net_day+'\n',
|
||||||
outfile.write("We had a total of "+str(num_checkins)+" check-ins on "+net_day+" to the Pacific Northwest (PNW)\n")
|
'There were '+str(num_checkins)+' check-ins on '+net_day+' to the Pacific',
|
||||||
outfile.write("Digital ARES & EMCOMM Check-In Net. Below is the detailed check-in list grouped\n")
|
'Northwest (PNW) Digital ARES & EmComm Check-In Net.',
|
||||||
outfile.write("by ARRL ARES districts. If any of the info (i.e. name or agency affiliation) below is\n")
|
' ',
|
||||||
outfile.write("incomplete or incorrect, please IM me on Jabber/XMPP (ed@n7ekb.net), or\n")
|
'The following is the detailed check-in list grouped',
|
||||||
outfile.write("e-mail \"ed@n7ekb.net\" with the correction(s).\n\n\n")
|
'by our region\'s ARRL ARES districts. If any of the',
|
||||||
|
'info (i.e. name or agency affiliation) below is',
|
||||||
|
'incomplete or incorrect, please IM me on Jabber/',
|
||||||
|
'XMPP at n7ekb@n7ekb.net, e-mail me at ed@n7ekb.net, or',
|
||||||
|
'call me on HOIP extension 103675 with your updates.',
|
||||||
|
'\n\n'
|
||||||
|
])
|
||||||
|
outfile.write(headertext)
|
||||||
|
|
||||||
# Check-in details
|
# Check-in details
|
||||||
for state in sorted(report_dict):
|
for state in sorted(report_dict):
|
||||||
|
|
@ -360,8 +367,10 @@ for net_day in net_day_list:
|
||||||
cur_call_list.sort()
|
cur_call_list.sort()
|
||||||
for call in cur_call_list:
|
for call in cur_call_list:
|
||||||
affil = call_data_dict[call][4]
|
affil = call_data_dict[call][4]
|
||||||
hh_num = call_data_dict[call][5]
|
hoip_num = call_data_dict[call][5]
|
||||||
outfile.write(" "+call+", "+call_data_dict[call][0])
|
outfile.write(" "+call+", "+call_data_dict[call][0])
|
||||||
|
if hoip_num != '':
|
||||||
|
outfile.write(", HOIP #"+hoip_num)
|
||||||
if affil != '':
|
if affil != '':
|
||||||
outfile.write(", "+affil+"\n")
|
outfile.write(", "+affil+"\n")
|
||||||
else:
|
else:
|
||||||
|
|
@ -373,8 +382,10 @@ for net_day in net_day_list:
|
||||||
cur_call_list.sort()
|
cur_call_list.sort()
|
||||||
for call in cur_call_list:
|
for call in cur_call_list:
|
||||||
affil = call_data_dict[call][4]
|
affil = call_data_dict[call][4]
|
||||||
hh_num = call_data_dict[call][5]
|
hoip_num = call_data_dict[call][5]
|
||||||
outfile.write(" "+call+", "+call_data_dict[call][0])
|
outfile.write(" "+call+", "+call_data_dict[call][0])
|
||||||
|
if hoip_num != '':
|
||||||
|
outfile.write(", HOIP #"+hoip_num)
|
||||||
if affil != '':
|
if affil != '':
|
||||||
outfile.write(", "+affil+"\n")
|
outfile.write(", "+affil+"\n")
|
||||||
else:
|
else:
|
||||||
|
|
@ -390,8 +401,10 @@ for net_day in net_day_list:
|
||||||
cur_call_list.sort()
|
cur_call_list.sort()
|
||||||
for call in cur_call_list:
|
for call in cur_call_list:
|
||||||
affil = call_data_dict[call][4]
|
affil = call_data_dict[call][4]
|
||||||
hh_num = call_data_dict[call][5]
|
hoip_num = call_data_dict[call][5]
|
||||||
outfile.write(" "+call+", "+call_data_dict[call][0])
|
outfile.write(" "+call+", "+call_data_dict[call][0])
|
||||||
|
if hoip_num != '':
|
||||||
|
outfile.write(", HOIP #"+hoip_num)
|
||||||
if affil != '':
|
if affil != '':
|
||||||
outfile.write(", "+affil+"\n")
|
outfile.write(", "+affil+"\n")
|
||||||
else:
|
else:
|
||||||
|
|
@ -400,14 +413,24 @@ for net_day in net_day_list:
|
||||||
outfile.write("\n")
|
outfile.write("\n")
|
||||||
|
|
||||||
# output the footer text...
|
# output the footer text...
|
||||||
outfile.write("\nAbout the net:\n\n")
|
footer = '\n'.join([
|
||||||
outfile.write("The PNW Digital ARES & EMCOMM Check-In Net is held every Sunday evening at 7:30 PM local\n")
|
'About the net:\n',
|
||||||
outfile.write("time on PNW Regional, DMR talk group 31771 (available on both the PNW Digital http://pnwdigital.net\n")
|
'The PNW Digital ARES & EMCOMM Check-In Net is held',
|
||||||
outfile.write("and Brandmeister DMR networks). Anyone interested in Amateur Radio Emergency Communications is\n")
|
'every Sunday evening at 7:30 PM local time on PNW',
|
||||||
outfile.write("welcome to check-in. The net is an opportunity for DMR-capable ARES and EMCOMM hams to exercise\n")
|
'Regional, DMR talkgroup 31771. DMR Talk Group 31771 is',
|
||||||
outfile.write("their DMR equipment in a regional directed net. The net demonstrates the wide coverage area and\n")
|
'available on both the PNW Digital and the Brandmeister',
|
||||||
outfile.write("capability of DMR repeaters and hot spots in our Pacific Northwest region. It also highlights the\n")
|
'DMR networks. Anyone interested in Amateur Radio and',
|
||||||
outfile.write("wide range of EMCOMM-related organizations who have members with DMR capability.\n\n")
|
'Emergency Communications is welcome to check-in. The',
|
||||||
|
'net is an opportunity for DMR-capable ARES and EmComm',
|
||||||
|
'hams to exercise their DMR equipment in a regional,',
|
||||||
|
'directed net. The net demonstrates the wide coverage',
|
||||||
|
'area and capability of DMR repeaters and hot spots in',
|
||||||
|
'our Pacific Northwest region. It also highlights the',
|
||||||
|
'wide range of EmComm-related organizations who have',
|
||||||
|
'members with DMR capability.\n\n',
|
||||||
|
'#net-reports #hamradio #EmComm #ARES #DMR #HOIP #PNWDigital\n'
|
||||||
|
])
|
||||||
|
outfile.write(footer)
|
||||||
|
|
||||||
# close this week's report file
|
# close this week's report file
|
||||||
outfile.close()
|
outfile.close()
|
||||||
|
|
|
||||||
6203
hoip_subscribers.csv
Normal file
6203
hoip_subscribers.csv
Normal file
File diff suppressed because it is too large
Load diff
56
hoipdownload.py
Normal file
56
hoipdownload.py
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
from ldap3 import Server, Connection, ALL, SUBTREE
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
# LDAP connection details from the wiki
|
||||||
|
server_url = 'ldap://207.246.98.219:389' # Server and port (no TLS, as per "TLS Mode: LDAP or No" – use 'ldaps://' if TLS needed)
|
||||||
|
search_base = 'ou=people,dc=hamsoverip,dc=com'
|
||||||
|
search_filter = '(telephoneNumber=*)' # Fetch all entries with an extension; adjust if needed, e.g., '(objectClass=person)'
|
||||||
|
attributes = ['cn', 'sn', 'telephoneNumber'] # Key attributes; add more if you discover others like 'callsign'
|
||||||
|
|
||||||
|
# Connect anonymously (no auth)
|
||||||
|
server = Server(server_url, get_info=ALL)
|
||||||
|
conn = Connection(server, auto_bind=True) # No user/password
|
||||||
|
|
||||||
|
# Perform the search
|
||||||
|
conn.search(
|
||||||
|
search_base=search_base,
|
||||||
|
search_filter=search_filter,
|
||||||
|
search_scope=SUBTREE,
|
||||||
|
attributes=attributes
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract data
|
||||||
|
data = []
|
||||||
|
for entry in conn.entries:
|
||||||
|
extension = entry.telephoneNumber.value if 'telephoneNumber' in entry else ''
|
||||||
|
cn_value = entry.cn.value if 'cn' in entry else ''
|
||||||
|
sn_value = entry.sn.value if 'sn' in entry else ''
|
||||||
|
|
||||||
|
# clean up data
|
||||||
|
hoipnumber = extension.partition(' ')[0]
|
||||||
|
callsign = cn_value.partition(' ')[0]
|
||||||
|
hamname = sn_value.partition(' ')[0]
|
||||||
|
|
||||||
|
if hoipnumber: # Only include entries with extensions
|
||||||
|
data.append({
|
||||||
|
'HOIP Extension': hoipnumber,
|
||||||
|
'Call': callsign,
|
||||||
|
'Name': hamname # Include raw for reference
|
||||||
|
})
|
||||||
|
|
||||||
|
# Save to CSV
|
||||||
|
if data:
|
||||||
|
df = pd.DataFrame(data)
|
||||||
|
|
||||||
|
# CSV output for debugging...
|
||||||
|
df.to_csv('hoip_subscribers.csv', index=False)
|
||||||
|
print(f'Saved {len(data)} entries to hoip_subscribers.csv')
|
||||||
|
|
||||||
|
# Excel output for production...
|
||||||
|
df.to_excel('hoip_subscribers.xlsx', index=False)
|
||||||
|
print(f'Saved {len(data)} entries to hoip_subscribers.xlsx')
|
||||||
|
else:
|
||||||
|
print('No entries found – check filter or connection.')
|
||||||
|
|
||||||
|
# Unbind
|
||||||
|
conn.unbind()
|
||||||
14
shell.nix
Normal file
14
shell.nix
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# shell.nix
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
(pkgs.python3.withPackages (ps: with ps; [
|
||||||
|
numpy
|
||||||
|
openpyxl
|
||||||
|
pandas
|
||||||
|
python-dateutil
|
||||||
|
]))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue