Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added new table of recent QSOs and more cleanup of the INI file plus a few others. #69

Merged
merged 6 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions INSTALL_RASPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ This last command will run for quite some time.
The latest test took about 90 minutes with a good internet connection for a Raspberry Pi 3 B+ (18 minutes on a Pi 4B). We gave it a lot of things to do so you won't need to do them.
While you are waiting, you could create a splash screen for your event: a 1000x1000 portable network graphics (.png) image in RGB format works great.

Copy the file config-sample.ini to ~/.config/n1mm_view.ini.
Copy the file `n1mm_view.ini.sample` to `~/.config/n1mm_view.ini`. Note you can also put the file in your home directory (`~/`) or the script directory. But the file can only be in one of those places--not two or three.

Note to pame it easier if you need to run dashboard.py as sudo, it is useful to create a symbolic link named /root/.config/n1mm_view.ini to point to /home/pi/.config/n1mm_view.ini.
Note to make it easier if you need to run dashboard.py as sudo, it is useful to create a symbolic link named /root/.config/n1mm_view.ini to point to /home/pi/.config/n1mm_view.ini.
Use the following command if so desired:
```
sudo ln -s /home/pi/.config/n1mm_view.ini /root/.config/n1mm_view.ini
Expand Down
30 changes: 0 additions & 30 deletions config-sample.ini

This file was deleted.

1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def __init__(self, *args, **kw):
self.DISPLAY_DWELL_TIME = cfg.getint('GLOBAL','DISPLAY_DWELL_TIME',fallback=6)
self.DATA_DWELL_TIME = cfg.getint('GLOBAL','DATA_DWELL_TIME',fallback=60)
self.HEADLESS_DWELL_TIME = cfg.getint('GLOBAL','HEADLESS_DWELL_TIME',fallback=180)
self.SKIP_TIMESTAMP_CHECK = cfg.getboolean('DEBUG','SKIP_TIMESTAMP_CHECK',fallback=False)



Expand Down
8 changes: 4 additions & 4 deletions constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,18 @@ class Modes:
"""
all the modes that are supported.
"""
MODES_LIST = ['N/A', 'CW', 'AM', 'FM', 'LSB', 'USB', 'RTTY', 'PSK', 'PSK31', 'PSK63', 'FT8', 'FT4']
MODES_LIST = ['N/A', 'CW', 'AM', 'FM', 'LSB', 'USB', 'SSB', 'RTTY', 'PSK', 'PSK31', 'PSK63', 'FT8', 'FT4', 'MFSK', 'NoMode', 'None']
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does your logging software (I think you are not using N1MM) emit a mode of 'SSB'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have seen other software send SSB. I think TR4W (which I maintain) did but I fixed that issue. This is a belt-and-suspenders approach now more so just in case.

MODES = {elem: index for index, elem in enumerate(MODES_LIST)}

"""
simplified modes for score reporting: CW, PHONE, DATA
"""
SIMPLE_MODES_LIST = ['N/A', 'CW', 'PHONE', 'DATA']
MODE_TO_SIMPLE_MODE = [0, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3]
MODE_TO_SIMPLE_MODE = [0, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2]
SIMPLE_MODE_POINTS = [0, 2, 1, 2] # n/a, CW, phone, digital
SIMPLE_MODES = {'N/A': 0, 'CW': 1,
'AM': 2, 'FM': 2, 'LSB': 2, 'USB': 2,
'RTTY': 3, 'PSK': 3, 'PSK31': 3, 'PSK63': 3, 'FT8': 3, 'FT4': 3, 'MFSK': 3,
'AM': 2, 'FM': 2, 'LSB': 2, 'USB': 2, 'SSB': 2, 'None': 2,
'RTTY': 3, 'PSK': 3, 'PSK31': 3, 'PSK63': 3, 'FT8': 3, 'FT4': 3, 'MFSK': 3, 'NoMode': 3,
}

@classmethod
Expand Down
35 changes: 34 additions & 1 deletion dataaccess.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def create_tables(db, cursor):
cursor.execute('CREATE INDEX IF NOT EXISTS qso_log_station_id ON qso_log(station_id);')
cursor.execute('CREATE INDEX IF NOT EXISTS qso_log_section ON qso_log(section);')
cursor.execute('CREATE INDEX IF NOT EXISTS qso_log_qso_id ON qso_log(qso_id);')
cursor.execute('CREATE INDEX IF NOT EXISTS qso_log_qso_timestamp ON qso_log(timestamp);')
db.commit()


Expand Down Expand Up @@ -211,7 +212,8 @@ def get_last_qso(cursor) :
message = 'Last QSO: %s %s %s on %s by %s at %s' % (
row[1], row[2], row[3], constants.Bands.BANDS_TITLE[row[5]], row[4],
datetime.utcfromtimestamp(row[0]).strftime('%H:%M:%S'))
logging.debug('%s' % (message))
logging.debug('%s' % (message))

return last_qso_time, message


Expand Down Expand Up @@ -308,3 +310,34 @@ def get_qsos_by_section(cursor):
qsos_by_section[row[0]] = row[1]
logging.debug(f'Section {row[0]} {row[1]}')
return qsos_by_section

def get_last_N_qsos(cursor, nQSOCount):
logging.info('get_last_N_qsos for last %d QSOs' % (nQSOCount))
qsos = []
cursor.execute('SELECT qso_id, timestamp, callsign, band_id, mode_id, operator.name, rx_freq, tx_freq, exchange, section, station.name \n'
'FROM qso_log '
'JOIN operator ON operator.id = operator_id\n'
'JOIN station ON station.id = station_id\n'
'ORDER BY timestamp DESC LIMIT %d;' % (nQSOCount))
for row in cursor:
qsos.append(( row[1] # raw timestamp 0
,row[2] # call 1
,constants.Bands.BANDS_TITLE[row[3]] # band 2
,constants.Modes.SIMPLE_MODES_LIST[constants.Modes.MODE_TO_SIMPLE_MODE[row[4]]] # mode 3
,row[5] # operator callsign 4
,row[8] # exchange 5
,row[9] # section 6
,row[10] # station name 7
))
message = 'QSO: time=%sZ call=%s exchange=%s %s mode=%s band=%s operator=%s station=%s' % (
datetime.utcfromtimestamp(row[1]).strftime('%Y %b %d %H:%M:%S')
,row[2] # callsign
,row[8] # exchange
,row[9] # section
,constants.Modes.SIMPLE_MODES_LIST[constants.Modes.MODE_TO_SIMPLE_MODE[row[4]]]
,constants.Bands.BANDS_TITLE[row[3]]
,row[5] #operator
,row[10] #station
)
logging.info('%s' % (message))
return qsos
28 changes: 28 additions & 0 deletions graphics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import matplotlib.pyplot as plt
import pygame
from matplotlib.dates import HourLocator, DateFormatter
import pprint
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's this for? I don't see any usages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a debugging remnant. It can be removed. Sorry.


from config import Config
from constants import *
Expand Down Expand Up @@ -210,7 +211,34 @@ def qso_classes_graph(size, qso_classes):
values.append(d[0])
return make_pie(size, values, labels, "QSOs by Class")

def qso_table(size, qsos):
"""
create the a table of the qso log
"""
if len(qsos) == 0:
return None, (0, 0)

count = 0
cells = [['Time', 'Call', 'Band', 'Mode', 'Operator', 'Section', 'Station']]

for d in qsos:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the number of qsos is already limited by the fetch, does this need to also limit the size? Another way to do this would be to slice the QSOs e.g. for d in qsos[:10] or something like that. in all this looks good. TY

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Since the fetch of the records is its own discrete function, this protects the table generation in case a result set of too large a size is returned. That way the generation protects itself without relying upon how big the supplied set is. I wasn't aware of the syntax for d in qsos[:10]. That is handy.

cells.append( ['%s' % datetime.datetime.utcfromtimestamp(d[0]).strftime('%Y %b %d %H:%M:%S') # Time
,'%s' % d[1] # Call
,'%s' % d[2] # Band
,'%s' % d[3] # Mode
,'%s' % d[4] # Operator
,'%s' % d[6] # Section
,'%s' % d[7] # Station
])
count += 1
if count >= 10:
break

if count == 0:
return None, (0, 0)
else:
return draw_table(size, cells, "Last 10 QSOs")

def qso_operators_table(size, qso_operators):
"""
create the Top 5 QSOs by Operators table
Expand Down
39 changes: 31 additions & 8 deletions headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def create_images(size, image_dir, last_qso_timestamp):
qsos_per_hour = []
qsos_by_section = {}
qso_classes = []
qsos = []

db = None
data_updated = False
Expand All @@ -70,7 +71,9 @@ def create_images(size, image_dir, last_qso_timestamp):
last_qso_time, message = dataaccess.get_last_qso(cursor)

logging.debug('old_timestamp = %s, timestamp = %s' % (last_qso_timestamp, last_qso_time))
if last_qso_time != last_qso_timestamp:
if config.SKIP_TIMESTAMP_CHECK:
logging.warn('Skipping check for a recent QSO - Please jkust use this for debug - Review SKIP_TIMESTAMP_CHECK in ini file')
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo jkust

if last_qso_time != last_qso_timestamp or config.SKIP_TIMESTAMP_CHECK:
# last_qso_time is passed as the result and updated in call to this function.
logging.debug('data updated!')
data_updated = True
Expand All @@ -95,8 +98,11 @@ def create_images(size, image_dir, last_qso_timestamp):

# load QSOs by Section
qsos_by_section = dataaccess.get_qsos_by_section(cursor)

# load last 10 qsos
qsos = dataaccess.get_last_N_qsos(cursor, 10) # Note this returns last 10 qsos in reverse order so oldest is first

logging.debug('load data done')
logging.info('load data done')
except sqlite3.OperationalError as error:
logging.exception(error)
return
Expand All @@ -115,78 +121,95 @@ def create_images(size, image_dir, last_qso_timestamp):
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_rates_table(size, operator_qso_rates)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_rates_table')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_operators_graph(size, qso_operators)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_operators_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_operators_table(size, qso_operators)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_operators_table')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_operators_table_all(size, qso_operators)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_operators_table_all')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_stations_graph(size, qso_stations)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_stations_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_bands_graph(size, qso_band_modes)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_bands_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_modes_graph(size, qso_band_modes)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_modes_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_classes_graph(size, qso_classes)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_classes_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_rates_graph(size, qsos_per_hour)
if image_data is not None:
filename = makePNGTitle(image_dir, 'qso_rates_graph')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

try:
image_data, image_size = graphics.qso_table(size, qsos)
if image_data is not None:
filename = makePNGTitle(image_dir, 'last_qso_table')
graphics.save_image(image_data, image_size, filename)
except Exception as e:
logging.exception(e)

# map gets updated every time so grey line moves
try:
# There is a memory leak in the next code -- is there?
image_data, image_size = graphics.draw_map(size, qsos_by_section)
if image_data is not None:
filename = makePNGTitle(image_dir, 'sections_worked_map')
graphics.save_image(image_data, image_size, filename)
gc.collect()
# There is a memory leak in the next code -- is there?
image_data, image_size = graphics.draw_map(size, qsos_by_section)
if image_data is not None:
filename = makePNGTitle(image_dir, 'sections_worked_map')
graphics.save_image(image_data, image_size, filename)
gc.collect()

except Exception as e:
logging.exception(e)
Expand Down
4 changes: 2 additions & 2 deletions init/n1mm_view_collector.service
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ ExecStart=/home/pi/n1mm_view/collector.py
Restart=always
RestartSec=30
StandardInput=tty
StandardOutput=syslog
StandardError=syslog
StandardOutput=journal
StandardError=journal
TTYPath=/dev/tty2
TTYReset=yes
TTYVHangup=yes
Expand Down
11 changes: 7 additions & 4 deletions init/n1mm_view_dashboard.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@

[Unit]
Description=N1MM View Dashboard process
After=gettty@tty3.service
After=gettty@tty5.service

[Service]
User=pi
Group=pi
Type=simple
WorkingDirectory=/home/pi/n1mm_view

# Wait until we startup dashboard
#ExecStartPre=/bin/sleep 30
ExecStart=/home/pi/n1mm_view/dashboard.py
StandardInput=tty
StandardOutput=syslog
StandardError=syslog
TTYPath=/dev/tty3
StandardOutput=journal
StandardError=journal
TTYPath=/dev/tty5
TTYReset=yes
TTYVHangup=yes

Expand Down
4 changes: 2 additions & 2 deletions init/n1mm_view_headless.service
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ ExecStart=/home/pi/n1mm_view/headless.py
Restart=always
RestartSec=30
StandardInput=tty
StandardOutput=syslog
StandardError=syslog
StandardOutput=journal
StandardError=journal
TTYPath=/dev/tty3
TTYReset=yes
TTYVHangup=yes
Expand Down
Loading