Skip to content
This repository was archived by the owner on Feb 18, 2025. It is now read-only.

Commit 3a6de9a

Browse files
committed
adding position and telemetry to db
1 parent 0d2925f commit 3a6de9a

File tree

15 files changed

+312
-200
lines changed

15 files changed

+312
-200
lines changed

balloon-tracker/hardware/README.md

+22-20
Large diffs are not rendered by default.

balloon-tracker/readme.md

+60-30
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,95 @@
11
![Banner](https://github.com/trackuino/trackuino/wiki/img/trackuino-banner-narrow.png)
22

3-
The software and hardware for Trackuino, an open-source APRS tracker based on the Arduino platform. It was designed primarily to track high altitude balloons, so it has other handy features like reading temperature sensors and a buzzer for acoustic location.
3+
The software and hardware for Trackuino-v2, an open-source APRS tracker based on the Arduino platform. It was designed primarily to track high altitude balloons, so it has other handy features like reading temperature sensors and a buzzer for acoustic location.
44

55
Trackuino is intended for use by licensed radio amateurs.
66

77
# Features
88

9-
- Arduino form factor (support for Arduino Nano, Uno, and Mega)
10-
- Fully modular, choose the daughterboards you want to use and print/buy them and the motherboard that fits your Arduino
9+
- Arduino form factor (support for Arduino Nano, others coming soon:tm:)
10+
- Fully modular, choose the daughterboard you want to use and print/buy them and the motherboard that fits your Arduino
1111
- Onboard and external LED headers for module status indications
1212
- Internal/external temperature sensors to read temperature in and outside the payload
1313
- Active/passive buzzer support to ease acoustic payload location
1414
- Support for custom additional analog and digital sensors
1515
- Open source (GPLv2 license), both software and hardware. In other words, do whatever you want with it: modify it, add it to your project, etc. as long as you open-source your modifications as well.
1616

17-
## Modules
17+
## Built-In Modules
1818

19-
Choose whatever modules you want to use and add them onto your motherboard.
19+
The following modules are included on the motherboard:
2020

2121
### GPS Module
2222

23-
- Any GPS module that outputs NMEA strings (e.g. SiRF, UBLOX, etc.) is supported
24-
- GPS used needs 5V, GND, RX, and TX pins
23+
- UBLOX MAX-M10S GPS chip
2524

26-
### GSM Cellular Module
25+
### MicroSD Card Local Storage
2726

28-
- Allows sending data to a GSM network (e.g. AT&T, T-Mobile, etc.) via web requests or SMS messages
29-
- Configuration for message format and destination phone number or URL
27+
- MicroSD card writing for an on-board copy of recorded data
28+
- Supports CSV format writing of data
29+
- Raw data dumping option
30+
- Custom pre-save formatting option (can convert raw analog input to temperature with given formula, etc.)
3031

31-
### Radio Module
32+
### LoRa Radio Module
3233

33-
- Radio: Radiometrix's HX1 (300 mW).
34-
- 1200 bauds AFSK using 8-bit PWM
35-
- Sends out standard APRS position messages (latitude, longitude, altitude, course, speed and time).
36-
- SMA female plug for radio out
34+
- Radio: RFM95W
35+
- 915MHz frequency (tuneable to others for different regions)
36+
- 20dBm output power
37+
- Used to send data in addition to daughterboard modules, allows 2-way communication
3738

38-
### SD Card Local Storage Module
39+
### Flight Termination Unit (FTU) Transmitter Module
3940

40-
- OpenLog SD Card writing for an on-board copy of recorded data
41-
- Supports CSV format writing of data
42-
- Raw data dumping option
43-
- Custom pre-save formatting option (can convert raw analog input to temperature with given formula, etc.)
41+
- Modified [315MHz transmitter](https://www.adafruit.com/product/1095) for sending digital FTU signals
4442

45-
### Buzzer Module
43+
### Buzzer
4644

4745
- Active buzzer for acoustic payload location
4846
- Supports GPS readings for variable buzzing based on altitude or satellite lock
4947

48+
### Temperature Sensors
49+
50+
- Internal and external temperature sensors
51+
- TMP36 analog temperature sensor
52+
53+
### LED Indicators
54+
55+
- Onboard and external LED headers for module status indications
56+
- Customizable LED blinking patterns for different module statuses
57+
58+
## Daughterboard Modules
59+
60+
In addition to the main motherboard, you can attach any one of the following daughterboards to the motherboard via the molex connector for additional functionality.
61+
62+
### GSM Cellular Module
63+
64+
**Note**: Work in progress
65+
66+
- Allows sending data to a GSM network (e.g. AT&T, T-Mobile, etc.) via web requests or SMS messages
67+
- Configuration for message format and destination phone number or URL
68+
69+
### HAM Radio APRS Module
70+
71+
- Radio: DORJI DRA818V
72+
- 1200 baud AFSK using 8-bit PWM
73+
- Sends out standard APRS position messages (latitude, longitude, altitude, course, speed and time), and telemetry data
74+
- SMA plug for radio out
75+
76+
### Iridium Satellite Module
77+
78+
- RockBLOCK 9603 Iridium satellite modem
79+
5080
# Setup
5181

52-
Use the `Download ZIP` button or [click here](https://github.com/EricAndrechek/trackuino-v2/archive/refs/heads/main.zip) to get the source code and board schematics.
82+
Use the `Download ZIP` button or [click here](https://github.com/EricAndrechek/trackuino-v2/archive/refs/heads/main.zip) to get the source code and board schematics. The hardware is designed in KiCad and the software is written in C++ for the Arduino platform.
5383

54-
## Choosing Boards
84+
Navigate to the `balloon-tracker/hardware` directory to find the KiCad files for the motherboard and daughterboard modules. Navigate to the `balloon-tracker/software` directory to find the Arduino sketch for the firmware.
5585

56-
Choose the motherboard you want to use based on what Arduino/MCU form factor you plan to use. The larger the Arduino/MCU and therefore motherboard, the more modules you will be able to add.
86+
## Choosing Boards
5787

58-
After selecting your motherboard, you can choose the modules you want to use. You can add as many modules as you want so long as they all fit on the motherboard. One of these modules needs to be the power daughterboard.
88+
Choose the motherboard you want to use based on what Arduino/MCU form factor you plan to use.
5989

60-
**Important**: Pay attention to the voltage your Arduino is using. If you are unsure, check [this page](https://learn.sparkfun.com/tutorials/arduino-comparison-guide/totally-tabular) to find out. If your MCU is 3.3V, you will need to print the 3.3V power daughterboard, if it is 5V, you will need to print the 5V power daughterboard.
90+
After selecting your motherboard, you can choose the daughterboard you want to use.
6191

62-
Select all motherboards and module daughter boards you would like to use, and find somewhere online to get them printed.
92+
Select all motherboards and daughterboards you would like to use, and find somewhere online to get them printed. You will need to open the KiCad files in KiCad and export the gerber files (and drill files) to send to a PCB printing service.
6393

6494
## Soldering
6595

@@ -75,15 +105,15 @@ Unzip the software in your sketches directory and load it up by double-clicking
75105

76106
The single most important configuration file is `config.h`. The file is self-documented. Here is where you set up your callsign, module settings, and more.
77107

78-
Some modules, like GSM, allow you to make additional configuration changes in their own custom, self-documented files so that you can do things like specify a JSON format for data to be sent through.
108+
Some daughterboards, like GSM, allow you to make additional configuration changes in their own custom, self-documented files so that you can do things like specify a JSON format for data to be sent through.
79109

80110
## Flashing
81111

82-
**Important**: When flashing the Arduino, remove the it from the motherboard. After flashing the firmware, you can plug it back in. The GPS, OpenLog, and the host computer share the same serial port on the AVR, so they will conflict when used together.
112+
**Important**: When flashing the Arduino, remove the it from the motherboard. After flashing the firmware, you can plug it back in. The GPS, microSD card module, and the host computer share the same serial port on the AVR, so they will conflict when used together.
83113

84114
Within the Arduino IDE, be sure to select the correct board type for your Arduino.
85115

86-
While trackuino.ino is opened in the Arduino IDE, select the `Program` tab and click the `Upload` button.
116+
While `software.ino` is opened in the Arduino IDE, select the `Program` tab and click the `Upload` button.
87117

88118
## Debugging
89119

ground-station/config.yaml

-8
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,6 @@ APRS IS:
5353
host: rotate.aprs2.net
5454
port: 14580
5555

56-
Telemetry:
57-
# Whether to send telemetry packets such as GS cpu, ram, etc
58-
enabled: true
59-
60-
# How often to send ground station telemetry packets (in seconds)
61-
# these do not show up on aprs.fi
62-
telemetry interval: 10
63-
6456
GPS:
6557
# Whether to use a USB GPS
6658
# If false, the ground station will not send any position update packets

ground-station/utilities/connection.py

+1-32
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,6 @@ def __init__(self):
5252
self.client.on_connect = self.on_connect
5353
self.client.on_disconnect = self.on_disconnect
5454

55-
if config.telemetry.enabled:
56-
# run daemon thread to send telemetry every config.telemetry.telemetry_interval seconds
57-
58-
from threading import Thread
59-
self.telemetry_thread = Thread(target=self.telemetry_daemon, daemon=True, name="Telemetry Daemon")
60-
self.telemetry_thread.daemon = True
61-
self.telemetry_thread.start()
62-
6355
def on_message(self, client, userdata, msg):
6456
print(parse_topic(msg.topic) + " " + msg.payload.decode())
6557

@@ -68,33 +60,10 @@ def on_connect(self, client, userdata, flags, rc):
6860

6961
def on_disconnect(self, client, userdata, rc):
7062
print("Disconnected - " + parse_rc(rc))
71-
72-
def send_telemetry(self):
73-
cpu = "{:.2f}".format(psutil.cpu_percent()) + "%"
74-
memory = "{:.2f}".format(psutil.virtual_memory().percent) + "%"
75-
total_memory = humanfriendly.format_size(psutil.virtual_memory().total)
76-
disk = "{:.2f}".format(psutil.disk_usage('/').percent) + "%"
77-
total_disk = humanfriendly.format_size(psutil.disk_usage('/').total)
78-
network = humanfriendly.format_size(psutil.net_io_counters().bytes_sent + psutil.net_io_counters().bytes_recv)
79-
uptime = humanfriendly.format_timespan(time.mktime(time.localtime()) - psutil.boot_time())
80-
81-
self.client.publish(f"{self.station}/cpu", cpu, qos=0, retain=True)
82-
self.client.publish(f"{self.station}/memory", memory, qos=0, retain=True)
83-
self.client.publish(f"{self.station}/total-memory", total_memory, qos=0, retain=True)
84-
self.client.publish(f"{self.station}/disk", disk, qos=0, retain=True)
85-
self.client.publish(f"{self.station}/total-disk", total_disk, qos=0, retain=True)
86-
self.client.publish(f"{self.station}/network", network, qos=0, retain=True)
87-
self.client.publish(f"{self.station}/uptime", uptime, qos=0, retain=True)
88-
89-
def telemetry_daemon(self):
90-
while True:
91-
self.send_telemetry()
92-
time.sleep(config.telemetry.telemetry_interval)
93-
9463

9564
# Main Loop
9665

9766
if __name__ == "__main__":
9867
client = GS_Client()
9968
while True:
100-
time.sleep(1)
69+
time.sleep(1)

tracking-dashboard/backend/api/data.py

+29-27
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,31 @@
77
# data upload endpoints
88

99
# upload should take data in the formats json and aprs, where data is replace with the aprs string or json object
10-
# {
11-
# "callsign": "N0CALL",
12-
# "ssid": 0,
13-
# "timestamp": "2021-01-01T00:00:00Z",
14-
# "type": "json",
15-
# "data": {
16-
# "callsign": "N0CALL",
17-
# "ssid": 0,
18-
# "symbol": "/",
19-
# "lat": 0.0,
20-
# "lon": 0.0,
21-
# "alt": 0.0,
22-
# "course": 0.0,
23-
# "speed": 0.0,
24-
# "comment": "test comment"
25-
# "telemetry": {
26-
# "battery": 0.0,
27-
# "temperature": 0.0,
28-
# "humidity": 0.0,
29-
# "pressure": 0.0
30-
# }
31-
# }
32-
# }
10+
example = {
11+
"callsign": "N0CALL",
12+
"ssid": 0,
13+
"timestamp": "2021-01-01T00:00:00Z",
14+
"type": "json",
15+
"data": {
16+
"callsign": "N0CALL",
17+
"ssid": 0,
18+
"symbol": "/",
19+
"lat": 0.0,
20+
"lon": 0.0,
21+
"alt": 0.0,
22+
"course": 0.0,
23+
"speed": 0.0,
24+
"comment": "test comment",
25+
"telemetry": {
26+
"battery": 0.0,
27+
"temperature": 0.0,
28+
"humidity": 0.0,
29+
"pressure": 0.0
30+
}
31+
}
32+
}
3333

34+
# JSON upload route
3435
@data_app.route('/upload', methods=['POST'])
3536
def api_upload():
3637
data_obj = Data()
@@ -40,7 +41,9 @@ def api_upload():
4041
try:
4142
data = request.get_json()
4243
except Exception as e:
43-
return "invalid json", 400
44+
# if data is not in json format, return error and show expected format with 400 status code
45+
# TODO: ideally prettier formatted json example?
46+
return "Data must be in json format. Example: " + str(example), 400
4447

4548
# accept upload data
4649
try:
@@ -49,8 +52,7 @@ def api_upload():
4952
return e, 400
5053

5154
# check if sender matches token
52-
if not data_obj.check_token(request.headers.get('Authorization')):
53-
return "invalid token", 401
55+
data_obj.check_token(request.headers.get('Authorization'))
5456

5557
# get ip address of client
5658
data_obj.get_client_ip(request)
@@ -69,4 +71,4 @@ def api_upload():
6971
elif status_code == 208:
7072
return "Data already exists", 208
7173
except Exception as e:
72-
return e, 400
74+
return e, 400

tracking-dashboard/backend/api/db.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66

77
@db_app.route('/db', methods=['DELETE'])
88
def api_db_delete():
9-
# TODO: call the delete method of the db class (need to figure out context of db class)
9+
# call the delete method of the db class
10+
# TODO: implement delete method (need to figure out context of db class) <-- I don't remember what I meant when I wrote this...
11+
# TODO: need authorization
1012
return "not implemented", 501
1113

1214
@db_app.route('/db', methods=['GET'])
1315
def api_db_get():
14-
# TODO: get the current db file and return it
16+
# get all data from the db and return the files as a downloadable zip
17+
# TODO: need to implement, probably need to run as a background job...
1518
return "not implemented", 501
1619

1720
# TODO: allow getting specific tables from the db and render them in the browser
18-
# maybe just redirect to pgadmin instead?
21+
# maybe just redirect to pgadmin instead?

tracking-dashboard/backend/api/server.py

+26-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# pull the latest version of the repo from github
1313
@server_app.route('/pull', methods=['GET'])
1414
def api_pull():
15+
# TODO: very janky and could break prod, maybe do something as a background job instead?
16+
1517
# ensure that the auth header is present and correct bearer token
1618
if 'Authorization' not in request.headers or request.headers.get('Authorization') != ('Bearer ' + config.push_key):
1719
return jsonify({'error': 'invalid authorization header'}), 401
@@ -35,11 +37,34 @@ def api_pull():
3537
return jsonify({'pull': pull_result.stdout.decode('utf-8'), 'install': install_result.stdout.decode('utf-8')})
3638

3739

40+
# allow getting and setting the config file from api route
41+
# dangerous - breaking change could disable the server
42+
@server_app.route('/config', methods=['GET', 'POST'])
43+
def api_config():
44+
# ensure that the auth header is present and correct bearer token
45+
if 'Authorization' not in request.headers or request.headers.get('Authorization') != ('Bearer ' + config.push_key):
46+
return jsonify({'error': 'invalid authorization header'}), 401
47+
48+
if request.method == 'GET':
49+
return jsonify(config)
50+
elif request.method == 'POST':
51+
# update the config with the new values
52+
new_config = request.get_json()
53+
for key in new_config:
54+
config[key] = new_config[key]
55+
56+
# write the new config to the config file
57+
with open('config.json', 'w') as config_file:
58+
json.dump(config, config_file, indent=4)
59+
60+
return jsonify(config)
61+
62+
3863
@server_app.route('/health', methods=['GET'])
3964
def api_health():
4065
# return current date and time
4166
to_return = {
4267
'status': 'ok',
4368
'time': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
4469
}
45-
return jsonify(to_return)
70+
return jsonify(to_return)

tracking-dashboard/backend/api/status.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
@status_app.route('/status', methods=['GET'])
1010
def status_api():
11+
# TODO
1112
# for now, we do Status().status every time
1213
# once we have a job scheduler, we can just return the .status value and not redefine Status() every time
13-
return jsonify(Status().status)
14+
return jsonify(Status().status)

0 commit comments

Comments
 (0)