Added MacOS build system
|
@ -1,4 +1,6 @@
|
|||
.DS_Store
|
||||
Distribution
|
||||
|
||||
# Firmware and signing keys
|
||||
firmware/*.hex
|
||||
firmware/*.key
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
OpenModem Configuration Utility
|
||||
==========
|
||||
This is the graphical configuration utility for [OpenModem](https://github.com/markqvist/OpenModem). For packaged builds, see the [OpenModem page at unsigned.io](https://unsigned.io/openmodem).
|
||||
|
||||
## Dependencies:
|
||||
- Python 3
|
||||
- pyserial
|
||||
- cryptography
|
||||
- requests
|
||||
- psutil
|
||||
- pywebview
|
||||
|
||||
```
|
||||
# Install dependencies
|
||||
pip3 install pyserial cryptography requests psutil pywebview
|
||||
pip3 install pyserial requests psutil pywebview
|
||||
|
||||
# Run
|
||||
python3 openmodemconfig.py
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
find . -type f -name "*.py[co]" -delete
|
||||
find . -type d -name "__pycache__" -delete
|
||||
rm -r ./build
|
||||
rm -r ./dist
|
||||
|
||||
python3 setup.py py2app
|
||||
cp -rv ./public ./dist/openmodemconfig.app/Contents/Resources/
|
||||
mv ./dist/openmodemconfig.app ./dist/OpenModem\ Config.app
|
After Width: | Height: | Size: 4.4 MiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 294 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 22 KiB |
|
@ -8,8 +8,6 @@ from time import sleep
|
|||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from socketserver import ThreadingMixIn
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
# from cryptography.hazmat.backends import default_backend
|
||||
# from cryptography.hazmat.primitives import hashes
|
||||
import hashlib
|
||||
import base64
|
||||
import psutil
|
||||
|
@ -19,6 +17,10 @@ import random
|
|||
import os
|
||||
import sys
|
||||
|
||||
if sys.platform.startswith('darwin'):
|
||||
import webview.platforms.cocoa
|
||||
import pkg_resources.py2_warn
|
||||
|
||||
portlist = []
|
||||
volumelist = []
|
||||
kiss_interface = None
|
||||
|
@ -909,9 +911,7 @@ def install_entropy_source(path):
|
|||
|
||||
|
||||
def get_port():
|
||||
# TODO: Change
|
||||
#return random.randrange(40000,49999,1)
|
||||
return 48031
|
||||
return random.randrange(40000,49999,1)
|
||||
|
||||
def start_server():
|
||||
retries = 0
|
||||
|
@ -919,24 +919,46 @@ def start_server():
|
|||
|
||||
while not server_started and retries < 100:
|
||||
try:
|
||||
retries += 1
|
||||
list_serial_ports()
|
||||
port = get_port()
|
||||
server_address = ("127.0.0.1", port)
|
||||
httpd = ThreadedHTTPServer(server_address, appRequestHandler)
|
||||
threading.Thread(target=httpd.serve_forever).start()
|
||||
print(("Server running on port "+str(port)))
|
||||
retval = webview.create_window('OpenModem Configuration', 'http://localhost:'+str(port)+'/', width=575, height=600)
|
||||
if retval == None:
|
||||
os._exit(0)
|
||||
server_started = True
|
||||
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
print("Exception while starting server: "+str(e))
|
||||
pass
|
||||
|
||||
print("Could not start server, exiting")
|
||||
exit()
|
||||
webview.create_window('OpenModem Configuration', 'http://localhost:'+str(port)+'/', width=575, height=600)
|
||||
webview.start()
|
||||
|
||||
os._exit(0)
|
||||
|
||||
def main():
|
||||
if sys.platform.startswith('darwin'):
|
||||
try:
|
||||
from Foundation import NSBundle
|
||||
bundle = NSBundle.mainBundle()
|
||||
if bundle:
|
||||
app_name = "OpenModem Configuration"
|
||||
app_info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
|
||||
if app_info:
|
||||
app_info['CFBundleName'] = app_name
|
||||
app_info["CFBundleGetInfoString"] = "unsigned.io"
|
||||
app_info["CFBundleLongVersionString"] = "unsigned.io"
|
||||
app_info["NSHumanReadableCopyright"] = "unsigned.io"
|
||||
app_info["CFBundleShortVersionString"] = "1.0.0"
|
||||
app_info["CFBundleVersion"] = "1.0.0"
|
||||
app_info["CFBundleIdentifier"] = "io.unsigned.openmodemconfig"
|
||||
app_info["CFBundleExecutable"] = "Test"
|
||||
print(app_info)
|
||||
print(app_info["CFBundleExecutable"])
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
include_path = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
os.chdir(include_path)
|
||||
list_serial_ports()
|
||||
|
|
|
@ -1,948 +0,0 @@
|
|||
import serial
|
||||
from serial.tools import list_ports
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import struct
|
||||
from time import sleep
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
|
||||
from SocketServer import ThreadingMixIn
|
||||
from urlparse import urlparse, parse_qs
|
||||
import base64
|
||||
import psutil
|
||||
import threading
|
||||
import webview
|
||||
import random
|
||||
import os
|
||||
import sys
|
||||
import md5
|
||||
|
||||
portlist = []
|
||||
volumelist = []
|
||||
kiss_interface = None
|
||||
|
||||
class RNS():
|
||||
@staticmethod
|
||||
def log(msg):
|
||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||
timestamp = time.time()
|
||||
logstring = "["+time.strftime(logtimefmt)+"] "+msg
|
||||
print(logstring)
|
||||
|
||||
@staticmethod
|
||||
def hexrep(data, delimit=True):
|
||||
delimiter = ":"
|
||||
if not delimit:
|
||||
delimiter = ""
|
||||
hexrep = delimiter.join("{:02x}".format(ord(c)) for c in data)
|
||||
return hexrep
|
||||
|
||||
@staticmethod
|
||||
def prettyhexrep(data):
|
||||
delimiter = ""
|
||||
hexrep = "<"+delimiter.join("{:02x}".format(ord(c)) for c in data)+">"
|
||||
return hexrep
|
||||
|
||||
class Interface:
|
||||
IN = False
|
||||
OUT = False
|
||||
FWD = False
|
||||
RPT = False
|
||||
name = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
class KISS():
|
||||
FEND = chr(0xC0)
|
||||
FESC = chr(0xDB)
|
||||
TFEND = chr(0xDC)
|
||||
TFESC = chr(0xDD)
|
||||
CMD_UNKNOWN = chr(0xFE)
|
||||
CMD_DATA = chr(0x00)
|
||||
CMD_TXDELAY = chr(0x01)
|
||||
CMD_P = chr(0x02)
|
||||
CMD_SLOTTIME = chr(0x03)
|
||||
CMD_TXTAIL = chr(0x04)
|
||||
CMD_FULLDUPLEX = chr(0x05)
|
||||
CMD_SETHARDWARE = chr(0x06)
|
||||
CMD_SAVE_CONFIG = chr(0x07)
|
||||
CMD_READY = chr(0x0F)
|
||||
CMD_AUDIO_PEAK = chr(0x12)
|
||||
CMD_OUTPUT_GAIN = chr(0x09)
|
||||
CMD_INPUT_GAIN = chr(0x0A)
|
||||
CMD_PASSALL = chr(0x0B)
|
||||
CMD_LOG_PACKETS = chr(0x0C)
|
||||
CMD_GPS_MODE = chr(0x0D)
|
||||
CMD_BT_MODE = chr(0x0E)
|
||||
CMD_SERIAL_BAUDRATE = chr(0x10)
|
||||
CMD_EN_DIAGS = chr(0x13)
|
||||
CMD_MODE = chr(0x14)
|
||||
CMD_PRINT_CONFIG = chr(0xF0)
|
||||
CMD_LED_INTENSITY = chr(0x08)
|
||||
CMD_RETURN = chr(0xFF)
|
||||
|
||||
ADDR_E_MAJ_VERSION = chr(0x00)
|
||||
ADDR_E_MIN_VERSION = chr(0x01)
|
||||
ADDR_E_CONF_VERSION = chr(0x02)
|
||||
ADDR_E_P = chr(0x03)
|
||||
ADDR_E_SLOTTIME = chr(0x04)
|
||||
ADDR_E_PREAMBLE = chr(0x05)
|
||||
ADDR_E_TAIL = chr(0x06)
|
||||
ADDR_E_LED_INTENSITY = chr(0x07)
|
||||
ADDR_E_OUTPUT_GAIN = chr(0x08)
|
||||
ADDR_E_INPUT_GAIN = chr(0x09)
|
||||
ADDR_E_PASSALL = chr(0x0A)
|
||||
ADDR_E_LOG_PACKETS = chr(0x0B)
|
||||
ADDR_E_CRYPTO_LOCK = chr(0x0C)
|
||||
ADDR_E_GPS_MODE = chr(0x0D)
|
||||
ADDR_E_BLUETOOTH_MODE = chr(0x0E)
|
||||
ADDR_E_SERIAL_BAUDRATE = chr(0x0F)
|
||||
ADDR_E_CHECKSUM = chr(0x10)
|
||||
ADDR_E_END = chr(0x20)
|
||||
|
||||
CONFIG_GPS_OFF = chr(0x00)
|
||||
CONFIG_GPS_AUTODETECT = chr(0x01)
|
||||
CONFIG_GPS_REQUIRED = chr(0x02)
|
||||
|
||||
CONFIG_BLUETOOTH_OFF = chr(0x00)
|
||||
CONFIG_BLUETOOTH_AUTODETECT = chr(0x01)
|
||||
CONFIG_BLUETOOTH_REQUIRED = chr(0x02)
|
||||
|
||||
MODE_AFSK_300 = chr(0x01)
|
||||
MODE_AFSK_1200 = chr(0x02)
|
||||
MODE_AFSK_2400 = chr(0x03)
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(chr(0xdb), chr(0xdb)+chr(0xdd))
|
||||
data = data.replace(chr(0xc0), chr(0xdb)+chr(0xdc))
|
||||
return data
|
||||
|
||||
class KISSInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
speed = None
|
||||
databits = None
|
||||
parity = None
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
self.port = port
|
||||
self.speed = speed
|
||||
self.databits = databits
|
||||
self.parity = serial.PARITY_NONE
|
||||
self.stopbits = stopbits
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
self.audiopeak = 0
|
||||
self.has_decode = False
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
|
||||
self.preamble = preamble if preamble != None else 350;
|
||||
self.txtail = txtail if txtail != None else 20;
|
||||
self.persistence = persistence if persistence != None else 64;
|
||||
self.slottime = slottime if slottime != None else 20;
|
||||
|
||||
self.modem_mode = None
|
||||
|
||||
self.config_p = None
|
||||
self.config_slottime = None
|
||||
self.config_preamble = None
|
||||
self.config_tail = None
|
||||
self.config_led_intensity = None
|
||||
self.config_output_gain = None
|
||||
self.config_input_gain = None
|
||||
self.config_passall = None
|
||||
self.config_log_packets = None
|
||||
self.config_crypto_lock = None
|
||||
self.config_gps_mode = None
|
||||
self.config_bluetooth_mode = None
|
||||
self.config_serial_baudrate = None
|
||||
self.config_valid = False
|
||||
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = serial.PARITY_EVEN
|
||||
|
||||
if parity.lower() == "o" or parity.lower() == "odd":
|
||||
self.parity = serial.PARITY_ODD
|
||||
|
||||
try:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = serial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port "+self.port)
|
||||
raise e
|
||||
|
||||
if self.serial.is_open:
|
||||
# Allow time for interface to initialise before config
|
||||
sleep(2.2)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
self.interface_ready = True
|
||||
RNS.log("KISS interface configured")
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
|
||||
def askForPeak(self):
|
||||
kiss_command = KISS.FEND+KISS.CMD_AUDIO_PEAK+b'\x01'+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not ask for peak data")
|
||||
|
||||
def displayPeak(self, peak):
|
||||
peak_value = struct.unpack("b", peak)
|
||||
self.audiopeak = peak_value[0]
|
||||
|
||||
def setPreamble(self, preamble):
|
||||
#preamble_ms = preamble
|
||||
#preamble = int(preamble_ms / 10)
|
||||
if preamble < 0:
|
||||
preamble = 0
|
||||
if preamble > 255:
|
||||
preamble = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_TXDELAY+chr(preamble)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface preamble to "+str(preamble)+" (command value "+str(preamble)+")")
|
||||
|
||||
def setTxTail(self, txtail):
|
||||
#txtail_ms = txtail
|
||||
#txtail = int(txtail_ms / 10)
|
||||
if txtail < 0:
|
||||
txtail = 0
|
||||
if txtail > 255:
|
||||
txtail = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_TXTAIL+chr(txtail)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface TX tail to "+str(txtail)+" (command value "+str(txtail)+")")
|
||||
|
||||
def setPersistence(self, persistence):
|
||||
if persistence < 0:
|
||||
persistence = 0
|
||||
if persistence > 255:
|
||||
persistence = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_P+chr(persistence)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface persistence to "+str(persistence))
|
||||
|
||||
def setSlotTime(self, slottime):
|
||||
#slottime_ms = slottime
|
||||
#slottime = int(slottime_ms / 10)
|
||||
if slottime < 0:
|
||||
slottime = 0
|
||||
if slottime > 255:
|
||||
slottime = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_SLOTTIME+chr(slottime)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface slot time to "+str(slottime)+" (command value "+str(slottime)+")")
|
||||
|
||||
def setFlowControl(self, flow_control):
|
||||
kiss_command = KISS.FEND+KISS.CMD_READY+chr(0x01)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
if (flow_control):
|
||||
raise IOError("Could not enable KISS interface flow control")
|
||||
else:
|
||||
raise IOError("Could not enable KISS interface flow control")
|
||||
|
||||
def setInputGain(self, gain):
|
||||
if gain < 0:
|
||||
gain = 0
|
||||
if gain > 255:
|
||||
gain = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_INPUT_GAIN+chr(gain)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface input gain to "+str(gain))
|
||||
|
||||
def setOutputGain(self, gain):
|
||||
if gain < 0:
|
||||
gain = 0
|
||||
if gain > 255:
|
||||
gain = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_OUTPUT_GAIN+chr(gain)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface input gain to "+str(gain))
|
||||
|
||||
|
||||
def setLEDIntensity(self, val):
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 255:
|
||||
val = 255
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_LED_INTENSITY+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface LED intensity to "+str(val))
|
||||
|
||||
|
||||
def setGPSMode(self, val):
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 2:
|
||||
val = 2
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_GPS_MODE+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface GPS mode to "+str(val))
|
||||
|
||||
def setBluetoothMode(self, val):
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 2:
|
||||
val = 2
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_BT_MODE+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface BT mode to "+str(val))
|
||||
|
||||
def setBaudrate(self, val):
|
||||
if val < 1:
|
||||
val = 1
|
||||
if val > 12:
|
||||
val = 12
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_SERIAL_BAUDRATE+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface baudrate to "+str(gain))
|
||||
|
||||
def setBaudrate(self, val):
|
||||
if val < 1:
|
||||
val = 1
|
||||
if val > 12:
|
||||
val = 12
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_SERIAL_BAUDRATE+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface baudrate to "+str(gain))
|
||||
|
||||
def setPassall(self, val):
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 1:
|
||||
val = 1
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_PASSALL+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface passall to "+str(gain))
|
||||
|
||||
def setLogToSD(self, val):
|
||||
if val < 0:
|
||||
val = 0
|
||||
if val > 1:
|
||||
val = 1
|
||||
|
||||
kiss_command = KISS.FEND+KISS.CMD_LOG_PACKETS+chr(val)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not configure KISS interface logtosd to "+str(gain))
|
||||
|
||||
|
||||
def saveConfig(self):
|
||||
kiss_command = KISS.FEND+KISS.CMD_SAVE_CONFIG+chr(0x01)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not send save config command")
|
||||
|
||||
def enableDiagnostics(self):
|
||||
kiss_command = KISS.FEND+KISS.CMD_EN_DIAGS+chr(0x01)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not enable KISS interface diagnostics")
|
||||
|
||||
def retrieveConfig(self):
|
||||
kiss_command = KISS.FEND+KISS.CMD_PRINT_CONFIG+chr(0x01)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not ask for config data")
|
||||
|
||||
def disableDiagnostics(self):
|
||||
kiss_command = KISS.FEND+KISS.CMD_EN_DIAGS+chr(0x00)+KISS.FEND
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("Could not disable KISS interface diagnostics")
|
||||
os._exit(0)
|
||||
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.has_decode = True
|
||||
RNS.log("Decoded packet");
|
||||
|
||||
def processConfig(self, data):
|
||||
md5sum = md5.new()
|
||||
md5sum.update(data[:16])
|
||||
md5_result = md5sum.digest()
|
||||
|
||||
print("Config data: "+RNS.hexrep(data[:16]))
|
||||
print("Config chks: "+RNS.hexrep(data[16:]))
|
||||
print("Config calc: "+RNS.hexrep(md5_result))
|
||||
|
||||
if md5_result == data[16:]:
|
||||
print("Config checksum match")
|
||||
self.config_p = data[ord(KISS.ADDR_E_P)]
|
||||
self.config_slottime = data[ord(KISS.ADDR_E_SLOTTIME)]
|
||||
self.config_preamble = data[ord(KISS.ADDR_E_PREAMBLE)]
|
||||
self.config_tail = data[ord(KISS.ADDR_E_TAIL)]
|
||||
self.config_led_intensity = data[ord(KISS.ADDR_E_LED_INTENSITY)]
|
||||
self.config_output_gain = data[ord(KISS.ADDR_E_OUTPUT_GAIN)]
|
||||
self.config_input_gain = data[ord(KISS.ADDR_E_INPUT_GAIN)]
|
||||
self.config_passall = data[ord(KISS.ADDR_E_PASSALL)]
|
||||
self.config_log_packets = data[ord(KISS.ADDR_E_LOG_PACKETS)]
|
||||
self.config_crypto_lock = data[ord(KISS.ADDR_E_CRYPTO_LOCK)]
|
||||
self.config_gps_mode = data[ord(KISS.ADDR_E_GPS_MODE)]
|
||||
self.config_bluetooth_mode = data[ord(KISS.ADDR_E_BLUETOOTH_MODE)]
|
||||
self.config_serial_baudrate = data[ord(KISS.ADDR_E_SERIAL_BAUDRATE)]
|
||||
self.config_valid = True
|
||||
else:
|
||||
print("Invalid checksum")
|
||||
self.config_valid = False
|
||||
|
||||
def processOutgoing(self,data):
|
||||
pass
|
||||
|
||||
def queue(self, data):
|
||||
pass
|
||||
|
||||
def process_queue(self):
|
||||
pass
|
||||
|
||||
def readLoop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = ""
|
||||
config_buffer = ""
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
while self.serial.is_open:
|
||||
if self.serial.in_waiting:
|
||||
byte = self.serial.read(1)
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (in_frame and byte == KISS.FEND and command == KISS.CMD_PRINT_CONFIG):
|
||||
in_frame = False
|
||||
self.processConfig(config_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
data_buffer = ""
|
||||
config_buffer = ""
|
||||
elif (in_frame and len(data_buffer) < 611):
|
||||
if (len(data_buffer) == 0 and command == KISS.CMD_UNKNOWN):
|
||||
command = byte
|
||||
elif (command == KISS.CMD_DATA):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+byte
|
||||
elif (command == KISS.CMD_PRINT_CONFIG):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
config_buffer = config_buffer+byte
|
||||
elif (command == KISS.CMD_AUDIO_PEAK):
|
||||
self.displayPeak(byte)
|
||||
elif (command == KISS.CMD_MODE):
|
||||
self.modem_mode = byte
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||
data_buffer = ""
|
||||
in_frame = False
|
||||
command = KISS.CMD_UNKNOWN
|
||||
escape = False
|
||||
sleep(0.2)
|
||||
self.askForPeak()
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e))
|
||||
RNS.log("The interface "+str(self.name)+" is now offline.")
|
||||
raise e
|
||||
close_device()
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
|
||||
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
|
||||
"""Handle requests threaded via mixin"""
|
||||
|
||||
class appRequestHandler(BaseHTTPRequestHandler):
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
def json_headers(request):
|
||||
request.send_response(200)
|
||||
request.send_header("Content-Type", "text/json")
|
||||
request.end_headers()
|
||||
|
||||
def do_GET(request):
|
||||
global kiss_interface, keyfile_exists, entropy_source_exists, aes_disabled
|
||||
|
||||
if (request.path == "/"):
|
||||
request.send_response(302)
|
||||
request.send_header("Location", "/app/")
|
||||
request.end_headers()
|
||||
|
||||
if (request.path == "/favicon.ico"):
|
||||
#request.path = "/app/favicon.ico"
|
||||
request.send_response(404)
|
||||
|
||||
if (request.path == "/getports"):
|
||||
request.json_headers()
|
||||
request.wfile.write(json.dumps(list_serial_ports()).encode("utf-8"))
|
||||
|
||||
if (request.path == "/getvolumes"):
|
||||
request.json_headers()
|
||||
request.wfile.write(json.dumps(list_volumes()).encode("utf-8"))
|
||||
|
||||
if (request.path == "/saveconfig"):
|
||||
request.json_headers()
|
||||
kiss_interface.saveConfig()
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path == "/getconfig"):
|
||||
request.json_headers()
|
||||
if kiss_interface and kiss_interface.config_valid:
|
||||
configData = {
|
||||
"preamble": ord(kiss_interface.config_preamble),
|
||||
"tail": ord(kiss_interface.config_tail),
|
||||
"p": ord(kiss_interface.config_p),
|
||||
"slottime": ord(kiss_interface.config_slottime),
|
||||
"led_intensity": ord(kiss_interface.config_led_intensity),
|
||||
"output_gain": ord(kiss_interface.config_output_gain),
|
||||
"input_gain": ord(kiss_interface.config_input_gain),
|
||||
"passall": ord(kiss_interface.config_passall),
|
||||
"log_packets": ord(kiss_interface.config_log_packets),
|
||||
"crypto_lock": ord(kiss_interface.config_crypto_lock),
|
||||
"gps_mode": ord(kiss_interface.config_gps_mode),
|
||||
"bluetooth_mode": ord(kiss_interface.config_bluetooth_mode),
|
||||
"serial_baudrate": ord(kiss_interface.config_serial_baudrate),
|
||||
"modem_mode": ord(kiss_interface.modem_mode)
|
||||
}
|
||||
request.wfile.write(json.dumps({"response":"ok", "config":configData}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"fail"}).encode("utf-8"))
|
||||
|
||||
if (request.path == "/getpeak"):
|
||||
request.json_headers()
|
||||
request.wfile.write(json.dumps({"response":"ok", "peak":kiss_interface.audiopeak, "decode": kiss_interface.has_decode}).encode("utf-8"))
|
||||
kiss_interface.has_decode = False
|
||||
|
||||
if (request.path.startswith("/disconnect")):
|
||||
close_device()
|
||||
|
||||
if (request.path.startswith("/connect")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_port = query["port"][0]
|
||||
q_baud = int(query["baud"][0])
|
||||
|
||||
if (open_device(q_port, q_baud)):
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"failed"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/volumeinit")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_path = query["path"][0]
|
||||
|
||||
if (volume_init(q_path)):
|
||||
request.wfile.write(json.dumps({"response":"ok", "key_installed": keyfile_exists, "aes_disabled":aes_disabled}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"failed"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setled")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setLEDIntensity(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setingain")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setInputGain(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setoutgain")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setOutputGain(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setpersistence")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setPersistence(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setpreamble")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setPreamble(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/settail")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setTxTail(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setslottime")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setSlotTime(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setbaudrate")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setBaudrate(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setpassall")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setPassall(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setlogtosd")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setLogToSD(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setgpsmode")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setGPSMode(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/setbluetoothmode")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = int(query["val"][0])
|
||||
kiss_interface.setBluetoothMode(q_val)
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
|
||||
if (request.path == "/aesenable"):
|
||||
request.json_headers()
|
||||
if aes_enable():
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"fail"}).encode("utf-8"))
|
||||
|
||||
if (request.path == "/aesdisable"):
|
||||
request.json_headers()
|
||||
if aes_disable():
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"fail"}).encode("utf-8"))
|
||||
|
||||
if (request.path == "/generatekey"):
|
||||
request.json_headers()
|
||||
if generate_key():
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"fail"}).encode("utf-8"))
|
||||
|
||||
if (request.path.startswith("/loadkey")):
|
||||
request.json_headers()
|
||||
query = parse_qs(urlparse(request.path).query)
|
||||
q_val = query["val"][0]
|
||||
if load_key(q_val):
|
||||
request.wfile.write(json.dumps({"response":"ok"}).encode("utf-8"))
|
||||
else:
|
||||
request.wfile.write(json.dumps({"response":"fail"}).encode("utf-8"))
|
||||
|
||||
|
||||
|
||||
if (request.path.startswith("/app/")):
|
||||
path = request.path.replace("/app/", "")
|
||||
file = None
|
||||
|
||||
if (path == ""):
|
||||
file = "index.html"
|
||||
else:
|
||||
file = path
|
||||
|
||||
base, extension = os.path.splitext(file)
|
||||
request.send_response(200)
|
||||
|
||||
if (extension == ".css"):
|
||||
request.send_header("Content-Type", "text/css")
|
||||
if (extension == ".js"):
|
||||
request.send_header("Content-Type", "text/javascript")
|
||||
if (extension == ".html"):
|
||||
request.send_header("Content-Type", "text/html")
|
||||
|
||||
request.end_headers()
|
||||
|
||||
requestpath = "./public/"+file
|
||||
|
||||
fh = open(requestpath, "rb")
|
||||
request.wfile.write(fh.read())
|
||||
fh.close()
|
||||
|
||||
def list_serial_ports():
|
||||
ports = list_ports.comports()
|
||||
portlist = []
|
||||
for port in ports:
|
||||
portlist.insert(0, port.device)
|
||||
|
||||
return portlist
|
||||
|
||||
def list_volumes():
|
||||
partitions = psutil.disk_partitions()
|
||||
volumelist = []
|
||||
for partition in partitions:
|
||||
if partition.mountpoint != "/" and partition.mountpoint != "C://" and not partition.mountpoint.startswith("/private") and not partition.mountpoint.startswith("/Volumes/Time Machine Backups"):
|
||||
RNS.log("Found partition:")
|
||||
RNS.log("\t"+str(partition))
|
||||
volumelist.append(partition.mountpoint)
|
||||
|
||||
return volumelist
|
||||
|
||||
def open_device(port, baud):
|
||||
global kiss_interface
|
||||
try:
|
||||
kiss_interface = KISSInterface(None, "OpenModem", port, baud, 8, "N", 1, None, None, None, None, False)
|
||||
kiss_interface.enableDiagnostics()
|
||||
kiss_interface.retrieveConfig()
|
||||
return True
|
||||
except Exception as e:
|
||||
#raise e
|
||||
return False
|
||||
|
||||
def close_device():
|
||||
os._exit(0)
|
||||
|
||||
keyfile_exists = False
|
||||
entropy_source_exists = False
|
||||
aes_disabled = False
|
||||
volume_ok = False
|
||||
volume_path = None
|
||||
def volume_init(path):
|
||||
global keyfile_exists, entropy_source_exists, volume_ok, volume_path, aes_disabled
|
||||
|
||||
volume_ok = False
|
||||
RNS.log("Volume init: "+path)
|
||||
|
||||
if os.path.isdir(path+"/OpenModem"):
|
||||
RNS.log("OpenModem data directory exists")
|
||||
else:
|
||||
RNS.log("OpenModem data directory does not exist, creating")
|
||||
try:
|
||||
os.mkdir(path+"/OpenModem")
|
||||
RNS.log("Directory created")
|
||||
except Exception as e:
|
||||
RNS.log("Could not create directory")
|
||||
volume_ok = False
|
||||
return False
|
||||
|
||||
if os.path.isfile(path+"/OpenModem/entropy.source"):
|
||||
entropy_source_exists = True
|
||||
RNS.log("Entropy source installed")
|
||||
else:
|
||||
RNS.log("Entropy source is not installed, installing...")
|
||||
if install_entropy_source(path+"/OpenModem/"):
|
||||
entropy_source_exists = True
|
||||
else:
|
||||
entropy_source_exists = False
|
||||
|
||||
if os.path.isfile(path+"/OpenModem/aes128.key"):
|
||||
keyfile_exists = True
|
||||
RNS.log("AES-128 key installed")
|
||||
else:
|
||||
RNS.log("AES-128 key is not installed")
|
||||
keyfile_exists = False
|
||||
|
||||
if os.path.isfile(path+"/OpenModem/aes128.disable"):
|
||||
aes_disabled = True
|
||||
RNS.log("AES-128 is disabled")
|
||||
else:
|
||||
RNS.log("AES-128 is allowed")
|
||||
aes_disabled = False
|
||||
|
||||
volume_ok = True
|
||||
volume_path = path + "/OpenModem/"
|
||||
RNS.log("Volume path is "+volume_path)
|
||||
return True
|
||||
|
||||
def generate_key():
|
||||
global volume_ok, volume_path
|
||||
if volume_ok:
|
||||
RNS.log("Generating new AES-128 key in "+volume_path+"...")
|
||||
try:
|
||||
file = open(volume_path+"aes128.key", "w")
|
||||
file.write(os.urandom(128/8))
|
||||
file.close()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
RNS.log("Could not generate key")
|
||||
return False
|
||||
|
||||
def load_key(keydata):
|
||||
global volume_ok, volume_path
|
||||
if volume_ok:
|
||||
RNS.log("Loading supplied key onto "+volume_path+"...")
|
||||
try:
|
||||
key = base64.b64decode(keydata)
|
||||
file = open(volume_path+"aes128.key", "w")
|
||||
file.write(key)
|
||||
file.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
raise e
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def aes_disable():
|
||||
global volume_ok, volume_path
|
||||
if volume_ok:
|
||||
open(volume_path+"aes128.disable", 'a').close()
|
||||
RNS.log("Disabling AES-128")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def aes_enable():
|
||||
global volume_ok, volume_path
|
||||
if volume_ok:
|
||||
if os.path.isfile(volume_path+"aes128.disable"):
|
||||
os.remove(volume_path+"aes128.disable")
|
||||
RNS.log("Allowing AES-128")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def install_entropy_source(path):
|
||||
RNS.log("Installing entropy source in "+path+"...")
|
||||
try:
|
||||
megabytes_to_write = 32
|
||||
bytes_per_block = 1024
|
||||
bytes_written = 0
|
||||
file = open(path+"entropy.source", "a")
|
||||
while bytes_written < megabytes_to_write*1024*1024:
|
||||
file.write(os.urandom(bytes_per_block))
|
||||
bytes_written = bytes_written + bytes_per_block
|
||||
file.close()
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
RNS.log("Could not install entropy source")
|
||||
return False
|
||||
|
||||
|
||||
def get_port():
|
||||
return random.randrange(40000,49999,1)
|
||||
|
||||
def start_server():
|
||||
retries = 0
|
||||
server_started = False
|
||||
|
||||
while not server_started and retries < 100:
|
||||
try:
|
||||
list_serial_ports()
|
||||
port = get_port()
|
||||
server_address = ("127.0.0.1", port)
|
||||
httpd = ThreadedHTTPServer(server_address, appRequestHandler)
|
||||
threading.Thread(target=httpd.serve_forever).start()
|
||||
print("Server running on port "+str(port))
|
||||
retval = webview.create_window('OpenModem Configuration', 'http://localhost:'+str(port)+'/', width=575, height=600)
|
||||
if retval == None:
|
||||
os._exit(0)
|
||||
|
||||
except Exception as e:
|
||||
retries += 1
|
||||
|
||||
print("Could not start server, exiting")
|
||||
exit()
|
||||
|
||||
|
||||
def main():
|
||||
include_path = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
os.chdir(include_path)
|
||||
list_serial_ports()
|
||||
list_volumes()
|
||||
if (len(sys.argv) == 1):
|
||||
start_server()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,4 +0,0 @@
|
|||
OpenModem Config Utility
|
||||
==========
|
||||
|
||||
This is the graphical configuration utility for [OpenModem](https://github.com/markqvist/OpenModem). For packaged builds, see the [OpenModem page at unsigned.io](https://unsigned.io/openmodem).
|
|
@ -0,0 +1,25 @@
|
|||
# Usage:
|
||||
# python setup.py py2app
|
||||
#
|
||||
# Manually add public folder to app resources
|
||||
|
||||
import os
|
||||
from setuptools import setup
|
||||
|
||||
def tree(src):
|
||||
return [(root, map(lambda f: os.path.join(root, f), files))
|
||||
for (root, dirs, files) in os.walk(os.path.normpath(src))]
|
||||
|
||||
APP = ['openmodemconfig.py']
|
||||
DATA_FILES = []
|
||||
OPTIONS = {'argv_emulation': False,
|
||||
'includes': ['WebKit', 'Foundation', 'webview'],
|
||||
'iconfile': 'gfx/AppIcon.icns',
|
||||
}
|
||||
|
||||
setup(
|
||||
app=APP,
|
||||
data_files=DATA_FILES,
|
||||
options={'py2app': OPTIONS},
|
||||
setup_requires=['py2app'],
|
||||
)
|