Compare commits
183 Commits
gps
...
aprs_featu
Author | SHA1 | Date | |
---|---|---|---|
|
cf50be32d8 | ||
|
80d1d1f1f8 | ||
|
369969c453 | ||
|
03919a4bdf | ||
|
45deeaf142 | ||
|
30eb140b03 | ||
|
c18ba0ec69 | ||
|
f4452c57cd | ||
|
c343449d86 | ||
|
d61821899d | ||
|
3946a6735d | ||
|
44b22a9b0d | ||
|
e6c61dd72a | ||
|
6b49e069e8 | ||
|
67f2093ff3 | ||
|
b22cfdbab4 | ||
|
6153f7da1c | ||
|
b7cc6ca9cb | ||
|
5f2861f4c6 | ||
|
c7f4ddb159 | ||
|
d1c55feb56 | ||
|
18af7790b3 | ||
|
3ef9d6d085 | ||
|
bc327303a2 | ||
|
966cea92e9 | ||
|
4c0ce0cfe9 | ||
|
09da5caffd | ||
|
13fcdd4b49 | ||
|
dc30dad1a1 | ||
|
bdce3d76bc | ||
|
0ed0c3de8d | ||
|
3ac23db9b1 | ||
|
24909bcdc1 | ||
|
a8714c9a7d | ||
|
90982b88de | ||
|
bced48f0de | ||
|
04667f0255 | ||
|
20b6feabdd | ||
|
0ccd4868d6 | ||
|
99270cf3f1 | ||
|
b0c9603498 | ||
|
79e90246a5 | ||
|
f2107c9715 | ||
|
4f3ea5db20 | ||
|
ab16d54971 | ||
|
07faef5c1c | ||
|
8d5037c27d | ||
|
32e33a41e1 | ||
|
bee14eb7e4 | ||
|
12d13dc385 | ||
|
940b46fc2e | ||
|
7aaf7f9330 | ||
|
15dab47dd6 | ||
|
22a87ea3d5 | ||
|
b64ac525e2 | ||
|
17b343d29e | ||
|
66e77f42e7 | ||
|
57b982bf50 | ||
|
dcd770ff1f | ||
|
bb381f771d | ||
|
b35b1a2330 | ||
|
8dd804401b | ||
|
c864f7fdde | ||
|
0d82f375c3 | ||
|
3a428781be | ||
|
71ef495a4a | ||
|
71f0a9a3a4 | ||
|
2512a066eb | ||
|
dfa8f925d6 | ||
|
9e248125bb | ||
|
837d372bb3 | ||
|
861d364a99 | ||
|
1900923246 | ||
|
03fd6a7a0c | ||
|
5da9a07fb0 | ||
|
ee4bde0c1c | ||
|
21799178b2 | ||
|
b8cf08a114 | ||
|
409cd7d0f1 | ||
|
1e17b0c574 | ||
|
35323a467d | ||
|
3d8b452a89 | ||
|
442af74b5b | ||
|
16239d4f41 | ||
|
f7831a18f0 | ||
|
a7325cb301 | ||
|
26486e1215 | ||
|
f5cd21e0ad | ||
|
e4ec84b27f | ||
|
af4b3f4897 | ||
|
ce80103592 | ||
|
05c5cae519 | ||
|
9d30e94c86 | ||
|
97a8689c62 | ||
|
ad18b5d527 | ||
|
6701003c51 | ||
|
093ed72c2c | ||
|
ffb8b16187 | ||
|
160d11e651 | ||
|
e362fa7320 | ||
|
944f1ce697 | ||
|
5340e0b441 | ||
|
8b873b77fc | ||
|
c687ce3bec | ||
|
14b06b7f0a | ||
|
c0ebd67bd8 | ||
|
6bba07acf1 | ||
|
926883cde9 | ||
|
6c889b8a05 | ||
|
e4019d97d4 | ||
|
269d7920a6 | ||
|
4cff528c5a | ||
|
ca5baebda4 | ||
|
28265db71d | ||
|
d68dff222a | ||
|
16a1d81757 | ||
|
88a791ee7b | ||
|
cae4ad03d8 | ||
|
e5697d8e2c | ||
|
c4032ac036 | ||
|
e6ec4ae990 | ||
|
de2cb2c60f | ||
|
81e9d30287 | ||
|
b4d0dec3f5 | ||
|
a922af2f66 | ||
|
a57be9a5a4 | ||
|
a2c782cef5 | ||
|
ec783b210b | ||
|
bcafd4f3bc | ||
|
fc4c75b990 | ||
|
23c984552b | ||
|
c7a311e89b | ||
|
02db8076e6 | ||
|
5276cc437b | ||
|
0242b5ea2a | ||
|
b8d2c9c163 | ||
|
564fa239da | ||
|
92a01d782a | ||
|
9f82b33103 | ||
|
5172faf7f2 | ||
|
ca2d6c33d2 | ||
|
65ceefef06 | ||
|
51f710d683 | ||
|
f7cd165684 | ||
|
6eed6dc809 | ||
|
1202c78450 | ||
|
51d469768a | ||
|
5c7742c7d0 | ||
|
9c2a1f5b2b | ||
|
3ce8326209 | ||
|
9633b3249b | ||
|
7c5c69a450 | ||
|
f7c68a1f54 | ||
|
8d80600f43 | ||
|
122b1be148 | ||
|
41699e9489 | ||
|
6aed4f9e43 | ||
|
7764feb298 | ||
|
de6386e4f1 | ||
|
4dc58f7e01 | ||
|
78b7671126 | ||
|
0a0f38794b | ||
|
bbc5610226 | ||
|
f4d0908b78 | ||
|
a24cc0bea9 | ||
|
2912383d6c | ||
|
f875ac6999 | ||
|
4a08e7f284 | ||
|
04ae240924 | ||
|
b4e5170cf1 | ||
|
d55a23a42f | ||
|
64d8c16e2e | ||
|
9f79b8c69c | ||
|
7ac8c40ac0 | ||
|
8271f1bd33 | ||
|
9a57531e71 | ||
|
596f7b309e | ||
|
431639e17a | ||
|
3912657bb6 | ||
|
1ba5db5f3c | ||
|
091f8bd7d1 | ||
|
5a1414d5d6 | ||
|
f70fc26707 |
176
README.md
176
README.md
@ -1,177 +1,13 @@
|
||||
### This project has morphed into HBNet. It is still under heavy development and not ready for production. You can see it [here](https://github.com/kf7eel/hblink3/tree/hbnet). The example/development server can be found at [https://demo.hbnet.xyz](https://demo.hbnet.xyz).
|
||||
|
||||
# GPS/Data Application
|
||||
# "Fully Featured" HBLink3
|
||||
|
||||
This repository contains everything needed to decode DMR GPS packets and SMS for HBLink3. This application can act as a master or peer and receive data as a group call or private call. It is designed to work in a multi system/user network.
|
||||
This is my version of HBLink3 that I use in "production." I have merged the private-call branch here. I have also merged a modified implementation of uploading APRS locations for connected peers here (modified to "per master" configuration. APRS peer implementation originally by **IU7IGU**). Finally, I have included the GPS/Data Application here to support APRS position reports from some GPS enabled radios. All of this leads to the following features:
|
||||
|
||||
Files modified from original master branch of HBLink3:
|
||||
* Working Unit calls (private calls)
|
||||
* APRS position upload for connected repeaters and hotspots
|
||||
* APRS position upload from some GPS enabled radios
|
||||
|
||||
* bridge.py
|
||||
* config.py
|
||||
|
||||
#### Required modules
|
||||
|
||||
* pynmea2
|
||||
* aprslib
|
||||
* maidenhead
|
||||
|
||||
#### Optional Modules
|
||||
* Flask - Required for dashboard
|
||||
* smtplib - Required for sending email. If pip fails to install module, it may already be installed as most Linux distributions have this module by default.
|
||||
* traceback - If pip fails to install module, it may already be installed as most Linux distrobutions have this module by default.
|
||||
* slixmpp - Required for upcoming XMPP gateway.
|
||||
* folium - Required for mapping on dashboard.
|
||||
|
||||
This should work for DMR radios that send location data as a UTF-8 NMEA sentence. I am hopping to add support for more radios in the future.
|
||||
|
||||
### Differences in branches
|
||||
|
||||
* **GPS**: Contains the GPS/Data Application.
|
||||
* **aprs_features**: Contains the GPS/Data Application and a modified version of the APRS implementation for repeaters and hotspots by **IU7IGU**. (See [https://github.com/iu7igu/hblink3-aprs](https://github.com/iu7igu/hblink3-aprs) for his work). I combined these for convenience.
|
||||
|
||||
## Confirmed working:
|
||||
Actually tested
|
||||
|
||||
| Radio | GPS | SMS |
|
||||
|-------|:---:|:---:|
|
||||
| Anytone D878| YES | YES |
|
||||
| Anytone D578| YES | YES |
|
||||
| BTech DMR-6x2 | YES | Most likely |
|
||||
| MD-380 (MD380tools, no GPS) | - | YES |
|
||||
| MD-380 (stock firmware, GPS) | YES | Most likely |
|
||||
| MD-390 (stock firmware) | YES | YES |
|
||||
| Retevis RT73* | YES | YES |
|
||||
| Ailunce HD1 | YES | YES |
|
||||
|
||||
*RT73 must have unconfirmed data setting enabled.
|
||||
|
||||
## Highly suspected to work:
|
||||
Not tested yet, but will most likely work.
|
||||
|
||||
| Radio | GPS | SMS |
|
||||
|-------|:---:|:---:|
|
||||
| Anytone D868 | Most likely | Most likely |
|
||||
| TYT MD-2017 | Most likely | Likely |
|
||||
| TYT MD-9600 | Most likely | Likely |
|
||||
| Retevis RT8 | Most likely | Likely |
|
||||
|
||||
|
||||
## Tested, but with issues.
|
||||
Tested, but with bugs present.
|
||||
|
||||
| Radio | GPS | SMS |
|
||||
|-------|:---:|:---:|
|
||||
| Alinco DJ-MD5TGP | WIP | Most likely |
|
||||
| Motorola DP3601| WIP | WIP |
|
||||
|
||||
|
||||
## Would like to test:
|
||||
|
||||
Connect Systems GPS enabled radios
|
||||
|
||||
## Features
|
||||
|
||||
* Decode GPS locations and upload APRS position packets
|
||||
* Each user/DMR ID can customize APRS position
|
||||
* Ability to receive data as a private call or group call
|
||||
* Trigger a command or script via DMR SMS
|
||||
* Optional web dashboard to show APRS packets uploaded
|
||||
* Display bulletins sent via SMS on web dashboard
|
||||
|
||||
|
||||
## How it works
|
||||
|
||||
A user should configure their radio for the DMR ID of the application and private or group call. When a position is received by the application, it will extract the coordinates and create an APRS position packet. The application will find the callsign of the user based on the sending radio's DMR ID. It is essential to have an up to date subscriber_ids file for this to work. A predefined APRS SSID is appended to the callsign. The APRS location packet is then uploaded to APRS-IS. No setup is required beforehand on the part of the user. This is pretty much "plug and play."
|
||||
|
||||
For example, N0CALL has a DMR ID of 1234567. N0CALL's radio sends a position to the application. The application will query the subscriber_ids file for DMR ID 1234567. The result will be N0CALL. An APRS location pack is created and uploaded to APRS-IS.
|
||||
|
||||
## Individual user/DMR ID APRS settings
|
||||
|
||||
By default, all APRS positions will have an SSID of 15, a default comment, and the callsign of the DMR user. These default settings can be changed.
|
||||
|
||||
The comment, SSID, and icon can be set for each individual user/DMR ID the application sees. The application stores all the setting in a file. You may have different SSIDs, icons, and comments for different DMR IDs. This is done via DMR SMS using the following commands:
|
||||
|
||||
| Command | Description | Example |
|
||||
|-------|:---:|:---:|
|
||||
|**@SSID**|Change SSID of user callsign.|`@SSID 7`|
|
||||
|**@ICON**|Change the icon of the APRS position. *See [http://aprs.net/vm/DOS/SYMBOLS.HTM](http://aprs.net/vm/DOS/SYMBOLS.HTM) for icon list.|`@icon /p`|
|
||||
|**@COM**|Change the comment of the APRS.|`@COM This is a test comment.`|
|
||||
|**@MH**|Set you location by maidenhead grid square. Designed for radios with no GPS or that are not compatable yet.|`@MH DN97uk`|
|
||||
|**@BB**|Post a bulliten to the web dashboard.|`@BB This is a test bulletin.`|
|
||||
|**@[CALLSIGN W/ SSID] A-[MESSAGE]**|Send a message to another station via APRS.|`@N0CALL-15 A-This is a test.`|
|
||||
|**[EMAIL ADDRESS] E-[MESSAGE]**|Send an email to an email address.|`test@example.org E-This is a test.`|
|
||||
|
||||
|
||||
|
||||
Send a DMR SMS to the configured dmr_data_id in the application with the desired command followed by the value. For example, to change your icon to a dog, the command would be `@ICON /p` (see the icon table for values). Changing your SSID is as simple as `@SSID 7`, and `@COM Testing 123` will change the comment.
|
||||
|
||||
Sending `@BB Test` will result in a post to the bulletin board with the messaage of "Test".
|
||||
|
||||
|
||||
**To remove any of the stored values, just send the appropriate command without any input.** `@COM` will remove the stored comment, `@ICON` will remove the stored icon, and `@COM` will remove the strored comment. Any position now reports sent will have the default settings.
|
||||
|
||||
|
||||
## Web Dashboard
|
||||
|
||||
The web dashboard is completely optional. Python module flask is required for this to work. The web dashboard will display the last 15 positions of radios sent to APRS-IS. The dashboard will also sh user bulletin. A bulletin is a message sent via SMS that will display on the web dashboard. There are several uses for this, including: testing SMS functionality of radio, announcements, and moire. It is a novel feature. The page will automatically reload every 2 minutes. Setup is rather simple. Just modify the example config in the dashboard directory and rename it to dashboard_settings.py. Then start dashboard.py.
|
||||
|
||||
## APRS messaging
|
||||
|
||||
**At this time, only sending of messages from DMR SMS to APRS-IS is supported.** I find this feature very pointless because it will only go one way, but someone else may find it important. **Messages from sent from APRS-IS to DMR SMS will not work.** I have not written the code for this yet. It will likley be a long time before this is a possibility.
|
||||
|
||||
## APRS TOCALL
|
||||
|
||||
The project was granted a [tocall](http://www.aprs.org/aprs11/tocalls.txt) of **APHBLx** by Bob Bruniga, WB4APR. This will identify that your APRS position came from HBLink. The x on the end can be any letter/number. Here are the current designations of APHBLx:
|
||||
|
||||
* **APHBL3** - HBlink3 D-APRS gateway
|
||||
* **APHBLD** - DMRlink D-APRS gateway (the IPSC version of the project)
|
||||
* **_APHBLS_** - Planned, but not in use: HBLink3 via KISS TNC
|
||||
|
||||
## Configuration
|
||||
|
||||
See hblink_SAMPLE.cfg, rules_SAMPLE.py, and gps_data_SAMPLE.cfg for examples.
|
||||
|
||||
## Special thanks to:
|
||||
|
||||
**N0MJS** - For creating HBLink and dmr_utils. This project not possible without him.
|
||||
|
||||
**IU7IGU** - For creating APRS position beaconing for PEER connections.
|
||||
|
||||
**IV3JDV** - For helping debug SMS in Anytone radios.
|
||||
|
||||
**KD7LMN** - For pointing out a critical bug.
|
||||
|
||||
**KB5PBM** - For helping implement support for MD-380 type radios.
|
||||
|
||||
**EI7IG** - For writing the page explaining MD-380 type GPS packets.
|
||||
|
||||
**M0GLJ** - For assisting with Motorola testing.
|
||||
|
||||
## Resources for DMR data
|
||||
|
||||
I spent many hours looking at the following for this project. You may find these links useful.
|
||||
|
||||
https://github.com/travisgoodspeed/md380tools/issues/160
|
||||
|
||||
https://jpronans.github.io/ei7ig/dmr.html
|
||||
|
||||
http://cloud.dstar.su/files/G4KLX/MMDVM/MMDVM%20Specification%2020150922.pdf
|
||||
|
||||
https://wiki.brandmeister.network/index.php/NMEA_Location_Reporting
|
||||
|
||||
https://forums.radioreference.com/threads/motorola-lrrp-protocol.370081/
|
||||
|
||||
https://forums.radioreference.com/threads/lrrp-decoding.359575/
|
||||
|
||||
https://github.com/polkabana/go-dmr
|
||||
|
||||
https://github.com/nonoo/dmrshark
|
||||
|
||||
https://wiki.brandmeister.network/index.php/Compressed_Location_Reporting
|
||||
|
||||
All of the ETSI DMR documents (ETSI 102 361-1 through 361-4).
|
||||
|
||||
The Shark RF forums.
|
||||
**The same stuff (commands, settings, etc.) in the gps branch apply here as well.**
|
||||
|
||||
---
|
||||
### FOR SUPPORT, DISCUSSION, GETTING INVOLVED ###
|
||||
|
@ -42,7 +42,7 @@ from twisted.protocols.basic import NetstringReceiver
|
||||
from twisted.internet import reactor, task
|
||||
|
||||
# Things we import from the main hblink module
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases, aprs_upload
|
||||
from dmr_utils3.utils import bytes_3, int_id, get_alias
|
||||
from dmr_utils3 import decode, bptc, const
|
||||
import config
|
||||
@ -1183,6 +1183,8 @@ if __name__ == '__main__':
|
||||
logger.info('(REPORT) TCP Socket reporting not configured')
|
||||
|
||||
# HBlink instance creation
|
||||
# Run aprs_upload loop
|
||||
aprs_upload(CONFIG)
|
||||
logger.info('(GLOBAL) HBlink \'bridge.py\' -- SYSTEM STARTING...')
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['ENABLED']:
|
||||
|
@ -46,7 +46,7 @@ from twisted.protocols.basic import NetstringReceiver
|
||||
from twisted.internet import reactor, task
|
||||
|
||||
# Things we import from the main hblink module
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, config_reports, mk_aliases, acl_check, aprs_upload
|
||||
from dmr_utils3.utils import bytes_3, int_id, get_alias
|
||||
from dmr_utils3 import decode, bptc, const
|
||||
import config
|
||||
@ -281,6 +281,8 @@ if __name__ == '__main__':
|
||||
report_server = config_reports(CONFIG, reportFactory)
|
||||
|
||||
# HBlink instance creation
|
||||
# Run aprs_upload loop
|
||||
aprs_upload(CONFIG)
|
||||
logger.info('HBlink \'bridge_all.py\' -- SYSTEM STARTING...')
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['ENABLED']:
|
||||
|
13
bridge_gps_data.py
Executable file → Normal file
13
bridge_gps_data.py
Executable file → Normal file
@ -44,7 +44,7 @@ from twisted.protocols.basic import NetstringReceiver
|
||||
from twisted.internet import reactor, task
|
||||
|
||||
# Things we import from the main hblink module
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases
|
||||
from hblink import HBSYSTEM, OPENBRIDGE, systems, hblink_handler, reportFactory, REPORT_OPCODES, mk_aliases, aprs_upload, sendAprs
|
||||
from dmr_utils3.utils import bytes_3, int_id, get_alias
|
||||
from dmr_utils3 import decode, bptc, const
|
||||
import config
|
||||
@ -184,7 +184,7 @@ def dashboard_loc_write(call, lat, lon, time, comment):
|
||||
user_loc_file.close()
|
||||
logger.info('User location saved for dashboard')
|
||||
#logger.info(dash_entries)
|
||||
|
||||
|
||||
def dashboard_bb_write(call, dmr_id, time, bulletin):
|
||||
#try:
|
||||
dash_bb = ast.literal_eval(os.popen('cat ' + bb_file).read())
|
||||
@ -289,8 +289,7 @@ def user_setting_write(dmr_id, setting, value):
|
||||
user_dict_file.close()
|
||||
logger.info('User setting saved')
|
||||
f.close()
|
||||
packet_assembly = ''
|
||||
|
||||
packet_assembly = ''
|
||||
# Process SMS, do something bases on message
|
||||
|
||||
def process_sms(_rf_src, sms):
|
||||
@ -305,7 +304,7 @@ def process_sms(_rf_src, sms):
|
||||
elif '@COM' in sms:
|
||||
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), re.sub('@COM |@COM','',sms))
|
||||
elif '@PIN' in sms:
|
||||
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), int(re.sub('@PIN |@PIN','',sms)))
|
||||
user_setting_write(int_id(_rf_src), re.sub(' .*|@','',sms), int(re.sub('@PIN |@PIN','',sms)))
|
||||
# Write blank entry to cause APRS receive to look for packets for this station.
|
||||
elif '@APRS' in sms:
|
||||
user_setting_write(int_id(_rf_src), 'APRS', '')
|
||||
@ -1827,6 +1826,8 @@ if __name__ == '__main__':
|
||||
loc_file = CONFIG['GPS_DATA']['LOCATION_FILE']
|
||||
the_mailbox_file = CONFIG['GPS_DATA']['MAILBOX_FILE']
|
||||
emergency_sos_file = CONFIG['GPS_DATA']['EMERGENCY_SOS_FILE']
|
||||
# User APRS settings
|
||||
user_settings_file = CONFIG['GPS_DATA']['USER_SETTINGS_FILE']
|
||||
|
||||
# Check if user_settings (for APRS settings of users) exists. Creat it if not.
|
||||
if Path(user_settings_file).is_file():
|
||||
@ -1916,7 +1917,7 @@ if __name__ == '__main__':
|
||||
systems[system] = routerHBP(system, CONFIG, report_server)
|
||||
reactor.listenUDP(CONFIG['SYSTEMS'][system]['PORT'], systems[system], interface=CONFIG['SYSTEMS'][system]['IP'])
|
||||
logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
|
||||
#aprs_upload(CONFIG)
|
||||
aprs_upload(CONFIG)
|
||||
|
||||
def loopingErrHandle(failure):
|
||||
logger.error('(GLOBAL) STOPPING REACTOR TO AVOID MEMORY LEAK: Unhandled error in timed loop.\n %s', failure)
|
||||
|
11
config.py
Executable file → Normal file
11
config.py
Executable file → Normal file
@ -106,6 +106,7 @@ def build_config(_config_file):
|
||||
CONFIG['GLOBAL'] = {}
|
||||
CONFIG['REPORTS'] = {}
|
||||
CONFIG['LOGGER'] = {}
|
||||
CONFIG['APRS'] = {}
|
||||
CONFIG['GPS_DATA'] = {}
|
||||
CONFIG['ALIASES'] = {}
|
||||
CONFIG['SYSTEMS'] = {}
|
||||
@ -183,6 +184,15 @@ def build_config(_config_file):
|
||||
'STALE_TIME': config.getint(section, 'STALE_DAYS') * 86400,
|
||||
})
|
||||
|
||||
elif section == 'APRS':
|
||||
CONFIG['APRS'].update({
|
||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
||||
'CALLSIGN': config.get(section, 'CALLSIGN'),
|
||||
'REPORT_INTERVAL': config.getint(section, 'REPORT_INTERVAL'),
|
||||
'SERVER': config.get(section, 'SERVER'),
|
||||
'MESSAGE': config.get(section, 'MESSAGE')
|
||||
})
|
||||
|
||||
elif config.getboolean(section, 'ENABLED'):
|
||||
if config.get(section, 'MODE') == 'PEER':
|
||||
CONFIG['SYSTEMS'].update({section: {
|
||||
@ -279,6 +289,7 @@ def build_config(_config_file):
|
||||
CONFIG['SYSTEMS'].update({section: {
|
||||
'MODE': config.get(section, 'MODE'),
|
||||
'ENABLED': config.getboolean(section, 'ENABLED'),
|
||||
'APRS': config.getboolean(section, 'APRS'),
|
||||
'REPEAT': config.getboolean(section, 'REPEAT'),
|
||||
'MAX_PEERS': config.getint(section, 'MAX_PEERS'),
|
||||
'IP': gethostbyname(config.get(section, 'IP')),
|
||||
|
@ -151,14 +151,6 @@ EMAIL_PASSWORD: letmein
|
||||
SMTP_SERVER: smtp.gmail.com
|
||||
SMTP_PORT: 465
|
||||
|
||||
# The options below are required for operation of the dashboard and will cause errors in gps_data.py
|
||||
# if configured wrong. Leave them as default unless you know what you are doing.
|
||||
# If you do change, you must use absolute paths.
|
||||
LOCATION_FILE: /tmp/gps_data_user_loc.txt
|
||||
BULLETIN_BOARD_FILE: /tmp/gps_data_user_bb.txt
|
||||
MAILBOX_FILE: /tmp/gps_data_user_mailbox.txt
|
||||
EMERGENCY_SOS_FILE: /tmp/gps_data_user_sos.txt
|
||||
|
||||
# The following options are used for the dashboard. The dashboard is optional.
|
||||
# Title of the Dashboard
|
||||
DASHBOARD_TITLE: HBLink3 D-APRS Dashboard
|
||||
|
@ -1,11 +1,9 @@
|
||||
# Various configurable options used in the GPS/Data application.
|
||||
|
||||
# SMS message that triggers command
|
||||
|
||||
|
||||
cmd_list = {
|
||||
|
||||
'COMMAND' : 'echo "command to execute"',
|
||||
'LS' : 'ls -lsh',
|
||||
'UPTIME' : 'uptime'
|
||||
|
||||
}
|
||||
|
16
hblink-SAMPLE.cfg
Executable file → Normal file
16
hblink-SAMPLE.cfg
Executable file → Normal file
@ -46,6 +46,19 @@ SUB_ACL: DENY:1
|
||||
TGID_TS1_ACL: PERMIT:ALL
|
||||
TGID_TS2_ACL: PERMIT:ALL
|
||||
|
||||
# APRS - BY IU7IGU
|
||||
# Enabling "APRS" will configure APRS-Beaconing of Master's connection
|
||||
# like repeater and hotspots.
|
||||
# REPORT_INTERVAL in Minute (ALLOW only > 3 Minutes)
|
||||
# CALLSIGN: Callsign that will pubblish data on aprs server
|
||||
# MESSAGE: This message will print on APRS description together RX and TX Frequency
|
||||
|
||||
[APRS]
|
||||
ENABLED: False
|
||||
REPORT_INTERVAL: 15
|
||||
CALLSIGN:HB1LNK-11
|
||||
SERVER:euro.aprs2.net
|
||||
MESSAGE:Connesso ad HBLINK
|
||||
|
||||
# NOT YET WORKING: NETWORK REPORTING CONFIGURATION
|
||||
# Enabling "REPORT" will configure a socket-based reporting
|
||||
@ -154,6 +167,9 @@ TGID_ACL: PERMIT:ALL
|
||||
[MASTER-1]
|
||||
MODE: MASTER
|
||||
ENABLED: True
|
||||
# The APRS setting below has to do with the beaconing of PEER conections, and not GPS positions
|
||||
# from radios. This setting will not affect gps_data.py
|
||||
APRS: False
|
||||
REPEAT: True
|
||||
MAX_PEERS: 10
|
||||
EXPORT_AMBE: False
|
||||
|
209
hblink.py
209
hblink.py
@ -27,6 +27,9 @@ works stand-alone before troubleshooting any applications that use it. It has
|
||||
sufficient logging to be used standalone as a troubleshooting application.
|
||||
'''
|
||||
|
||||
# Added config option for APRS in the master config section. Will only send packets to APRS-IS if each master is enabled.
|
||||
# Modified by KF7EEL - 10-15-2020
|
||||
|
||||
# Specifig functions from modules we need
|
||||
from binascii import b2a_hex as ahex
|
||||
from binascii import a2b_hex as bhex
|
||||
@ -35,6 +38,9 @@ from hashlib import sha256, sha1
|
||||
from hmac import new as hmac_new, compare_digest
|
||||
from time import time
|
||||
from collections import deque
|
||||
import aprslib
|
||||
import os
|
||||
|
||||
|
||||
# Twisted is pretty important, so I keep it separate
|
||||
from twisted.internet.protocol import DatagramProtocol, Factory, Protocol
|
||||
@ -66,6 +72,124 @@ __email__ = 'n0mjs@me.com'
|
||||
# Global variables used whether we are a module or __main__
|
||||
systems = {}
|
||||
|
||||
open("nom_aprs","w").close
|
||||
|
||||
file_config=config.build_config('hblink.cfg')
|
||||
|
||||
#CONFIG = ''
|
||||
|
||||
def sendAprs():
|
||||
AIS = aprslib.IS(str(file_config['APRS']['CALLSIGN']), passwd=aprslib.passcode(str(file_config['APRS']['CALLSIGN'])), host=str(file_config['APRS']['SERVER']), port=14580)
|
||||
AIS.connect()
|
||||
f = open('nom_aprs', 'r')
|
||||
lines = f.readlines()
|
||||
if lines:
|
||||
for line in lines:
|
||||
if line != ' ':
|
||||
lat_verso = ''
|
||||
lon_verso = ''
|
||||
dati = line.split(":")
|
||||
d1_c = int(float(dati[4]))
|
||||
d2_c = int(float(dati[5]))
|
||||
|
||||
if d1_c < 0:
|
||||
d1 = abs(d1_c)
|
||||
dm1=abs(float(dati[4])) - d1
|
||||
dm1_s= float(dm1) * 60
|
||||
dm1_u="{:.4f}".format(dm1_s)
|
||||
if int(str(dm1_s).split(".")[0]) < 10:
|
||||
if d1 < 10 and d1 > -10:
|
||||
lat_utile='0'+str(d1)+'0'+str(dm1_u)
|
||||
else:
|
||||
lat_utile = str(d1)+'0'+str(dm1_u)
|
||||
else:
|
||||
if d1 < 10 and d1 > -10:
|
||||
lat_utile='0'+str(d1)+str(dm1_u)
|
||||
else:
|
||||
lat_utile = str(d1)+str(dm1_u)
|
||||
|
||||
lat_verso = 'S'
|
||||
|
||||
else:
|
||||
d1 = int(float(dati[4]))
|
||||
dm1=float(dati[4]) - d1
|
||||
dm1_s= float(dm1) * 60
|
||||
dm1_u="{:.4f}".format(dm1_s)
|
||||
if int(str(dm1_s).split(".")[0]) < 10:
|
||||
if int(str(dm1_s).split(".")[0]) < 10:
|
||||
if d1 < 10 and d1 > -10:
|
||||
lat_utile='0'+str(d1)+'0'+str(dm1_u)
|
||||
else:
|
||||
lat_utile = str(d1)+'0'+str(dm1_u)
|
||||
else:
|
||||
if d1 < 10 and d1 > -10:
|
||||
lat_utile='0'+str(d1)+str(dm1_u)
|
||||
else:
|
||||
lat_utile = str(d1)+str(dm1_u)
|
||||
lat_verso = 'N'
|
||||
|
||||
|
||||
if d2_c < 0:
|
||||
d2=abs(d2_c)
|
||||
dm2=abs(float(dati[5])) - d2
|
||||
dm2_s= float(dm2) * 60
|
||||
dm2_u="{:.3f}".format(dm2_s)
|
||||
if int(str(dm2_s).split(".")[0]) < 10:
|
||||
if d2 < 10 and d2 > -10:
|
||||
lon_utile = '00'+str(d2)+'0'+str(dm2_u)
|
||||
elif d2 < 100:
|
||||
lon_utile = '0'+str(d2)+'0'+str(dm2_u)
|
||||
else:
|
||||
lon_utile = str(d2)+'0'+str(dm2_s)
|
||||
else:
|
||||
if d2 < 10 and d2 > -10:
|
||||
lon_utile = '00'+str(d2)+str(dm2_u)
|
||||
elif d2 < 100:
|
||||
lon_utile = '0'+str(d2)+str(dm2_u)
|
||||
else:
|
||||
lon_utile = str(d2)+str(dm2_u)
|
||||
lon_verso = 'W'
|
||||
|
||||
else:
|
||||
d2=int(float(dati[5]))
|
||||
dm2=float(dati[5]) - d2
|
||||
dm2_s= float(dm2) * 60
|
||||
dm2_u="{:.3f}".format(dm2_s)
|
||||
if int(str(dm2_s).split(".")[0]) < 10:
|
||||
if d2 < 10 and d2 > -10:
|
||||
lon_utile = '00'+str(d2)+'0'+str(dm2_u)
|
||||
elif d2 < 100:
|
||||
lon_utile = '0'+str(d2)+'0'+str(dm2_u)
|
||||
else:
|
||||
lon_utile = str(d2)+'0'+str(dm2_s)
|
||||
else:
|
||||
if d2 < 10 and d2 > -10:
|
||||
lon_utile = '00'+str(d2)+str(dm2_u)
|
||||
elif d2 < 100:
|
||||
lon_utile = '0'+str(d2)+str(dm2_u)
|
||||
else:
|
||||
lon_utile = str(d2)+str(dm2_u)
|
||||
lon_verso = 'E'
|
||||
|
||||
rx_utile = dati[2][0:3]+'.'+dati[2][3:]
|
||||
tx_utile = dati[3][0:3]+'.'+dati[3][3:]
|
||||
|
||||
#AIS.sendall(str(dati[0])+">APRS,TCPIP*,qAC,"+str(file_config['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:-2]+lat_verso+"/"+str(lon_utile)[:-1]+lon_verso+"r"+str(file_config['APRS']['MESSAGE'])+' RX: '+str(rx_utile)+' TX: '+str(tx_utile))
|
||||
AIS.sendall(str(dati[0])+">APRS,TCPIP*,qAC,"+str(file_config['APRS']['CALLSIGN'])+":!"+str(lat_utile)[:7]+lat_verso+"/"+str(lon_utile)[:8]+lon_verso+"r"+str(file_config['APRS']['MESSAGE'])+' RX: '+str(rx_utile)[:8]+' TX: '+str(tx_utile)[:8]) # + ' CC: ' + str(_this_peer['COLORCODE']).decode('UTF-8'))
|
||||
logging.info('APRS INVIATO/APRS Sent')
|
||||
|
||||
def aprs_upload(config):
|
||||
if config['APRS']['ENABLED']:
|
||||
if int(config['APRS']['REPORT_INTERVAL']) >= 10:
|
||||
l=task.LoopingCall(sendAprs)
|
||||
interval_time = int(int(config['APRS']['REPORT_INTERVAL'])*60)
|
||||
l.start(interval_time)
|
||||
else:
|
||||
l=task.LoopingCall(sendAprs)
|
||||
l.start(15*60)
|
||||
logger.info('Report Time APRS to short')
|
||||
|
||||
|
||||
# Timed loop used for reporting HBP status
|
||||
def config_reports(_config, _factory):
|
||||
def reporting_loop(_logger, _server):
|
||||
@ -80,10 +204,10 @@ def config_reports(_config, _factory):
|
||||
|
||||
reporting = task.LoopingCall(reporting_loop, logger, report_server)
|
||||
reporting.start(_config['REPORTS']['REPORT_INTERVAL'])
|
||||
|
||||
return report_server
|
||||
|
||||
|
||||
|
||||
# Shut ourselves down gracefully by disconnecting from the masters and peers.
|
||||
def hblink_handler(_signal, _frame):
|
||||
for system in systems:
|
||||
@ -104,6 +228,7 @@ def acl_check(_id, _acl):
|
||||
# OPENBRIDGE CLASS
|
||||
#************************************************
|
||||
|
||||
|
||||
class OPENBRIDGE(DatagramProtocol):
|
||||
def __init__(self, _name, _config, _report):
|
||||
# Define a few shortcuts to make the rest of the class more readable
|
||||
@ -112,7 +237,9 @@ class OPENBRIDGE(DatagramProtocol):
|
||||
self._report = _report
|
||||
self._config = self._CONFIG['SYSTEMS'][self._system]
|
||||
self._laststrid = deque([], 20)
|
||||
|
||||
|
||||
|
||||
|
||||
def dereg(self):
|
||||
logger.info('(%s) is mode OPENBRIDGE. No De-Registration required, continuing shutdown', self._system)
|
||||
|
||||
@ -474,8 +601,19 @@ class HBSYSTEM(DatagramProtocol):
|
||||
and self._peers[_peer_id]['SOCKADDR'] == _sockaddr:
|
||||
logger.info('(%s) Peer is closing down: %s (%s)', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id))
|
||||
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
|
||||
if self._CONFIG['SYSTEMS'][self._system]['APRS']:
|
||||
#if self._config['APRS_ENABLED'] == True:
|
||||
fn = 'nom_aprs'
|
||||
f = open(fn)
|
||||
output = []
|
||||
for line in f:
|
||||
if not str(int_id(_peer_id)) in line:
|
||||
output.append(line)
|
||||
f.close()
|
||||
f = open(fn, 'w')
|
||||
f.writelines(output)
|
||||
f.close()
|
||||
del self._peers[_peer_id]
|
||||
|
||||
else:
|
||||
_peer_id = _data[4:8] # Configure Command
|
||||
if _peer_id in self._peers \
|
||||
@ -502,6 +640,48 @@ class HBSYSTEM(DatagramProtocol):
|
||||
|
||||
self.send_peer(_peer_id, b''.join([RPTACK, _peer_id]))
|
||||
logger.info('(%s) Peer %s (%s) has sent repeater configuration', self._system, _this_peer['CALLSIGN'], _this_peer['RADIO_ID'])
|
||||
#APRS IMPLEMENTATION
|
||||
conta = 0
|
||||
lista_blocco=['ysf', 'xlx', 'nxdn', 'dstar', 'echolink','p25', 'svx', 'l1nk']
|
||||
#if self._CONFIG['SYSTEMS']['APRS_ENABLED']['ENABLED'] and self._CONFIG['APRS']['ENABLED'] and not str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').isalpha() :
|
||||
# Check if master has APRS enabled instead of global.
|
||||
if self._CONFIG['SYSTEMS'][self._system]['APRS'] and not str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').isalpha() :
|
||||
file = open("nom_aprs","r")
|
||||
linee = file.readlines()
|
||||
file.close()
|
||||
for link in lista_blocco:
|
||||
if int(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '').find(link.upper())) == 0:
|
||||
conta = conta + 1
|
||||
if len(linee) > 0:
|
||||
logging.info('Leggo')
|
||||
for linea in linee:
|
||||
dati_l = linea.split(':')
|
||||
if str(_this_peer['RADIO_ID']) == str(dati_l[1]):
|
||||
conta = conta + 1
|
||||
|
||||
if conta == 0:
|
||||
file=open("nom_aprs",'a')
|
||||
if len(str(_this_peer['RADIO_ID'])) > 7:
|
||||
id_pr=int(str(_this_peer['RADIO_ID'])[-2:])
|
||||
callsign_u=str(_this_peer['CALLSIGN'].decode('UTF-8'))+"-"+str(id_pr)
|
||||
file.write(callsign_u.replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
|
||||
file.close()
|
||||
else:
|
||||
file.write(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
|
||||
file.close()
|
||||
else:
|
||||
if conta == 0:
|
||||
file=open("nom_aprs",'a')
|
||||
if len(str(_this_peer['RADIO_ID'])) > 7:
|
||||
id_pr=int(str(_this_peer['RADIO_ID'])[-2:])
|
||||
callsign_u=str(_this_peer['CALLSIGN'].decode('UTF-8'))+"-"+str(id_pr)
|
||||
file.write(callsign_u.replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
|
||||
file.close()
|
||||
else:
|
||||
file.write(str(_this_peer['CALLSIGN'].decode('UTF-8')).replace(' ', '')+ ":"+ str(_this_peer['RADIO_ID']) +":"+ str(_this_peer['RX_FREQ'].decode('UTF-8')) + ":" + str(_this_peer['TX_FREQ'].decode('UTF-8'))+ ":" + str(_this_peer['LATITUDE'].decode('UTF-8')) + ":" + str(_this_peer['LONGITUDE'].decode('UTF-8')) + "\n")
|
||||
file.close()
|
||||
|
||||
|
||||
else:
|
||||
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
|
||||
logger.warning('(%s) Peer info from Radio ID that has not logged in: %s', self._system, int_id(_peer_id))
|
||||
@ -519,18 +699,6 @@ class HBSYSTEM(DatagramProtocol):
|
||||
self.transport.write(b''.join([MSTNAK, _peer_id]), _sockaddr)
|
||||
logger.warning('(%s) Ping from Radio ID that is not logged in: %s', self._system, int_id(_peer_id))
|
||||
|
||||
elif _command == RPTO:
|
||||
_peer_id = _data[4:8]
|
||||
if _peer_id in self._peers \
|
||||
and self._peers[_peer_id]['CONNECTION'] == 'YES' \
|
||||
and self._peers[_peer_id]['SOCKADDR'] == _sockaddr:
|
||||
logger.info('(%s) Peer %s (%s) has send options: %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_peer_id), _data[8:])
|
||||
self.transport.write(b''.join([RPTACK, _peer_id]), _sockaddr)
|
||||
|
||||
elif _command == DMRA:
|
||||
_peer_id = _data[4:8]
|
||||
logger.info('(%s) Recieved DMR Talker Alias from peer %s, subscriber %s', self._system, self._peers[_peer_id]['CALLSIGN'], int_id(_rf_src))
|
||||
|
||||
else:
|
||||
logger.error('(%s) Unrecognized command. Raw HBP PDU: %s', self._system, ahex(_data))
|
||||
|
||||
@ -660,7 +828,6 @@ class HBSYSTEM(DatagramProtocol):
|
||||
self._stats['CONNECTION'] = 'YES'
|
||||
self._stats['CONNECTED'] = time()
|
||||
logger.info('(%s) Connection to Master Completed', self._system)
|
||||
|
||||
# If we are an XLX, send the XLX module request here.
|
||||
if self._config['MODE'] == 'XLXPEER':
|
||||
self.send_xlxmaster(self._config['RADIO_ID'], int(4000), self._config['MASTER_SOCKADDR'])
|
||||
@ -781,6 +948,8 @@ if __name__ == '__main__':
|
||||
import sys
|
||||
import os
|
||||
import signal
|
||||
import aprslib
|
||||
import threading
|
||||
|
||||
# Change the current directory to the location of the application
|
||||
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
|
||||
@ -802,12 +971,12 @@ if __name__ == '__main__':
|
||||
if cli_args.LOG_LEVEL:
|
||||
CONFIG['LOGGER']['LOG_LEVEL'] = cli_args.LOG_LEVEL
|
||||
logger = log.config_logging(CONFIG['LOGGER'])
|
||||
logger.info('\n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.\n')
|
||||
logger.info('APRS IMPLEMENTATION BY IU7IGU email: iu7igu@yahoo.com \n APRS per master config by KF7EEL - KF7EEL@qsl.net \n\nCopyright (c) 2013, 2014, 2015, 2016, 2018, 2019, 2020\n\tThe Regents of the K0USY Group. All rights reserved.')
|
||||
logger.debug('(GLOBAL) Logging system started, anything from here on gets logged')
|
||||
|
||||
# Set up the signal handler
|
||||
def sig_handler(_signal, _frame):
|
||||
logger.info('(GLOBAL) SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal))
|
||||
logger.info('(GLOBAL) SHUTDOWN: HBLINK IS TERMINATING WITH SIGNAL %s', str(_signal))
|
||||
hblink_handler(_signal, _frame)
|
||||
logger.info('(GLOBAL) SHUTDOWN: ALL SYSTEM HANDLERS EXECUTED - STOPPING REACTOR')
|
||||
reactor.stop()
|
||||
@ -826,6 +995,8 @@ if __name__ == '__main__':
|
||||
logger.info('(REPORT) TCP Socket reporting not configured')
|
||||
|
||||
# HBlink instance creation
|
||||
# Run aprs_upload loop
|
||||
aprs_upload(CONFIG)
|
||||
logger.info('(GLOBAL) HBlink \'HBlink.py\' -- SYSTEM STARTING...')
|
||||
for system in CONFIG['SYSTEMS']:
|
||||
if CONFIG['SYSTEMS'][system]['ENABLED']:
|
||||
@ -837,3 +1008,5 @@ if __name__ == '__main__':
|
||||
logger.debug('(GLOBAL) %s instance created: %s, %s', CONFIG['SYSTEMS'][system]['MODE'], system, systems[system])
|
||||
|
||||
reactor.run()
|
||||
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Install the required support programs
|
||||
apt-get install python3 python3-pip -y
|
||||
pip3 install -r requirements.txt
|
Loading…
Reference in New Issue
Block a user