Giter VIP home page Giter VIP logo

n1mm_view's Introduction

n1mm_view

n1mm_view is a set of python scripts to display real-time contest QSO statistics for N4N ARRL Field Day.

It listens to N1MM+ or TR4W "Contact Info" UDP broadcasts (see the N1MM+ documentation) and collects the contact info into a database. The contact info data is used to create useful data screens that are continuously rotated.

The contactchange and contactdelete UDP messages are also handled for changes made to the data in the logging program.

It was built to run on a Raspberry Pi and to display the statistics on a large television screen. It should run anywhere its dependencies can be installed on, meaning it should work on Linux, Windows, and OS X. It is known to run on the Raspberry Pi and also on Windows 7/10.

Currently, it supports the following displays:

  • QSOs by Operator pie chart
  • QSO by Band Pie chart
  • QSOs by Mode Pie chart
  • QSOs by Station Pie chart
  • QSOs Summary Table
  • QSO/Hour Rate Table
  • Top 5 Operators by QSO Count Table
  • QSOs/Hour/Band stacked chart
  • Sections Worked choropleth Map, shows all US and Canada sections.

Example Images:

QSOs by Section:

QSOs by Section

QSO Rate Chart:

QSO Rates Chart

QSOs by Operator Chart:

QSOs by Operator Chart

Summary Table:

QSO Summary Table

QSOs by Operator Table:

QSOs by Operator Table

QSO Rates Table:

QSO Rates Table

Dependencies:

(see requirements.txt)

  • python 3
  • python matplotlib library
  • python pygame library
  • python sqlite3 library
  • python Cartopy library

