mirror of
https://codeberg.org/mclemens/hr50-remote-display.git
synced 2024-09-27 22:06:09 -04:00
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:
parent
ecf516dd4b
commit
24fe5c873d
49
README.md
49
README.md
@ -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")
|
![Photo of the Heltec board](hr50-remote-display.jpg "Photo of the Heltec board")
|
||||||
|
|
||||||
|
# Client
|
||||||
|
|
||||||
## Preconditions
|
## Preconditions
|
||||||
|
|
||||||
@ -25,7 +26,6 @@ This is how the display looks like in action:
|
|||||||
### Software
|
### Software
|
||||||
|
|
||||||
* Arduino IDE
|
* Arduino IDE
|
||||||
* HR50-Api: [https://git.qrz.is/clemens/hr50-api]
|
|
||||||
|
|
||||||
### Libraries
|
### Libraries
|
||||||
|
|
||||||
@ -33,6 +33,7 @@ This is how the display looks like in action:
|
|||||||
* ArduinoJson
|
* ArduinoJson
|
||||||
* HTTPClient
|
* HTTPClient
|
||||||
* WiFi
|
* WiFi
|
||||||
|
* EasyButton
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ Edit the following lines inside the Arduino sketch:
|
|||||||
```
|
```
|
||||||
const char* ssid = "<ENTER_HERE_YOUR_WIFI_SSID>";
|
const char* ssid = "<ENTER_HERE_YOUR_WIFI_SSID>";
|
||||||
const char* password = "<PASSWORD>";
|
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
|
## 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
|
* If wifi is unavailable or misconfigured, the device will show "No Wifi!" on the screen
|
||||||
* HTTP Errors will also displayed on the OLED
|
* 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.
|
* 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
|
||||||
|
|
||||||
|
@ -4,38 +4,45 @@
|
|||||||
#include "heltec.h"
|
#include "heltec.h"
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include "hr50_fonts.h"
|
#include "hr50_fonts.h"
|
||||||
|
#include <EasyButton.h>
|
||||||
|
|
||||||
#define ESP_INTR_FLAG_DEFAULT 0
|
// Pin 0 is the built-in upper button
|
||||||
#define PRG_BUTTON 0
|
#define BUTTON_PIN 0
|
||||||
|
|
||||||
|
EasyButton button(BUTTON_PIN);
|
||||||
|
|
||||||
const char* ssid = "<WIFI-SSID>";
|
const char* ssid = "<WIFI-SSID>";
|
||||||
const char* password = "<WIFI-PASSWORD>";
|
const char* password = "<WIFI-PASSWORD>";
|
||||||
String api_base_url = "http://<IP_OF_HR50_API>:5000";
|
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";
|
// Contruct API calls
|
||||||
String api_status_url = api_base_url + "/status";
|
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 first_row = 0;
|
||||||
int second_row = 24;
|
int second_row = 24;
|
||||||
int third_row = 48;
|
int third_row = 48;
|
||||||
int left = 0;
|
int left = 0;
|
||||||
int right = 128;
|
int right = 128;
|
||||||
|
|
||||||
String cmd_status = "";
|
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) {
|
// This happens when the button will be pressed long
|
||||||
xSemaphoreGiveFromISR(semaphore, NULL);
|
void long_button_press() {
|
||||||
|
cmd_status = send_http_request(api_button_long);
|
||||||
}
|
}
|
||||||
|
|
||||||
void button_task(void* arg) {
|
// This happens when the button will be pressed short
|
||||||
for (;;) {
|
void short_button_press() {
|
||||||
if (xSemaphoreTake(semaphore, portMAX_DELAY) == pdTRUE) {
|
cmd_status = send_http_request(api_button_short);
|
||||||
cmd_status = send_http_request(api_button_url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Sends a HTTP request to the server and grabs the response
|
||||||
String send_http_request(String url) {
|
String send_http_request(String url) {
|
||||||
HTTPClient http;
|
HTTPClient http;
|
||||||
String response = "";
|
String response = "";
|
||||||
@ -53,31 +60,27 @@ String send_http_request(String url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Things that need to be done after booting the device
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// Initialize Heltec display
|
||||||
Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, false /*Serial Enable*/);
|
Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, false /*Serial Enable*/);
|
||||||
Heltec.display->flipScreenVertically();
|
Heltec.display->flipScreenVertically();
|
||||||
Heltec.display->setFont(DialogInput_plain_16);
|
Heltec.display->setFont(DialogInput_plain_16);
|
||||||
|
|
||||||
|
// Log into Wifi and wait a bit
|
||||||
WiFi.begin(ssid, password);
|
WiFi.begin(ssid, password);
|
||||||
delay(4000);
|
delay(4000);
|
||||||
|
|
||||||
// Configure PRG button of the Heltec
|
// Initialize the PRG button
|
||||||
semaphore = xSemaphoreCreateBinary();
|
button.begin();
|
||||||
// Setup the button GPIO pin
|
// attach action to long button press
|
||||||
gpio_pad_select_gpio(PRG_BUTTON );
|
button.onPressedFor(duration, long_button_press);
|
||||||
// Quite obvious, a button is a input
|
// attach action to short button press
|
||||||
gpio_set_direction(GPIO_NUM_0, GPIO_MODE_INPUT);
|
button.onPressed(short_button_press);
|
||||||
// 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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Print error message on the display
|
||||||
void printError(String err) {
|
void printError(String err) {
|
||||||
Heltec.display->clear();
|
Heltec.display->clear();
|
||||||
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
|
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) {
|
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 band = status_json["BND"].as<String>();
|
||||||
String pep = status_json["PEP"].as<String>();
|
String pep = status_json["PEP"].as<String>();
|
||||||
String avg = status_json["AVG"].as<String>();
|
String avg = status_json["AVG"].as<String>();
|
||||||
String swr = status_json["SWR"].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 power = pep + "W/" + avg + "W";
|
||||||
String ptt = status_json["PTT"].as<String>();
|
String ptt = status_json["PTT"].as<String>();
|
||||||
String temp = status_json["TMP"].as<String>();
|
String temp = status_json["TMP"].as<String>();
|
||||||
|
String return_msg = status_json["RET"].as<String>();
|
||||||
|
|
||||||
// clear the display
|
// clear the display
|
||||||
Heltec.display->clear();
|
Heltec.display->clear();
|
||||||
// print left column
|
// print left column
|
||||||
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
|
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
Heltec.display->drawString(left, first_row, band );
|
Heltec.display->drawString(left, first_row, band ); // top row
|
||||||
Heltec.display->drawString(left, second_row, power );
|
Heltec.display->drawString(left, second_row, power ); // mid row
|
||||||
if ( cmd_status == "" ) {
|
Heltec.display->drawString(left, third_row, return_msg ); // bottom row
|
||||||
Heltec.display->drawString(left, third_row, voltage );
|
|
||||||
} else {
|
|
||||||
Heltec.display->drawString(left, third_row, cmd_status );
|
|
||||||
}
|
|
||||||
// print right column
|
// print right column
|
||||||
Heltec.display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
Heltec.display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||||
Heltec.display->drawString(right, first_row, swr );
|
Heltec.display->drawString(right, first_row, swr ); // top row
|
||||||
Heltec.display->drawString(right, second_row, ptt );
|
Heltec.display->drawString(right, second_row, ptt ); // mid row
|
||||||
Heltec.display->drawString(right, third_row, temp );
|
Heltec.display->drawString(right, third_row, temp ); // bottom row
|
||||||
Heltec.display->display();
|
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() {
|
void loop() {
|
||||||
//Check WiFi connection status
|
//Check WiFi connection status
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
// read out button state
|
||||||
|
button.read();
|
||||||
|
// get status data from server
|
||||||
String response = send_http_request(api_status_url);
|
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;
|
StaticJsonDocument<200> doc;
|
||||||
deserializeJson(doc, response);
|
deserializeJson(doc, response);
|
||||||
JsonObject obj = doc.as<JsonObject>();
|
JsonObject obj = doc.as<JsonObject>();
|
||||||
|
// print data to OLED screen
|
||||||
print_status_screen(obj);
|
print_status_screen(obj);
|
||||||
|
// wait a bit
|
||||||
|
delay(500);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
printError("No WiFi!");
|
printError("No WiFi!");
|
||||||
}
|
}
|
||||||
}
|
}
|
0
server/__init__.py
Normal file
0
server/__init__.py
Normal file
BIN
server/__pycache__/__init__.cpython-37.pyc
Normal file
BIN
server/__pycache__/__init__.cpython-37.pyc
Normal file
Binary file not shown.
BIN
server/__pycache__/hr50_rd_server.cpython-37.pyc
Normal file
BIN
server/__pycache__/hr50_rd_server.cpython-37.pyc
Normal file
Binary file not shown.
BIN
server/__pycache__/hr50_server.cpython-37.pyc
Normal file
BIN
server/__pycache__/hr50_server.cpython-37.pyc
Normal file
Binary file not shown.
BIN
server/__pycache__/hr50api.cpython-37.pyc
Normal file
BIN
server/__pycache__/hr50api.cpython-37.pyc
Normal file
Binary file not shown.
2
server/bootstrap.sh
Executable file
2
server/bootstrap.sh
Executable 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
179
server/hr50_rd_server.py
Normal 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()
|
||||||
|
|
||||||
|
|
16
server/scripts/toggle_antenna.sh
Executable file
16
server/scripts/toggle_antenna.sh
Executable 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
|
Loading…
Reference in New Issue
Block a user