moved client and server into a single project

fixed a lot of bugs
added pushable button with long and short press
added antenna toggle script
This commit is contained in:
Michael Clemens 2021-11-27 23:58:18 +00:00
parent ecf516dd4b
commit 24fe5c873d
11 changed files with 297 additions and 57 deletions

View File

@ -15,6 +15,7 @@ This is how the display looks like in action:
![Photo of the Heltec board](hr50-remote-display.jpg "Photo of the Heltec board")
# Client
## Preconditions
@ -25,7 +26,6 @@ This is how the display looks like in action:
### Software
* Arduino IDE
* HR50-Api: [https://git.qrz.is/clemens/hr50-api]
### Libraries
@ -33,6 +33,7 @@ This is how the display looks like in action:
* ArduinoJson
* HTTPClient
* WiFi
* EasyButton
## Configuration
@ -41,7 +42,7 @@ Edit the following lines inside the Arduino sketch:
```
const char* ssid = "<ENTER_HERE_YOUR_WIFI_SSID>";
const char* password = "<PASSWORD>";
String api_url = "http://<ENTER_HERE_THE_IP_OF_YOUR API_SERVER>:5000/status";
String api_base_url = "http://<ENTER_HERE_THE_IP_OF_YOUR API_SERVER>:5000";
```
## Error handling
@ -49,3 +50,47 @@ String api_url = "http://<ENTER_HERE_THE_IP_OF_YOUR API_SERVER>:5000/status";
* If wifi is unavailable or misconfigured, the device will show "No Wifi!" on the screen
* HTTP Errors will also displayed on the OLED
* When you key up your TRX and therefore the HR50, no serial communication is possible. The latest gathered information will still be displayed on the screen but there will be a "(!)" in the third row as an indicator for this situation.
# Server
# Preconditions
You need a computer connected to the Hardrock-50 via USB, e.g. a Raspberry Pi. This system needs to be able to run Python and has to be connected to your network.
# Configuration
Open the file _hr50_rd_server.py_ and adapt the following lines to your environment:
```
serial_port = '/dev/ttyUSB0'
baud = 19200
```
# Execution
Executing this script will spawn a web server running on port 5000. This is not meant for production use and/or public exposure. Anyone able to access this server via the network can alter any settings on your HR50.
Run it as follows:
```
# cd server
# ./bootstrap.sh
```
# API Endpoints
There are two API endpoints available:
#### /exec_serial
This method allows you to send commands to the HR50. The commands can be taken from the HR50 operator's manual and do not require the trailing ";"
#### /exec_shell
This method allows you to execute shell commands on the server
#### /get_status
This method returns all avalable information in JSON

View File

@ -4,38 +4,45 @@
#include "heltec.h"
#include <ArduinoJson.h>
#include "hr50_fonts.h"
#include <EasyButton.h>
#define ESP_INTR_FLAG_DEFAULT 0
#define PRG_BUTTON 0
// Pin 0 is the built-in upper button
#define BUTTON_PIN 0
EasyButton button(BUTTON_PIN);
const char* ssid = "<WIFI-SSID>";
const char* password = "<WIFI-PASSWORD>";
String api_base_url = "http://<IP_OF_HR50_API>:5000";
//String api_button_url = api_base_url + "/?cmd=hrtu1";
String api_button_url = api_base_url + "/exec";
String api_status_url = api_base_url + "/status";
// Contruct API calls
String api_button_long = api_base_url + "/exec_serial";
String api_button_short = api_base_url + "/exec_shell";
String api_status_url = api_base_url + "/get_status";
int first_row = 0;
int second_row = 24;
int third_row = 48;
int left = 0;
int right = 128;
String cmd_status = "";
SemaphoreHandle_t semaphore = nullptr;
// milliseconds of button push until count as a long press
int duration = 1000;
void IRAM_ATTR handler(void* arg) {
xSemaphoreGiveFromISR(semaphore, NULL);
// This happens when the button will be pressed long
void long_button_press() {
cmd_status = send_http_request(api_button_long);
}
void button_task(void* arg) {
for (;;) {
if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdTRUE) {
cmd_status = send_http_request(api_button_url);
}
}
// This happens when the button will be pressed short
void short_button_press() {
cmd_status = send_http_request(api_button_short);
}
// Sends a HTTP request to the server and grabs the response
String send_http_request(String url) {
HTTPClient http;
String response = "";
@ -53,31 +60,27 @@ String send_http_request(String url) {
}
// Things that need to be done after booting the device
void setup() {
// Initialize Heltec display
Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, false /*Serial Enable*/);
Heltec.display->flipScreenVertically();
Heltec.display->setFont(DialogInput_plain_16);
// Log into Wifi and wait a bit
WiFi.begin(ssid, password);
delay(4000);
// Configure PRG button of the Heltec
semaphore = xSemaphoreCreateBinary();
// Setup the button GPIO pin
gpio_pad_select_gpio(PRG_BUTTON );
// Quite obvious, a button is a input
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT);
// Trigger the interrupt when going from HIGH -> LOW ( == pushing button)
gpio_set_intr_type(GPIO_NUM_0, GPIO_INTR_NEGEDGE);
// Associate button_task method as a callback
xTaskCreate(button_task, "prg_button_task", 4096, NULL, 10, NULL);
// Install ISR service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
// Add button handler to the ISR
gpio_isr_handler_add(GPIO_NUM_0, handler, NULL);
// Initialize the PRG button
button.begin();
// attach action to long button press
button.onPressedFor(duration, long_button_press);
// attach action to short button press
button.onPressed(short_button_press);
}
// Print error message on the display
void printError(String err) {
Heltec.display->clear();
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
@ -86,59 +89,54 @@ void printError(String err) {
}
// Extracts data from the server response, formats it
// and writes it to the OLED
void print_status_screen(JsonObject status_json) {
if ( status_json != NULL and status_json["PTT"].as<String>() != "ERR") {
if ( status_json != NULL ) { // and status_json["PTT"].as<String>() != "ERR") {
String band = status_json["BND"].as<String>();
String pep = status_json["PEP"].as<String>();
String avg = status_json["AVG"].as<String>();
String swr = status_json["SWR"].as<String>();
String voltage = status_json["VLT"].as<String>();
String vlt = status_json["VLT"].as<String>();
String power = pep + "W/" + avg + "W";
String ptt = status_json["PTT"].as<String>();
String temp = status_json["TMP"].as<String>();
String return_msg = status_json["RET"].as<String>();
// clear the display
Heltec.display->clear();
// print left column
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
Heltec.display->drawString(left, first_row, band );
Heltec.display->drawString(left, second_row, power );
if ( cmd_status == "" ) {
Heltec.display->drawString(left, third_row, voltage );
} else {
Heltec.display->drawString(left, third_row, cmd_status );
}
Heltec.display->drawString(left, first_row, band ); // top row
Heltec.display->drawString(left, second_row, power ); // mid row
Heltec.display->drawString(left, third_row, return_msg ); // bottom row
// print right column
Heltec.display->setTextAlignment(TEXT_ALIGN_RIGHT);
Heltec.display->drawString(right, first_row, swr );
Heltec.display->drawString(right, second_row, ptt );
Heltec.display->drawString(right, third_row, temp );
Heltec.display->drawString(right, first_row, swr ); // top row
Heltec.display->drawString(right, second_row, ptt ); // mid row
Heltec.display->drawString(right, third_row, temp ); // bottom row
Heltec.display->display();
}
else
{
// print a "(!)" centered in the third row
// this happens when the TRX/PA is transmitting
// or otherwise an empty response was send by the API
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
Heltec.display->drawString(58, third_row, "(!)" );
Heltec.display->display();
}
}
void loop() {
//Check WiFi connection status
if (WiFi.status() == WL_CONNECTED) {
// read out button state
button.read();
// get status data from server
String response = send_http_request(api_status_url);
// The server sends the status data as json string
// This converts the string to a json object
StaticJsonDocument<200> doc;
deserializeJson(doc, response);
JsonObject obj = doc.as<JsonObject>();
// print data to OLED screen
print_status_screen(obj);
// wait a bit
delay(500);
}
else {
printError("No WiFi!");
}
}
}

0
server/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
server/bootstrap.sh Executable file
View File

@ -0,0 +1,2 @@
export FLASK_APP=hr50_rd_server.py
flask run --host=0.0.0.0

179
server/hr50_rd_server.py Normal file
View File

@ -0,0 +1,179 @@
from flask import Flask, jsonify, request
import sys
import getopt
import time
import serial
from subprocess import check_output
import threading
app = Flask(__name__)
shell_cmd = "./scripts/toggle_antenna.sh"
serial_cmd = "hrtu1"
# The serial/USB port the HR50 is connected to
serial_port = '/dev/ttyUSB0'
# doesn't really matter but must match settings of the HR50
baud = 19200
# the active command that has been sent via the API
# If populated: next connection to the HR50 will be used
# to send the command
# If empty: next connection to the HR50 will be used
# to query status information
cmd_rcvd = ""
# time in milliseconds when we started to display a status message
msg_time = 0
# time in milliseconds a status message will be displayed
msg_duration = 3000
'''
bands = {
'6': '0',
'10': '1',
'12': '2',
'15': '3',
'17': '4',
'20': '5',
'30': '6',
'40': '7',
'60': '8',
'80': '9',
'160': '10'
}
'''
vlcd = {
'STA': '-',
'PTT': '-',
'BND': '-',
'VLT': '-',
'PEP': '-',
'AVG': '-',
'SWR': '-',
'TMP': '-',
'RET': 'ok'
}
'''
keying_methods = {
"off" : "0",
"ptt" : "1",
"cor" : "2",
"qrp" : "3"
}
'''
def current_milli_time():
return round(time.time() * 1000)
def get_serial(port, baud):
try:
ser = serial.Serial(port, baud, timeout=2)
return ser
except serial.serialutil.SerialException as e:
print("The following error has occured while opening the serial connecti on to {} with {} baud:".format(port, baud))
print(e)
sys.exit(2)
# sends a command to the
# Hardrock-50 via the serial interface
def send_cmd_via_serial(cmd):
ser = get_serial(serial_port, baud)
res = None
try:
command = cmd + ';'
ser.write(str.encode(command))
res = ser.readline().decode("utf-8").rstrip()
except Exception as e:
print("The following error has occured while sending the command {} to t he HR50:".format(port, baud))
print(e)
ser.close()
return res
# Executed two commands vie the serial interface,
# parsed the result ad polulates a dict with the
# collected information
def get_info():
global msg_time
ser = get_serial(serial_port, baud)
try:
ser.write(b'HRRX;')
time.sleep(0.5)
res = ser.readline().decode("utf-8").rstrip().replace(';', '').split(',')
except Exception as e:
print("The following error has occured while sending the command {} to the HR50:".format(port, baud))
print(e)
ser.close()
return None
if res and len(res) > 3:
vlcd['STA'] = res[0]
vlcd['PTT'] = res[1]
vlcd['BND'] = res[2]
vlcd['TMP'] = res[3]
vlcd['VLT'] = res[4]
ser.write(b'HRMX;')
time.sleep(0.5)
res = ser.readline().decode("utf-8").rstrip().split()
ser.close()
vlcd['PEP'] = res[1][1:]
vlcd['AVG'] = res[2][1:]
if res[3][1:] != "0":
vlcd['SWR'] = res[3][1:2] + "." + res[3][2:3]
else:
vlcd['RET'] = "(!)"
msg_time = current_milli_time()
def background_loop():
global cmd_rcvd
global msg_time
threading.Timer(3.0, background_loop).start()
if cmd_rcvd != "":
ret = ""
try:
send_cmd_via_serial(cmd_rcvd)
vlcd['RET'] = 'Tune'
msg_time = current_milli_time()
except Exception as e:
ret = "ERROR"
cmd_rcvd = ""
else:
get_info()
if msg_time != 0 and msg_time + msg_duration < current_milli_time() or vlcd['RET'] == "ok":
vlcd['RET'] = vlcd['VLT']
@app.route('/get_status')
def get_status():
return jsonify(vlcd)
@app.route('/exec_shell')
def exec_shell_command():
global msg_time
out = check_output([shell_cmd, ""])
if out:
vlcd['RET'] = out.decode("utf-8")
msg_time = current_milli_time()
return out
@app.route('/exec_serial')
def exec_serial_command():
global cmd_rcvd
cmd_rcvd = serial_cmd
return jsonify(isError= False,
message= "Success",
statusCode= 200,
data= "" ), 200
background_loop()

View File

@ -0,0 +1,16 @@
#!/usr/bin/bash
eval $(sudo usbrelay 2>/dev/null)
#echo $BITFT_1
if [ $BITFT_1 -eq 0 ]
then
sudo usbrelay BITFT_1=1 > /dev/null 2>&1
else
sudo usbrelay BITFT_1=0 > /dev/null 2>&1
fi
eval $(sudo usbrelay 2>/dev/null)
if [ $BITFT_1 -eq 0 ]
then
echo "Cobweb"
else
echo "Vertical"
fi