Components:

  • collector.py -- collect contact data from n1mm+ broadcasts
  • config.py -- configuration data. edit this to change configuration.
    In theory, the only part you should need to edit to configure n1mm_view for your environment.
  • constants.py -- constant values shared by collector and dashboard. Bands and Modes are defined here.
  • dashboard.py -- display collected statistics on screen
  • dataaccess.py -- module contains data access code
  • graphics.py -- module contains code to create and manipulate the graphs, charts, and map.
  • headless.py -- application to create graphs, charts, and maps non-interactively, producing image files. Useful if you want to serve the images by http.
  • one_chart.py -- application that will display one chart only. Use this when debugging charts.
  • replayer.py -- test application, "replays" an old N1MM+ log to test collector and dashboard.
  • init/n1mm_view_collector.service -- systemd control file, starts collector at boot
  • init/n1mm_view_dashboard.service -- systemd control file, starts dashboard at boot
  • shapes/* -- map shapes for every US section. Thank you, Charles.

Installation

See INSTALL_RASPI.md for information to install n1mm_view on a Raspberry Pi. See INSTALL_WINDOWS.md for information to install n1mm_view on Windows.

N1MM+ Setup

N1MM+ needs to be configured to send the UDP messages.

N1MM+ needs to be configured to send UDP contact broadcasts to N1MM_VIEW.

Before performing these N1MM+ changes, get the IPv4 address of the PC or Raspberry Pi that is running N1MM_VIEW. For example, the IPv4 address would look like 192.168.1.123.

  • On Windows, open a DOS CMD window and type ipconfig
  • On a Raspberry Pi, open a terminal window and type ip addr

Now open N1MM+, and open the Configurer. Use the "Config->Configure Ports, Mode Control..." menu option to open the "Configurer".

From the "Configurer" window, select the "Broadcast Data" tab.

In the "Broadcast Data" tab, check the box in front of the word "contacts".
Set the IP address on that same line to be either the single, specific IP of the Raspberry Pi running collector.py, or set it to the proper broadcast address for your subnet.

(I believe the proper broadcast address can be calculated from the machines's IP address ORed with the NOT of the machine's subnet mask. I could be wrong. But for your garden variety 192.168.1.n IP address, the broadcast address is 192.168.1.255. That's a good place to start from.)

Usage:

Log in to your raspberry pi. You don't need X-window system, the dashboard can create the graphics window without X. Open at least two login sessions. (Alt-F1, Alt-F2, etc. to switch between virtual consoles if not running X is good.)

Change to the directory where the code is installed.

You may wish to edit the n1mm_view_config.py file to adjust the start and stop dates of field day, for instance.

You may wish to delete the n1mm_view.db database file to reset the counts to zero. The collector.py program will re-create the database.

In one login session, start the collector: $ ./collector.py

The collector should display output for every QSO message it receives. This is a good thing.

Control-C will stop the collector.

in the other login session, start the dashboard: $ ./dashboard.py

The dashboard should start up. Eventually, graphs and tables will be displayed. The dashboard supports the following keys:

  • Q: quit
  • left and right arrows: change displayed page
  • scroll lock button: stop automatic page changing

If HTML_DIR is set, the dashboard will create PNG files of all the images.

If HTML_ONLY is selected, then no screen interface is displayed. (Use CTRL-\ to stop dashboard).

License:

This software is licensed under the terms of the "Simplified BSD license", see LICENSE.

Copyright 2016, 2017, 2019 Jeffrey B. Otterson, N1KDO
All Rights Reserved

To Do / Help Wanted!

There's more to do. This project is still in late-prototype stage.

  • add command-line options
  • somebody might want to support other contests besides field day
    • multipliers table
  • the qso_log table could be exported to ADIF.
  • perhaps instead of listening to UDP packets, the N1MM log database could be read.
  • A help option ("?" key?) for the dashboard to display what keys do what.

GitHub pull requests will be happily looked at, and the odds are good that they will be merged. Your contributions are welcome. See https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request for information on how to create a pull request

n1mm_view's People

Contributors

craigpl avatar dependabot[bot] avatar jotterson avatar kd4z avatar ke0pci avatar n1kdo avatar njohnsn avatar ny4i avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

n1mm_view's Issues

Using the SavetoPNG files option

To use this code, one has to define a var called HTML_DIR on the config file. Mine looks like this:
HTML_DIR = '/home/toms/n1mm_view/html'

but I run under my user. This could also be /home/pi/n1mm_view/html or even /var/www/html to keep it simple. As long as the user in question has write access to that directory.

How one uses the files depends upon what display mechanism one chooses. I am working on a JavaScript image rotating system but anything that rotates through the PNG files and refreshes them every minute would be fine.

A future patch will establish an HTML_ONLY option so the display engine to the screen is not used. This makes it strictly for PNG file generation.

rpi_install.sh gives error on [[ not found

Error when running rpi_install.sh file:

$ sudo ./rpi_install.sh
./rpi_install.sh: 4: [[: not found

Script fails in the beginning around the first IF statement with two open square brackets. Maybe I'm running the wrong shell. Maybe we should include the shell on the first line of the script. Changing to use the Bash shell and removing one set of square brackets seems to fix this.

Note: this could be due to the user using the wrong shell.
Environment used:
Raspberry Pi (Bullseye 32-bit full install).
Default shell (bash).

Got it working but

In the instructions to configure N1MM it says "In the "Broadcast Data" tab, check the box in front of the word "content"." There is not selection for "content" I see "Contacts", is this what it is supposed to be?
broadcast data

Pi and WIndows questions

Hi
I have some questions and observations. I'm looking forward to comments
Cheers
Rick AG6AY

Installing on Pi using Stretch

  1. I found line 51 in graphics.py needed 'x11' in the list before init_display() would work
    (the full list of possible values is at https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlenvvars.html) but debian-based Pi only needed 'x11' added)
  2. I found packages python-pygame and numpy pre-installed installed with Stretch and I did not need to add them manually.
  3. I did not need to build basemap ... this seemed to work ...

After installing Stretch ...
sudo aptitude update
sudo aptitude upgrade
sudo aptitude install libgeos-3.5.1
sudo aptitude install libgeos-dev
sudo aptitude install python-matplotlib
sudo aptitude install python-mpltoolkits.basemap
git clone

I say seemed to work because running the ./collector.py and ./dashboard.py brings up the logo and marque moving line at the bottom of the screen but i have not yet gotten the various dashboards to visually cycle on the screen.

Do I need data in the database first?
Can you provide more instructions on using replayer.py?

Installing on Windows

  1. I am stuck on Step 14 and 15 of INSTALL_WINDOWS.md. "call ..\Scripts\activate.bat" finds nothing to execute.

All states?

I'm new to the shape files. I wonder if you have any hints on where to look for a state shape for contests that use states? I did a bit of research on country maps but that might be too much. I was thinking states would be useful for state contests like NAQP and QSO Parties.

It would also be useful for events like a local science festival where we can just log state and have it highlight the whole state for when they ask, "How far away have you talked to".

What I am unsure of is if you are gathering counties together to draw the lines in multi-section states so I can make equivalent files of just CA, WA, TX, FL, NY, NJ, PA, MA, etc.

Thanks...

Sorry I'm new at this!

Following your directions, and trying to start the ./dashboard.py I'm getting an error that says No Module named basemap. The whole error reads this

Traceback (most recent call last):
File "./dashboard.py", line 19 in
import graphics
File "/home/pi/nimm_view/graphics/py". line 17 in
from mpl_toolkits.basemap import Basemap
Import Error: No module named basemap

I also had to get the basemap from this location instead of the one mentioned in the directions.
https://sourceforge.mirrorservice.org/m/ma/matplotlib/matplotlib-toolkits/basemap-1.0.7/

I was getting a 404 error with the link in your directions, not sure if that was the problem or not. I'm sorry for being a newb and asking this question.

Thanks in advance!

Using the N1MM ID field for the duplicate QSO detection

N1MM now sends an ID in the contacting record. This appears to be a 32 byte GUID. I suggest that the duplicate detection can be simplified to use the ID to lookup the contact. This is important because the duplicate table is made up of the following fields:

def checksum(data):
    """
    generate a unique ID for each QSO.
    this is using md5 rather than crc32 because it is hoped that md5 will have less collisions.
    """
    hval = data['timestamp'] + data['rxfreq'] + data['txfreq'] + data['operator'] + data['mode'] + data['call']
    return int(hashlib.md5(hval.encode()).hexdigest(), 16)

But the deletes record from N1MM does not send everything in the hash.

From the N1MM UDP Documentation:

<?xml version="1.0" encoding="utf-8"?>
<contactdelete>
    <app>N1MM</app>
    <timestamp>2020-01-17 16 :43:38</timestamp>
    <call>WlAW</call>
    <contestnr>73</contestnr>
    <StationName>CONTEST-PC</StationName>
    <ID>a1b2c3d4e5f6g7h</ID>
</contactdelete>

So when a contact is corrected and N1MM sends the contact delete and then contact replace, the collector cannot delete the entry from the duplicates hash table.

If it uses the ID field, then it can be deleted.

If it is not deleted, when N1MM sends the contact replace packet (which has the same fields as contactinfo, it would flag as a duplicate unless the timestamp of the contact or callsign were changed.

So by just using ID for duplicates detection, it would allow the deletion/replace change process to work.

Where this is really important is that if N1MM resends all the contacts to UDP, it sends out contact delete and contact replace packets.

This is the sequence sent by N1MM on a rebroadcast:

'<?xml version="1.0" encoding="utf-8"?>\r<contactdelete>\r\t<app>N1MM</app>\r\t<timestamp>2022-06-27 19:45:53</timestamp>\r\t<call>W2IU</call>\r\t<contestnr>22</contestnr>\r\t<StationName>DESKTOP-4K20B9M</StationName>\r\t<ID>fcbdeb9c-c69d-4479-90b1-2944b6cd76be</ID>\r</contactdelete>

2022-06-27 19:59:36.086 INFO DELETEQSO: W2IU, timestamp = 1656359153

Then it sends the contact replace

<?xml version="1.0" encoding="utf-8"?>\r<contactreplace>\r\t<app>N1MM</app>\r\t<contestname>FD</contestname>\r\t<contestnr>22</contestnr>\r\t<timestamp>2022-06-27 19:45:53</timestamp>\r\t<mycall>W4TA</mycall>\r\t<band>14</band>\r\t<rxfreq>1420000</rxfreq>\r\t<txfreq>1420000</txfreq>\r\t<operator>NY4I</operator>\r\t<mode>USB</mode>\r\t<call>W2IU</call>\r\t<countryprefix>K</countryprefix>\r\t<wpxprefix>W2</wpxprefix>\r\t<stationprefix>W4TA</stationprefix>\r\t<continent>NA</continent>\r\t<snt>59</snt>\r\t<sntnr>2</sntnr>\r\t<rcv>59</rcv>\r\t<rcvnr>0</rcvnr>\r\t<gridsquare> </gridsquare>\r\t<exchange1>1E</exchange1>\r\t<section>NLI</section>\r\t<comment></comment>\r\t<qth></qth>\r\t<name></name>\r\t<power></power>\r\t<misctext> </misctext>\r\t<zone>5</zone>\r\t<prec></prec>\r\t<ck>0</ck>\r\t<ismultiplier1>0</ismultiplier1>\r\t<ismultiplier2>0</ismultiplier2>\r\t<ismultiplier3>0</ismultiplier3>\r\t<points>1</points>\r\t<radionr>1</radionr>\r\t<run1run2>1</run1run2>\r\t<RoverLocation> </RoverLocation>\r\t<RadioInterfaced>0</RadioInterfaced>\r\t<NetworkedCompNr>0</NetworkedCompNr>\r\t<IsOriginal>True</IsOriginal>\r\t<NetBiosName>DESKTOP-4K20B9M</NetBiosName>\r\t<IsRunQSO>0</IsRunQSO>\r\t<StationName>DESKTOP-4K20B9M</StationName>\r\t<ID>fcbdeb9cc69d447990b12944b6cd76be</ID>\r\t<IsClaimedQso>1</IsClaimedQso>\r</contactreplace>

And then in the collector log, we get a duplicate message because the item was not deleted upon the contact delete.

2022-06-27 19:59:36.118 DEBUG duplicate message

UnboundLocalError: local variable 'row' referenced before assignment

I'm getting the following error when dashboard tries to start the chart engine:
(Trying to get running on a RPi 3).

Traceback (most recent call last): File "./dashboard.py", line 282, in update_charts last_qso_timestamp = load_data(size, q, base_map, last_qso_timestamp) File "./dashboard.py", line 71, in load_data last_qso_time, message = dataaccess.get_last_qso(cursor) File "/home/pi/n1mm_view/dataaccess.py", line 110, in get_last_qso row[1], row[2], row[3], constants.Bands.BANDS_TITLE[row[5]], row[4], UnboundLocalError: local variable 'row' referenced before assignment

GS to GH

Presuming you accept the pull request to change GS to GH, you have to change the 4 shape files to GH as well (shaqpes/GS.* to shapes/GH.*)

qso_rates_chart tightly bound to BANDS_LIST constant

The BANDS_LIST constant included bands that might not be used for all contests (6m, 2m and 440). I removed those bands as I did not want them to appear in the chart, but that qso_rates_chart had an error in this code:

ax.stackplot(dates, qso_counts[1], qso_counts[2], qso_counts[3], qso_counts[4], qso_counts[5], qso_counts[6],
                     qso_counts[7], qso_counts[8], qso_counts[9], labels=labels, colors=colors, linewidth=0.2)

If the select statement (which loads the array is shorter than this line expects, the later arrays are not populated. It would be good to check how many bands are defined and join only those array elements.

I add this as a reminder as I am glad to change that to make this more parameter driven.

Another way might be to have a definition of type like HF/VHF. As this is Field Day, I can see where the users might want to include VHF and UHF is a free VHF station is being used, but others might like this just for HF�—which also makes it easier to extend for other contests like Sweepstakes.

Question about headless.py

My use case is I have a single PI that displays the screens on a monitor (or projector) and sends the QSOs to the Internet. When I originally added the coded to create PNG files and upload them, I had it combined. When the code went through its modularization, these functions were separated into dashboard.py and headless.py.

In my use case, would I run two instances (on different Pis)?

I am curious if there is a particular reason the option to have these functions combines was removed from the code?

Thanks, Tom NY4I

time removed from action chart of headless?

I noticed the timestamp was removed from the sections chart. It is updated anyway because of the gray line so I was curious if that was just an oversight. I added awhile ago if I recall as it told me the updates on the website were happening as during the day the gray line does not change.

Any reason I cannot add it back?

Tom

Auto-start (or kiosk mode)

Hi. This is exactly what I was think about doing. Always glad to find an idea already coded. I am curious if you see any issues if I make this more like a Kiosk. My main concern is if the Pi lost power for some reason, I would want it to just come back up upon restart so essentially boot to running the display with no keyboard interaction.

Unless there is some issue with that, I will look at that after I get the basics running.

Thanks, Tom Ny4I

Basemap is impossible to install

I was going to update N1MM_View to support Python 3 and the latest matplotlib, and I struck out trying to install basemap, it is a giant pain in the ass on Windows, and probably worse on Raspbian.

I think I want to re-factor the display logic to use javascript libraries d3s for the graphs and leaflet for the mapping. This will result in the displays needing a modern web browser to run, but will also allow stations on the network to display the graphics as well as the main monitor connected to the Pi.

Issues with init of pygame with Bookworm on a Pi5

I am documenting this here since I ran into it while testing my Pi5. The Raspberry Pi 5 can only run bookworm. There is an option in raspi-config to use X11 instead of Wayland but I still am running into an error:

pi@n1mmview5:~/n1mm_view $ sudo ./dashboard.py 
pygame 2.1.2 (SDL 2.26.5, Python 3.11.2)
Hello from the pygame community. https://www.pygame.org/contribute.html
2024-01-17 00:36:00.498 INFO     generated new fontManager
2024-01-17 00:36:00.799 INFO     dashboard startup
2024-01-17 00:36:00.802 ERROR    Could not initialize display.
Traceback (most recent call last):
  File "/home/pi/n1mm_view/./dashboard.py", line 319, in main
    screen, size = graphics.init_display()
                   ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/pi/n1mm_view/graphics.py", line 72, in init_display
    raise Exception('No suitable video driver found!')
Exception: No suitable video driver found!

Before I selected X11 in raspi-config, I did some investigation on how to get pygame to use wayland. I added 'wayland' as a driver type to graphics.py but that did not help.

I don't have much experience with pygame but have exhausted my search results for the moment looking for a workaround. So until that is solved, stick to a Pi 4 (since you cannot use the PI5 on anything but Bookworm) and buster or bullseye.

Hints to handle install in a Python environment

Given when I ran pip to install numpy, I received this message:

pi@bench5:~/n1mm_view$ pip install numpy
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    For more information visit http://rptl.io/venv

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

Is there a recommended best practice to handle this (and something we may want to put in the install document)? Last time I did this on Bookwork, I believe I setup Python to not use environments onthe whole system. But I am not sure if that is the recommended step for everyone.

Some issues with headless.py

Jeff - I found some issues with headless and I needed to change not to make it run. I will send a pull request with my changes but I wanted to confirm something.

I could not figure out where the code to setup the directory not he RAM disk went then I realized you changed the INIT to create the directory at start-up. Is that the idea so the script does not have to create the directory? I added that back in as my original changes had it.

Is it OK to leave that in as a check so if someone does not have the INIT script to create the directory in RAM that the script will create them?

Thanks - Tom

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.