Initial Combination
Start the combination process between:d3505230f6and the at-telnet portion of39c20b83b1
This commit is contained in:
64
README (2).md
Normal file
64
README (2).md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Simple Web Admin Interface for Quectel Modem using RJ45 Boards
|
||||||
|
Simple Admin / Monitoring web UI for Quectel modems that are connected via a RGMII Ethernet interface (aka a "RJ45 to M.2" or "Ethernet to M.2" adapter board). Such as <a href="https://www.aliexpress.us/item/3256804672394777.html">Generic RJ45 Board</a> or the <a href="https://www.aliexpress.us/item/3256805527880876.html">MCUZone board</a>
|
||||||
|
|
||||||
|
This heavily relies on the work of <a href="https://github.com/natecarlson/">Nate</a> building on top of <a href="https://github.com/natecarlson/quectel-rgmii-at-command-client/tree/main/at_telnet_daemon">at_telnet_daemon</a> which is required prerequisite install before this will work.
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
Working in ADB is complex and running additional items not from the factory can be dangerous. Please run this with caution and be warned this comes "AS IS" without warranty and I will not be responsible for anything that happens in result of using this project.
|
||||||
|
|
||||||
|
## Tested Quectel Modems
|
||||||
|
Currently Only the RM520 has been tested and determined working, I will be doing additional tests the For the RM502.
|
||||||
|
|
||||||
|
If you are able to test on other modems and get it working, feel free to PR.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* ADB access to your modem
|
||||||
|
* Installing Nate's at_telnet_daemon
|
||||||
|
|
||||||
|
## Installation Automated
|
||||||
|
Script will do everything but setup Nate's at_telnet_daemon
|
||||||
|
```bash
|
||||||
|
adb shell wget -P /tmp https://raw.githubusercontent.com/rbflurry/quectel-rgmii-simpleadmin/main/install_on_modem.sh
|
||||||
|
adb shell chmod +x /tmp/install_on_modem.sh
|
||||||
|
adb shell sh /tmp/install_on_modem.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation DIY
|
||||||
|
```bash
|
||||||
|
adb push quectel-rgmii-simpleadmin /usrdata/simpleadmin
|
||||||
|
adb shell chmod +x /usrdata/simpleadmin/scripts/* /usrdata/simpleadmin/www/cgi-bin/* /usrdata/simpleadmin/ttl/ttl-override
|
||||||
|
adb shell mount -o remount,rw /
|
||||||
|
adb shell cp /usrdata/simpleadmin/systemd/* /lib/systemd/system
|
||||||
|
adb shell systemctl daemon-reload
|
||||||
|
adb shell ln -s /lib/systemd/system/simpleadmin_httpd.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell ln -s /lib/systemd/system/simpleadmin_generate_status.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell ln -s /lib/systemd/system/ttl-override.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell mount -o remount,ro /
|
||||||
|
adb shell systemctl start simpleadmin_generate_status
|
||||||
|
adb shell systemctl start simpleadmin_httpd
|
||||||
|
adb shell systemctl start ttl-override
|
||||||
|
```
|
||||||
|
|
||||||
|
## Access Simple Admin
|
||||||
|
This will launch on port 8080 by default, you are welcome to change that if you do not desire to use the QCMAP_CLI in the simpleadmin_generate_status.service file.
|
||||||
|
|
||||||
|
Launch your browser to http://192.168.225.1:8080
|
||||||
|
|
||||||
|
The backend and frontend will automatically update every 30 seconds. Will implement ways to change the update time in the future but will need some additional users testing to see if this is stable enough.
|
||||||
|
|
||||||
|
### Access Notice!
|
||||||
|
This is not password protected at the moment, please be careful if you are not CGNAT and have a public IP as this will be available to the public
|
||||||
|
|
||||||
|
## Note About TTL Mod
|
||||||
|
If you are currently using Nate's TTL-Override, please remove that systemd service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell /etc/initscripts/ttl-override stop
|
||||||
|
adb shell mount -o remount,rw /
|
||||||
|
adb shell rm -v /etc/initscripts/ttl-override /lib/systemd/system/ttl-override.service /lib/systemd/system/multi-user.target.wants/ttl-override.service
|
||||||
|
adb shell mount -o remount,ro /
|
||||||
|
adb shell systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
This heavily uses the AT Command Parsing Scripts (Basically a copy with minor tweaks) of Dairyman's Rooter Source https://github.com/ofmodemsandmen/ROOterSource2203
|
||||||
95
README (3).md
Normal file
95
README (3).md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# AT Telnet Daemon for Quectel Modem
|
||||||
|
|
||||||
|
This will provide a telnet interface to the AT command port of Quectel modems that are connected via a RGMII Ethernet interface (aka a "RJ45 to M.2" or "Ethernet to M.2" adapter board). It is an alternative to the ETH AT command interface that Quectel provides, which is a bit flaky and requires a custom client.
|
||||||
|
|
||||||
|
The downside is this does require ADB. But that documentation is covered on my main page: [https://github.com/natecarlson/quectel-rgmii-configuration-notes](https://github.com/natecarlson/quectel-rgmii-configuration-notes)
|
||||||
|
|
||||||
|
If you're interested in supporting more work on things like this:
|
||||||
|
|
||||||
|
<a href="https://www.buymeacoffee.com/natecarlson" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a> <!-- markdownlint-disable-line -->
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Supports multiple clients connected via telnet at the same time. They will all see the same data. Commands entered by the clients are send in the order they are received; there _shouldn't_ be any problems with commands getting garbled by multiple inputs. (The intent of this is to allow other scripts to connect via TCP and inject commands into the modem.. for example, a connection stats monitoring script.)
|
||||||
|
* Relatively lightweight; uses the Unix port of Micropython, which is remarkably small. Having Micropython available on the modem also opens up many other opportunities; however, be aware that it isn't at parity with CPython, and that it needs different modules (ie, you can't just use pip.)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Known issues
|
||||||
|
|
||||||
|
* **This currently only works with RM520 modems!** My build environment targeted the library versions of the RM520; the other modems have an older environment. I'll rebuild on an older base version soonish.
|
||||||
|
* If your telnet client sends each character individually (instead of waiting for you to press enter), this won't work properly. I'll get a patch in for it soonish. I've confirmed that with default settings putty, netcat, and NetKit telnet all work fine. (I'll always recommend using a client that waits to send until you hit enter, though, as it makes it possible to fix type-o's before sending to the modem!)
|
||||||
|
* ~~This currently listens on port 5000 on all interfaces. If you're not behind CGNAT, this is a big risk!~~ It now listens on both IPv4 and IPv6, but sets up a firewall rule to prevent external access. If your public input interface is something under than rmnet+, it will not work, however.
|
||||||
|
* It's also currently unauthenticated.
|
||||||
|
* The connection is not encrypted.
|
||||||
|
* The socat binary is from a different source. I will add a public build for it at some point, which will alleviate risk. For now, I haven't seen anything suspicious about it.
|
||||||
|
* The method I use to interact with the smd11 interface is kind of a kludge right now. Micropython doesn't have direct os.open support, and I haven't been able to figure out a way to interact directly with /dev/smd11 from python without that, due to missing ioctls/etc. So, I've set up a socat instance that listens on /dev/ttyIN and /dev/ttyOUT. I then use a pair of cat's - one reading from smd11 and writing to ttyIN, and one reading from ttyIN and writing to smd11. It's all automated by the systemd scripts, including proper restarts/etc, but it's still a bit of a kludge. I'm open to suggestions on how to improve this.
|
||||||
|
* I haven't tested this with modems other than the RM520 as of yet.
|
||||||
|
* I'm not super happy with the micropython build I'm shipping right now - but it does work! I plan on modifying it to clean up the sys.path to make it easier to install additional extensions/etc.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* **RM520** modem. It will not work on RM50x yet (see above.)
|
||||||
|
* ADB access to the modem
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
* Clone this repository to a host connected via USB to the modem
|
||||||
|
* In a shell, navigate to the at_telnet_daemon directory.
|
||||||
|
* Run the following commands from your host:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb push micropython /usrdata/micropython
|
||||||
|
adb push at-telnet /usrdata/at-telnet
|
||||||
|
adb shell chmod +x /usrdata/micropython/micropython /usrdata/at-telnet/modem-multiclient.py /usrdata/at-telnet/socat-armel-static /usrdata/at-telnet/picocom
|
||||||
|
adb shell mount -o remount,rw /
|
||||||
|
adb shell cp /usrdata/at-telnet/systemd_units/*.service /lib/systemd/system
|
||||||
|
adb shell systemctl daemon-reload
|
||||||
|
adb shell ln -s /lib/systemd/system/at-telnet-daemon.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell ln -s /lib/systemd/system/socat-smd11.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell ln -s /lib/systemd/system/socat-smd11-to-ttyIN.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell ln -s /lib/systemd/system/socat-smd11-from-ttyIN.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
adb shell mount -o remount,ro /
|
||||||
|
adb shell systemctl start socat-smd11
|
||||||
|
adb shell sleep 2s
|
||||||
|
adb shell systemctl start socat-smd11-to-ttyIN
|
||||||
|
adb shell systemctl start socat-smd11-from-ttyIN
|
||||||
|
adb shell systemctl start at-telnet-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, it should be ready for you to connect on port 5000.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### I can type commands in, but I don't see any output
|
||||||
|
|
||||||
|
I haven't perfected the systemd units yet. If it doesn't work, sometimes it might help to stop everything and start it again, one by one..
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell systemctl stop at-telnet-daemon socat-smd11 socat-smd11-to-ttyIN socat-smd11-from-ttyIN
|
||||||
|
adb shell systemctl start socat-smd11
|
||||||
|
adb shell sleep 2s
|
||||||
|
adb shell systemctl start socat-smd11-to-ttyIN
|
||||||
|
adb shell systemctl start socat-smd11-from-ttyIN
|
||||||
|
adb shell systemctl start at-telnet-daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
If it still doesn't work, log in and try picocom:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb shell
|
||||||
|
systemctl stop at-telnet-daemon
|
||||||
|
/usrdata/at-telnet/picocom /dev/ttyOUT
|
||||||
|
```
|
||||||
|
|
||||||
|
..and see if you can issue AT commands. (Ctrl-A, Ctrl-X to exit picocom - hold down Ctrl the whole time.)
|
||||||
|
|
||||||
|
If it works there, try manually launching the daemon from your adb shell: `/usrdata/at-telnet/modem-multiclient.py`. The first thing it does is issues an ATE0 command, so if the bridge isn't working, you will get:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash-3.2# ./modem-multiclient.py
|
||||||
|
[2023-07-08 16:21:33: INFO/606ms] AT Server listening on TCP port 5000
|
||||||
|
[2023-07-08 16:21:33: WARNING/638ms] Did not get expected OK when running ATE0. Result: b''
|
||||||
|
```
|
||||||
|
|
||||||
|
If it's still not working, let me know!
|
||||||
281
at-telnet/modem-multiclient.py
Normal file
281
at-telnet/modem-multiclient.py
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#!/usrdata/micropython/micropython
|
||||||
|
|
||||||
|
# Add the /usrdata/micropython directory to sys.path so we can find the external modules.
|
||||||
|
# TODO: Move external modules to lib?
|
||||||
|
# TODO: Recompile Micropython with a syspath set up for our use case.
|
||||||
|
import sys
|
||||||
|
# Remove the home directory from sys.path.
|
||||||
|
if "~/.micropython/lib" in sys.path:
|
||||||
|
sys.path.remove("~/.micropython/lib")
|
||||||
|
sys.path.append("/usrdata/micropython/lib")
|
||||||
|
sys.path.append("/usrdata/micropython")
|
||||||
|
|
||||||
|
import uos
|
||||||
|
import usocket as socket
|
||||||
|
import _thread as thread
|
||||||
|
import serial
|
||||||
|
import select
|
||||||
|
import traceback
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Set up logging
|
||||||
|
logging.basicConfig(level=logging.INFO, format='[%(asctime)s: %(levelname)s/%(msecs)ims] %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||||
|
# Globally define client_sockets and serialport. That way, we can access them from handle_output and make it a separate thread, so responses (and unsolicited responses) can come in while we're waiting for input.
|
||||||
|
global client_sockets, serialport
|
||||||
|
client_sockets = []
|
||||||
|
# We are referencing one of the two ports exposed by our socat command. The other one is /dev/ttyIN, and two running "cat" commands are keeping it sync'd with /dev/smd11.
|
||||||
|
serialport = serial.Serial("/dev/ttyOUT", baudrate=115200)
|
||||||
|
|
||||||
|
# These will be set in the main routine.
|
||||||
|
global firewall_is_setup, fwpublicinterface, port
|
||||||
|
firewall_is_setup = 0
|
||||||
|
|
||||||
|
# Make these configurable via /etc/default or similar
|
||||||
|
port = 5000
|
||||||
|
fwpublicinterface = "rmnet+"
|
||||||
|
|
||||||
|
# Block access to port 5000 via ipv4 and ipv6 on public-facing interfaces.
|
||||||
|
def add_firewll_rules(port=port, fwpublicinterface=fwpublicinterface):
|
||||||
|
if not port or not fwpublicinterface:
|
||||||
|
logging.error(f"Port or fwpublicinterface not set. Values: fwpublicinterface: {fwpublicinterface} port: {port}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
logging.info(f"Adding firewall rules for port {port} on interface {fwpublicinterface}.")
|
||||||
|
|
||||||
|
# Check if the rule already exists in iptables
|
||||||
|
iptables_check_cmd = f"iptables -C INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT &> /dev/null"
|
||||||
|
iptables_check_result = uos.system(iptables_check_cmd)
|
||||||
|
if iptables_check_result != 0:
|
||||||
|
# Rule doesn't exist, add it to iptables
|
||||||
|
iptables_add_cmd = f"iptables -A INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT"
|
||||||
|
iptables_add_result = uos.system(iptables_add_cmd)
|
||||||
|
if iptables_add_result:
|
||||||
|
logging.error(f"ERROR: Failed to add iptables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
# Treat this as fatal.
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
logging.debug(f"Added iptables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
|
||||||
|
# Check if the rule already exists in ip6tables
|
||||||
|
ip6tables_check_cmd = f"ip6tables -C INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT &> /dev/null"
|
||||||
|
ip6tables_check_result = uos.system(ip6tables_check_cmd)
|
||||||
|
if ip6tables_check_result != 0:
|
||||||
|
# Rule doesn't exist, add it to ip6tables
|
||||||
|
ip6tables_add_cmd = f"ip6tables -A INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT"
|
||||||
|
ip6tables_add_result = uos.system(ip6tables_add_cmd)
|
||||||
|
if ip6tables_add_result:
|
||||||
|
logging.error(f"ERROR: Failed to add ip6tables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
# Treat this as fatal.
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
logging.debug(f"Added ip6tables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
|
||||||
|
global firewall_is_setup
|
||||||
|
firewall_is_setup = 1
|
||||||
|
|
||||||
|
logging.info(f"Successfully firewall rules for port {port} on interface {fwpublicinterface}.")
|
||||||
|
|
||||||
|
|
||||||
|
def remove_firewall_rules(port=port, fwpublicinterface=fwpublicinterface):
|
||||||
|
if firewall_is_setup:
|
||||||
|
iptables_del_cmd = f"iptables -D INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT"
|
||||||
|
ip6tables_del_cmd = f"ip6tables -D INPUT -i {fwpublicinterface} -p tcp --dport {port} -j REJECT"
|
||||||
|
iptables_del_result = uos.system(iptables_del_cmd)
|
||||||
|
ip6tables_del_result = uos.system(ip6tables_del_cmd)
|
||||||
|
|
||||||
|
if iptables_del_result or ip6tables_del_result:
|
||||||
|
logging.error(f"ERROR: Failed to remove iptables or ip6tables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Removed iptables and ip6tables rule - input interface {fwpublicinterface} port {port}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.info(f"Firewall rules not set up; not removing.")
|
||||||
|
|
||||||
|
# This routine pulls data from the serial port and sends it to all connected clients.
|
||||||
|
def handle_output():
|
||||||
|
while True:
|
||||||
|
# Make data an empty bytes list
|
||||||
|
data = b''
|
||||||
|
|
||||||
|
try:
|
||||||
|
while serialport.in_waiting > 0:
|
||||||
|
data += serialport.read(1)
|
||||||
|
except Exception as e:
|
||||||
|
# This will keep trying.
|
||||||
|
print(f"Exception reading data from serialport: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if data:
|
||||||
|
logging.info(f"Got data from modem: {data}")
|
||||||
|
for client_socket in client_sockets:
|
||||||
|
client_socket.send(data)
|
||||||
|
|
||||||
|
# Start the server on the specified port, listen for clients, etc.
|
||||||
|
def start_at_server(port):
|
||||||
|
|
||||||
|
# Server initialization stuff
|
||||||
|
# NOTE: This now supports IPv6. And means that on many connections it'll be directly exposed
|
||||||
|
# to the internet. So we're adding firewall rules to block access to it via rmnet+.
|
||||||
|
try:
|
||||||
|
server_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||||
|
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
addr_info = socket.getaddrinfo("::", port)
|
||||||
|
addr = addr_info[0][4]
|
||||||
|
server_socket.bind(addr)
|
||||||
|
server_socket.listen(1)
|
||||||
|
|
||||||
|
logging.info(f"AT Server listening on TCP port {port}")
|
||||||
|
|
||||||
|
# Disable echo so user doesn't see a second copy of all their commands.
|
||||||
|
serialport.write("ATE0\r\n")
|
||||||
|
# time.sleep() segfaults?! ugh.
|
||||||
|
uos.system("sleep 0.025s")
|
||||||
|
# wait for an OK
|
||||||
|
out=b''
|
||||||
|
while serialport.in_waiting > 0:
|
||||||
|
out += serialport.read(1)
|
||||||
|
|
||||||
|
if "OK" not in str(out):
|
||||||
|
logging.warning(f"Did not get expected OK when running ATE0. Result: {str(out)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error initializing server: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Start the output handler in its own thread
|
||||||
|
try:
|
||||||
|
thread.start_new_thread(handle_output, ())
|
||||||
|
except Exception as e:
|
||||||
|
print("Error with output handler:", e)
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Set up a select.poll object to listen for input from the server socket and all client sockets.
|
||||||
|
# Logic mostly from https://pymotw.com/2/select/
|
||||||
|
try:
|
||||||
|
poll_obj = select.poll()
|
||||||
|
poll_obj.register(server_socket, select.POLLIN)
|
||||||
|
|
||||||
|
# Register the server socket in the fd_to_socket dict; this will also be used to register the rest of the clients.
|
||||||
|
fd_to_socket = { server_socket.fileno(): server_socket,
|
||||||
|
}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
events = poll_obj.poll()
|
||||||
|
|
||||||
|
for fd, flag in events:
|
||||||
|
logging.debug(f"Pool loop event. fd: {fd} flag: {flag} fd_to_socket.keys(): {fd_to_socket.keys()}")
|
||||||
|
|
||||||
|
# Check if the client already exists in the fd_to_socket dict.
|
||||||
|
if fd.fileno() in fd_to_socket.keys():
|
||||||
|
s = fd_to_socket[fd.fileno()]
|
||||||
|
logging.debug("Event matches existing socket.")
|
||||||
|
else:
|
||||||
|
s = fd
|
||||||
|
logging.debug(f"Event doesn't match existing socket. fd: {fd} fd_to_socket: {fd_to_socket}")
|
||||||
|
|
||||||
|
# If the flag is POLLIN, then we have data to process.
|
||||||
|
if flag & (select.POLLIN):
|
||||||
|
# If the server socket is ready to read, then we have a new client connection.
|
||||||
|
if s is server_socket:
|
||||||
|
# Accept the connection.
|
||||||
|
client_socket, client_address = s.accept()
|
||||||
|
# TODO: This gives a garbled IP. Figure it out.
|
||||||
|
#client_address_translated = socket.inet_ntop(socket.AF_INET, client_address)
|
||||||
|
logging.info(f"New connection")
|
||||||
|
|
||||||
|
# Set the client socket to non-blocking, and add it to the list of client sockets.
|
||||||
|
# TODO: trim down to just storing one copy of the client sockets..
|
||||||
|
client_socket.setblocking(0)
|
||||||
|
fd_to_socket[ client_socket.fileno() ] = client_socket
|
||||||
|
client_sockets.append(client_socket)
|
||||||
|
poll_obj.register(client_socket, select.POLLIN)
|
||||||
|
|
||||||
|
# Send a good 'ol hello message to the client.
|
||||||
|
client_socket.send("** Welcome to the AT server!\r\n".encode())
|
||||||
|
client_socket.send("** Note that your commands are interleaved with any other connected clients,\r\n** so responses may appear out of order.\r\n".encode())
|
||||||
|
client_socket.send("** \r\n".encode())
|
||||||
|
client_socket.send("** You may also receive unsolicited responses (URC's) depending on the\r\n** modem configuration.\r\n".encode())
|
||||||
|
client_socket.send("** \r\n".encode())
|
||||||
|
client_socket.send("** Echo is off (ATE0); if you change it you'll see what you've typed both\r\n** locally and echo'd back.\r\n".encode())
|
||||||
|
client_socket.send("** \r\n".encode())
|
||||||
|
client_socket.send("** I have tested this with telnet.netkit and netcat on Linux. If your client\r\n** doesn't work,\r\n** please open an issue at:\r\n** https://github.com/natecarlson/quectel-rgmii-at-command-client/ **\r\n".encode())
|
||||||
|
client_socket.send("**\r\n".encode())
|
||||||
|
client_socket.send("** If you would like to support further development, you can at:\r\n** https://www.buymeacoffee.com/natecarlson **\r\n".encode())
|
||||||
|
client_socket.send("\r\n".encode())
|
||||||
|
|
||||||
|
|
||||||
|
# Otherwise, we have data from a client socket.
|
||||||
|
else:
|
||||||
|
data = s.recv(1024)
|
||||||
|
logging.info(f"Got data from client: {data}")
|
||||||
|
if data:
|
||||||
|
# Ensure it ends with \r\n
|
||||||
|
if not data.endswith("\r\n"):
|
||||||
|
# Just stripping \n for now; add others in the future if needed.
|
||||||
|
data = re.sub(b"\n$", "", data) + "\r\n"
|
||||||
|
logging.info(f"Modified client data to end with \\r\\n: {data}")
|
||||||
|
|
||||||
|
# Good client data; write out to the serial port.
|
||||||
|
serialport.write(data)
|
||||||
|
# Write out out to the rest of the clients too
|
||||||
|
for fd in fd_to_socket.keys():
|
||||||
|
if fd != server_socket.fileno() and fd != s.fileno():
|
||||||
|
logging.debug(f"Writing data to other connected client: {data}")
|
||||||
|
try:
|
||||||
|
fd_to_socket[fd].send(data)
|
||||||
|
except Exception as e:
|
||||||
|
logging.info(f"Failed to write data to an additional client. Ignorning. Result: {e}")
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Client disconnected
|
||||||
|
print("Client disconnected")
|
||||||
|
client_sockets.remove(s)
|
||||||
|
poll_obj.unregister(s)
|
||||||
|
del fd_to_socket[s.fileno()]
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
# Not sure if this can happen. But , if it does, we should close the socket.
|
||||||
|
elif flag & select.POLLERR:
|
||||||
|
logging.warn(f"Strange connection issue with a client; closing.")
|
||||||
|
# Stop listening for input on the connection
|
||||||
|
poll_obj.unregister(s)
|
||||||
|
client_sockets.remove(s)
|
||||||
|
del fd_to_socket[s.fileno()]
|
||||||
|
s.close()
|
||||||
|
|
||||||
|
# TODO: I don't believe we need this here, since the output is now handled in its own thread.
|
||||||
|
#uos.system("sleep 0.025s")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("Error after server initialization:", e)
|
||||||
|
serialport.write("ATE1\r\n")
|
||||||
|
traceback.print_exc()
|
||||||
|
# I believe this will drop out of the while loop, so we'll close the sockets and exit.
|
||||||
|
|
||||||
|
# Close client sockets and server socket
|
||||||
|
for client_socket in client_sockets:
|
||||||
|
client_socket.close()
|
||||||
|
|
||||||
|
server_socket.close()
|
||||||
|
|
||||||
|
# TODO: By using the dict, we shouldn't need this code. Clean it up.
|
||||||
|
#def fd_to_socket(fd, client_sockets):
|
||||||
|
# for client_socket in client_sockets:
|
||||||
|
# if client_socket.fileno() == fd:
|
||||||
|
# return client_socket
|
||||||
|
# return None
|
||||||
|
|
||||||
|
# App startup. TODO: Make the port configurable.
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Register an atexit handler to remove the firewall rules.
|
||||||
|
sys.atexit(remove_firewall_rules)
|
||||||
|
|
||||||
|
# Add the firewall rules before starting anything
|
||||||
|
add_firewll_rules(port=port, fwpublicinterface=fwpublicinterface)
|
||||||
|
|
||||||
|
# Light 'er up!
|
||||||
|
start_at_server(port)
|
||||||
BIN
at-telnet/picocom
Normal file
BIN
at-telnet/picocom
Normal file
Binary file not shown.
BIN
at-telnet/socat-armel-static
Normal file
BIN
at-telnet/socat-armel-static
Normal file
Binary file not shown.
25
at-telnet/systemd_units/at-telnet-daemon.service
Normal file
25
at-telnet/systemd_units/at-telnet-daemon.service
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Telnet daemon for AT command
|
||||||
|
|
||||||
|
# Being extra silly with the dependencies for this.
|
||||||
|
# TODO: Update the python code to validate that the serial port
|
||||||
|
# is working on a regular basis, and keep attempting to retry
|
||||||
|
# if not. Then these dependencies won't need to be so strict.
|
||||||
|
After=socat-smd11.service
|
||||||
|
Requires=socat-smd11.service socat-smd11-from-ttyIN.service socat-smd11-to-ttyIN.service
|
||||||
|
ReloadPropagatedFrom=socat-smd11.service socat-smd11-from-ttyIN.service socat-smd11-to-ttyIN.service
|
||||||
|
|
||||||
|
StartLimitIntervalSec=2m
|
||||||
|
StartLimitBurst=100
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usrdata/at-telnet/modem-multiclient.py
|
||||||
|
Nice=5
|
||||||
|
Restart=always
|
||||||
|
RestartSec=2s
|
||||||
|
# Increased log rate limits, so we can see what's going on.
|
||||||
|
LogRateLimitIntervalSec=5s
|
||||||
|
LogRateLimitBurst=100
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
at-telnet/systemd_units/socat-smd11-from-ttyIN.service
Normal file
14
at-telnet/systemd_units/socat-smd11-from-ttyIN.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Read from /dev/ttyIN and write to smd11
|
||||||
|
BindsTo=socat-smd11.service
|
||||||
|
After=socat-smd11.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/bash -c "/bin/cat /dev/ttyIN > /dev/smd11"
|
||||||
|
ExecStartPost=/bin/sleep 2s
|
||||||
|
StandardInput=tty-force
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
14
at-telnet/systemd_units/socat-smd11-to-ttyIN.service
Normal file
14
at-telnet/systemd_units/socat-smd11-to-ttyIN.service
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Read from /dev/smd11 and write to ttyIN
|
||||||
|
BindsTo=socat-smd11.service
|
||||||
|
After=socat-smd11.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/bin/bash -c "/bin/cat /dev/smd11 > /dev/ttyIN"
|
||||||
|
ExecStartPost=/bin/sleep 2s
|
||||||
|
StandardInput=tty-force
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
13
at-telnet/systemd_units/socat-smd11.service
Normal file
13
at-telnet/systemd_units/socat-smd11.service
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Socat Serial Emulation for smd11
|
||||||
|
After=ql-netd.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usrdata/at-telnet/socat-armel-static -d -d pty,link=/dev/ttyIN,raw,echo=0 pty,link=/dev/ttyOUT,raw,echo=1
|
||||||
|
# Add a delay to prevent the clients from starting too early
|
||||||
|
ExecStartPost=/bin/sleep 2s
|
||||||
|
Restart=always
|
||||||
|
RestartSec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
50
install_on_modem.sh
Normal file
50
install_on_modem.sh
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Installs SimpleAdmin
|
||||||
|
#
|
||||||
|
|
||||||
|
read -p "Do you want to install SimpleAdmin (yes/no) " yn
|
||||||
|
|
||||||
|
case $yn in
|
||||||
|
yes ) echo ok, we will proceed;;
|
||||||
|
no ) echo exiting...;
|
||||||
|
exit;;
|
||||||
|
* ) echo invalid response;
|
||||||
|
exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
# Download
|
||||||
|
cd /tmp
|
||||||
|
wget https://github.com/rbflurry/quectel-rgmii-simpleadmin/archive/refs/heads/main.zip
|
||||||
|
|
||||||
|
# Unzip
|
||||||
|
unzip main.zip
|
||||||
|
cp -R quectel-rgmii-simpleadmin-main* simpleadmin/
|
||||||
|
|
||||||
|
# Copy over to /usrdata
|
||||||
|
cp -R /tmp/simpleadmin /usrdata/
|
||||||
|
|
||||||
|
# Chmod execute on scripts and cgi-bin
|
||||||
|
chmod +x /usrdata/simpleadmin/scripts/* /usrdata/simpleadmin/www/cgi-bin/* /usrdata/simpleadmin/ttl/ttl-override
|
||||||
|
|
||||||
|
# Remount
|
||||||
|
mount -o remount,rw /
|
||||||
|
|
||||||
|
# Copy systemd init files & reload
|
||||||
|
cp /usrdata/simpleadmin/systemd/* /lib/systemd/system
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Link systemd files
|
||||||
|
ln -s /lib/systemd/system/simpleadmin_httpd.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
ln -s /lib/systemd/system/simpleadmin_generate_status.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
ln -s /lib/systemd/system/ttl-override.service /lib/systemd/system/multi-user.target.wants/
|
||||||
|
|
||||||
|
# Remount readonly
|
||||||
|
mount -o remount,ro /
|
||||||
|
|
||||||
|
# Start Services
|
||||||
|
systemctl start simpleadmin_generate_status
|
||||||
|
systemctl start simpleadmin_httpd
|
||||||
|
systemctl start ttl-override
|
||||||
38
micropython/errno.py
Normal file
38
micropython/errno.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
EPERM = 1 # Operation not permitted
|
||||||
|
ENOENT = 2 # No such file or directory
|
||||||
|
ESRCH = 3 # No such process
|
||||||
|
EINTR = 4 # Interrupted system call
|
||||||
|
EIO = 5 # I/O error
|
||||||
|
ENXIO = 6 # No such device or address
|
||||||
|
E2BIG = 7 # Argument list too long
|
||||||
|
ENOEXEC = 8 # Exec format error
|
||||||
|
EBADF = 9 # Bad file number
|
||||||
|
ECHILD = 10 # No child processes
|
||||||
|
EAGAIN = 11 # Try again
|
||||||
|
ENOMEM = 12 # Out of memory
|
||||||
|
EACCES = 13 # Permission denied
|
||||||
|
EFAULT = 14 # Bad address
|
||||||
|
ENOTBLK = 15 # Block device required
|
||||||
|
EBUSY = 16 # Device or resource busy
|
||||||
|
EEXIST = 17 # File exists
|
||||||
|
EXDEV = 18 # Cross-device link
|
||||||
|
ENODEV = 19 # No such device
|
||||||
|
ENOTDIR = 20 # Not a directory
|
||||||
|
EISDIR = 21 # Is a directory
|
||||||
|
EINVAL = 22 # Invalid argument
|
||||||
|
ENFILE = 23 # File table overflow
|
||||||
|
EMFILE = 24 # Too many open files
|
||||||
|
ENOTTY = 25 # Not a typewriter
|
||||||
|
ETXTBSY = 26 # Text file busy
|
||||||
|
EFBIG = 27 # File too large
|
||||||
|
ENOSPC = 28 # No space left on device
|
||||||
|
ESPIPE = 29 # Illegal seek
|
||||||
|
EROFS = 30 # Read-only file system
|
||||||
|
EMLINK = 31 # Too many links
|
||||||
|
EPIPE = 32 # Broken pipe
|
||||||
|
EDOM = 33 # Math argument out of domain of func
|
||||||
|
ERANGE = 34 # Math result not representable
|
||||||
|
EAFNOSUPPORT = 97 # Address family not supported by protocol
|
||||||
|
ECONNRESET = 104 # Connection timed out
|
||||||
|
ETIMEDOUT = 110 # Connection timed out
|
||||||
|
EINPROGRESS = 115 # Operation now in progress
|
||||||
36
micropython/fcntl.py
Normal file
36
micropython/fcntl.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import ffi
|
||||||
|
import os_compat as os
|
||||||
|
import ffilib
|
||||||
|
|
||||||
|
libc = ffilib.libc()
|
||||||
|
|
||||||
|
fcntl_l = libc.func("i", "fcntl", "iil")
|
||||||
|
fcntl_s = libc.func("i", "fcntl", "iip")
|
||||||
|
ioctl_l = libc.func("i", "ioctl", "iil")
|
||||||
|
ioctl_s = libc.func("i", "ioctl", "iip")
|
||||||
|
|
||||||
|
|
||||||
|
def fcntl(fd, op, arg=0):
|
||||||
|
if type(arg) is int:
|
||||||
|
r = fcntl_l(fd, op, arg)
|
||||||
|
os.check_error(r)
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
r = fcntl_s(fd, op, arg)
|
||||||
|
os.check_error(r)
|
||||||
|
# TODO: Not compliant. CPython says that arg should be immutable,
|
||||||
|
# and possibly mutated buffer is returned.
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def ioctl(fd, op, arg=0, mut=False):
|
||||||
|
if type(arg) is int:
|
||||||
|
r = ioctl_l(fd, op, arg)
|
||||||
|
os.check_error(r)
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
# TODO
|
||||||
|
assert mut
|
||||||
|
r = ioctl_s(fd, op, arg)
|
||||||
|
os.check_error(r)
|
||||||
|
return r
|
||||||
51
micropython/ffilib.py
Normal file
51
micropython/ffilib.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import sys
|
||||||
|
try:
|
||||||
|
import ffi
|
||||||
|
except ImportError:
|
||||||
|
ffi = None
|
||||||
|
|
||||||
|
_cache = {}
|
||||||
|
|
||||||
|
|
||||||
|
def open(name, maxver=10, extra=()):
|
||||||
|
if not ffi:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return _cache[name]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def libs():
|
||||||
|
if sys.platform == "linux":
|
||||||
|
yield '%s.so' % name
|
||||||
|
for i in range(maxver, -1, -1):
|
||||||
|
yield '%s.so.%u' % (name, i)
|
||||||
|
else:
|
||||||
|
for ext in ('dylib', 'dll'):
|
||||||
|
yield '%s.%s' % (name, ext)
|
||||||
|
for n in extra:
|
||||||
|
yield n
|
||||||
|
|
||||||
|
err = None
|
||||||
|
for n in libs():
|
||||||
|
try:
|
||||||
|
l = ffi.open(n)
|
||||||
|
_cache[name] = l
|
||||||
|
return l
|
||||||
|
except OSError as e:
|
||||||
|
err = e
|
||||||
|
raise err
|
||||||
|
|
||||||
|
|
||||||
|
def libc():
|
||||||
|
return open("libc", 6)
|
||||||
|
|
||||||
|
|
||||||
|
# Find out bitness of the platform, even if long ints are not supported
|
||||||
|
# TODO: All bitness differences should be removed from micropython-lib, and
|
||||||
|
# this snippet too.
|
||||||
|
bitness = 1
|
||||||
|
v = sys.maxsize
|
||||||
|
while v:
|
||||||
|
bitness += 1
|
||||||
|
v >>= 1
|
||||||
245
micropython/logging.py
Normal file
245
micropython/logging.py
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
from micropython import const
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
CRITICAL = const(50)
|
||||||
|
ERROR = const(40)
|
||||||
|
WARNING = const(30)
|
||||||
|
INFO = const(20)
|
||||||
|
DEBUG = const(10)
|
||||||
|
NOTSET = const(0)
|
||||||
|
|
||||||
|
_DEFAULT_LEVEL = const(WARNING)
|
||||||
|
|
||||||
|
_level_dict = {
|
||||||
|
CRITICAL: "CRITICAL",
|
||||||
|
ERROR: "ERROR",
|
||||||
|
WARNING: "WARNING",
|
||||||
|
INFO: "INFO",
|
||||||
|
DEBUG: "DEBUG",
|
||||||
|
NOTSET: "NOTSET",
|
||||||
|
}
|
||||||
|
|
||||||
|
_loggers = {}
|
||||||
|
_stream = sys.stderr
|
||||||
|
_default_fmt = "%(levelname)s:%(name)s:%(message)s"
|
||||||
|
_default_datefmt = "%Y-%m-%d %H:%M:%S"
|
||||||
|
|
||||||
|
|
||||||
|
class LogRecord:
|
||||||
|
def set(self, name, level, message):
|
||||||
|
self.name = name
|
||||||
|
self.levelno = level
|
||||||
|
self.levelname = _level_dict[level]
|
||||||
|
self.message = message
|
||||||
|
self.ct = time.time()
|
||||||
|
self.msecs = int((self.ct - int(self.ct)) * 1000)
|
||||||
|
self.asctime = None
|
||||||
|
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
def __init__(self, level=NOTSET):
|
||||||
|
self.level = level
|
||||||
|
self.formatter = None
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setLevel(self, level):
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
def setFormatter(self, formatter):
|
||||||
|
self.formatter = formatter
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
return self.formatter.format(record)
|
||||||
|
|
||||||
|
|
||||||
|
class StreamHandler(Handler):
|
||||||
|
def __init__(self, stream=None):
|
||||||
|
self.stream = _stream if stream is None else stream
|
||||||
|
self.terminator = "\n"
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if hasattr(self.stream, "flush"):
|
||||||
|
self.stream.flush()
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
if record.levelno >= self.level:
|
||||||
|
self.stream.write(self.format(record) + self.terminator)
|
||||||
|
|
||||||
|
|
||||||
|
class FileHandler(StreamHandler):
|
||||||
|
def __init__(self, filename, mode="a", encoding="UTF-8"):
|
||||||
|
super().__init__(stream=open(filename, mode=mode, encoding=encoding))
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
super().close()
|
||||||
|
self.stream.close()
|
||||||
|
|
||||||
|
|
||||||
|
class Formatter:
|
||||||
|
def __init__(self, fmt=None, datefmt=None):
|
||||||
|
self.fmt = _default_fmt if fmt is None else fmt
|
||||||
|
self.datefmt = _default_datefmt if datefmt is None else datefmt
|
||||||
|
|
||||||
|
def usesTime(self):
|
||||||
|
return "asctime" in self.fmt
|
||||||
|
|
||||||
|
def formatTime(self, datefmt, record):
|
||||||
|
if hasattr(time, "strftime"):
|
||||||
|
return time.strftime(datefmt, time.localtime(record.ct))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if self.usesTime():
|
||||||
|
record.asctime = self.formatTime(self.datefmt, record)
|
||||||
|
return self.fmt % {
|
||||||
|
"name": record.name,
|
||||||
|
"message": record.message,
|
||||||
|
"msecs": record.msecs,
|
||||||
|
"asctime": record.asctime,
|
||||||
|
"levelname": record.levelname,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Logger:
|
||||||
|
def __init__(self, name, level=NOTSET):
|
||||||
|
self.name = name
|
||||||
|
self.level = level
|
||||||
|
self.handlers = []
|
||||||
|
self.record = LogRecord()
|
||||||
|
|
||||||
|
def setLevel(self, level):
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
def isEnabledFor(self, level):
|
||||||
|
return level >= self.getEffectiveLevel()
|
||||||
|
|
||||||
|
def getEffectiveLevel(self):
|
||||||
|
return self.level or getLogger().level or _DEFAULT_LEVEL
|
||||||
|
|
||||||
|
def log(self, level, msg, *args):
|
||||||
|
if self.isEnabledFor(level):
|
||||||
|
if args:
|
||||||
|
if isinstance(args[0], dict):
|
||||||
|
args = args[0]
|
||||||
|
msg = msg % args
|
||||||
|
self.record.set(self.name, level, msg)
|
||||||
|
handlers = self.handlers
|
||||||
|
if not handlers:
|
||||||
|
handlers = getLogger().handlers
|
||||||
|
for h in handlers:
|
||||||
|
h.emit(self.record)
|
||||||
|
|
||||||
|
def debug(self, msg, *args):
|
||||||
|
self.log(DEBUG, msg, *args)
|
||||||
|
|
||||||
|
def info(self, msg, *args):
|
||||||
|
self.log(INFO, msg, *args)
|
||||||
|
|
||||||
|
def warning(self, msg, *args):
|
||||||
|
self.log(WARNING, msg, *args)
|
||||||
|
|
||||||
|
def error(self, msg, *args):
|
||||||
|
self.log(ERROR, msg, *args)
|
||||||
|
|
||||||
|
def critical(self, msg, *args):
|
||||||
|
self.log(CRITICAL, msg, *args)
|
||||||
|
|
||||||
|
def exception(self, msg, *args):
|
||||||
|
self.log(ERROR, msg, *args)
|
||||||
|
if hasattr(sys, "exc_info"):
|
||||||
|
sys.print_exception(sys.exc_info()[1], _stream)
|
||||||
|
|
||||||
|
def addHandler(self, handler):
|
||||||
|
self.handlers.append(handler)
|
||||||
|
|
||||||
|
def hasHandlers(self):
|
||||||
|
return len(self.handlers) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def getLogger(name=None):
|
||||||
|
if name is None:
|
||||||
|
name = "root"
|
||||||
|
if name not in _loggers:
|
||||||
|
_loggers[name] = Logger(name)
|
||||||
|
if name == "root":
|
||||||
|
basicConfig()
|
||||||
|
return _loggers[name]
|
||||||
|
|
||||||
|
|
||||||
|
def log(level, msg, *args):
|
||||||
|
getLogger().log(level, msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def debug(msg, *args):
|
||||||
|
getLogger().debug(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def info(msg, *args):
|
||||||
|
getLogger().info(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def warning(msg, *args):
|
||||||
|
getLogger().warning(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def error(msg, *args):
|
||||||
|
getLogger().error(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def critical(msg, *args):
|
||||||
|
getLogger().critical(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def exception(msg, *args):
|
||||||
|
getLogger().exception(msg, *args)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown():
|
||||||
|
for k, logger in _loggers.items():
|
||||||
|
for h in logger.handlers:
|
||||||
|
h.close()
|
||||||
|
_loggers.pop(logger, None)
|
||||||
|
|
||||||
|
|
||||||
|
def addLevelName(level, name):
|
||||||
|
_level_dict[level] = name
|
||||||
|
|
||||||
|
|
||||||
|
def basicConfig(
|
||||||
|
filename=None,
|
||||||
|
filemode="a",
|
||||||
|
format=None,
|
||||||
|
datefmt=None,
|
||||||
|
level=WARNING,
|
||||||
|
stream=None,
|
||||||
|
encoding="UTF-8",
|
||||||
|
force=False,
|
||||||
|
):
|
||||||
|
if "root" not in _loggers:
|
||||||
|
_loggers["root"] = Logger("root")
|
||||||
|
|
||||||
|
logger = _loggers["root"]
|
||||||
|
|
||||||
|
if force or not logger.handlers:
|
||||||
|
for h in logger.handlers:
|
||||||
|
h.close()
|
||||||
|
logger.handlers = []
|
||||||
|
|
||||||
|
if filename is None:
|
||||||
|
handler = StreamHandler(stream)
|
||||||
|
else:
|
||||||
|
handler = FileHandler(filename, filemode, encoding)
|
||||||
|
|
||||||
|
handler.setLevel(level)
|
||||||
|
handler.setFormatter(Formatter(format, datefmt))
|
||||||
|
|
||||||
|
logger.setLevel(level)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(sys, "atexit"):
|
||||||
|
sys.atexit(shutdown)
|
||||||
BIN
micropython/micropython
Normal file
BIN
micropython/micropython
Normal file
Binary file not shown.
312
micropython/os_compat.py
Normal file
312
micropython/os_compat.py
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
import array
|
||||||
|
import ustruct as struct
|
||||||
|
import errno as errno_
|
||||||
|
import stat as stat_
|
||||||
|
import ffilib
|
||||||
|
import uos
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
R_OK = const(4)
|
||||||
|
W_OK = const(2)
|
||||||
|
X_OK = const(1)
|
||||||
|
F_OK = const(0)
|
||||||
|
|
||||||
|
O_ACCMODE = 0o0000003
|
||||||
|
O_RDONLY = 0o0000000
|
||||||
|
O_WRONLY = 0o0000001
|
||||||
|
O_RDWR = 0o0000002
|
||||||
|
O_CREAT = 0o0000100
|
||||||
|
O_EXCL = 0o0000200
|
||||||
|
O_NOCTTY = 0o0000400
|
||||||
|
O_TRUNC = 0o0001000
|
||||||
|
O_APPEND = 0o0002000
|
||||||
|
O_NONBLOCK = 0o0004000
|
||||||
|
|
||||||
|
error = OSError
|
||||||
|
name = "posix"
|
||||||
|
sep = "/"
|
||||||
|
curdir = "."
|
||||||
|
pardir = ".."
|
||||||
|
environ = {"WARNING": "NOT_IMPLEMENTED"}
|
||||||
|
|
||||||
|
libc = ffilib.libc()
|
||||||
|
|
||||||
|
if libc:
|
||||||
|
chdir_ = libc.func("i", "chdir", "s")
|
||||||
|
mkdir_ = libc.func("i", "mkdir", "si")
|
||||||
|
rename_ = libc.func("i", "rename", "ss")
|
||||||
|
unlink_ = libc.func("i", "unlink", "s")
|
||||||
|
rmdir_ = libc.func("i", "rmdir", "s")
|
||||||
|
getcwd_ = libc.func("s", "getcwd", "si")
|
||||||
|
opendir_ = libc.func("P", "opendir", "s")
|
||||||
|
readdir_ = libc.func("P", "readdir", "P")
|
||||||
|
open_ = libc.func("i", "open", "sii")
|
||||||
|
read_ = libc.func("i", "read", "ipi")
|
||||||
|
write_ = libc.func("i", "write", "iPi")
|
||||||
|
close_ = libc.func("i", "close", "i")
|
||||||
|
dup_ = libc.func("i", "dup", "i")
|
||||||
|
access_ = libc.func("i", "access", "si")
|
||||||
|
fork_ = libc.func("i", "fork", "")
|
||||||
|
pipe_ = libc.func("i", "pipe", "p")
|
||||||
|
_exit_ = libc.func("v", "_exit", "i")
|
||||||
|
getpid_ = libc.func("i", "getpid", "")
|
||||||
|
waitpid_ = libc.func("i", "waitpid", "ipi")
|
||||||
|
system_ = libc.func("i", "system", "s")
|
||||||
|
execvp_ = libc.func("i", "execvp", "PP")
|
||||||
|
kill_ = libc.func("i", "kill", "ii")
|
||||||
|
getenv_ = libc.func("s", "getenv", "P")
|
||||||
|
|
||||||
|
|
||||||
|
def check_error(ret):
|
||||||
|
# Return True is error was EINTR (which usually means that OS call
|
||||||
|
# should be restarted).
|
||||||
|
if ret == -1:
|
||||||
|
e = uos.errno()
|
||||||
|
if e == errno_.EINTR:
|
||||||
|
return True
|
||||||
|
raise OSError(e)
|
||||||
|
|
||||||
|
|
||||||
|
def raise_error():
|
||||||
|
raise OSError(uos.errno())
|
||||||
|
|
||||||
|
|
||||||
|
stat = uos.stat
|
||||||
|
|
||||||
|
|
||||||
|
def getcwd():
|
||||||
|
buf = bytearray(512)
|
||||||
|
return getcwd_(buf, 512)
|
||||||
|
|
||||||
|
|
||||||
|
def mkdir(name, mode=0o777):
|
||||||
|
e = mkdir_(name, mode)
|
||||||
|
check_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
def rename(old, new):
|
||||||
|
e = rename_(old, new)
|
||||||
|
check_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
def unlink(name):
|
||||||
|
e = unlink_(name)
|
||||||
|
check_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
remove = unlink
|
||||||
|
|
||||||
|
|
||||||
|
def rmdir(name):
|
||||||
|
e = rmdir_(name)
|
||||||
|
check_error(e)
|
||||||
|
|
||||||
|
|
||||||
|
def makedirs(name, mode=0o777, exist_ok=False):
|
||||||
|
s = ""
|
||||||
|
comps = name.split("/")
|
||||||
|
if comps[-1] == "":
|
||||||
|
comps.pop()
|
||||||
|
for i, c in enumerate(comps):
|
||||||
|
s += c + "/"
|
||||||
|
try:
|
||||||
|
uos.mkdir(s)
|
||||||
|
except OSError as e:
|
||||||
|
if e.args[0] != errno_.EEXIST:
|
||||||
|
raise
|
||||||
|
if i == len(comps) - 1:
|
||||||
|
if exist_ok:
|
||||||
|
return
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(uos, "ilistdir"):
|
||||||
|
ilistdir = uos.ilistdir
|
||||||
|
else:
|
||||||
|
|
||||||
|
def ilistdir(path="."):
|
||||||
|
dir = opendir_(path)
|
||||||
|
if not dir:
|
||||||
|
raise_error()
|
||||||
|
res = []
|
||||||
|
dirent_fmt = "LLHB256s"
|
||||||
|
while True:
|
||||||
|
dirent = readdir_(dir)
|
||||||
|
if not dirent:
|
||||||
|
break
|
||||||
|
import uctypes
|
||||||
|
dirent = uctypes.bytes_at(dirent, struct.calcsize(dirent_fmt))
|
||||||
|
dirent = struct.unpack(dirent_fmt, dirent)
|
||||||
|
dirent = (dirent[-1].split(b'\0', 1)[0], dirent[-2], dirent[0])
|
||||||
|
yield dirent
|
||||||
|
|
||||||
|
|
||||||
|
def listdir(path="."):
|
||||||
|
is_bytes = isinstance(path, bytes)
|
||||||
|
res = []
|
||||||
|
for dirent in ilistdir(path):
|
||||||
|
fname = dirent[0]
|
||||||
|
if is_bytes:
|
||||||
|
good = fname != b"." and fname == b".."
|
||||||
|
else:
|
||||||
|
good = fname != "." and fname != ".."
|
||||||
|
if good:
|
||||||
|
if not is_bytes:
|
||||||
|
fname = fsdecode(fname)
|
||||||
|
res.append(fname)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def walk(top, topdown=True):
|
||||||
|
files = []
|
||||||
|
dirs = []
|
||||||
|
for dirent in ilistdir(top):
|
||||||
|
mode = dirent[1] << 12
|
||||||
|
fname = fsdecode(dirent[0])
|
||||||
|
if stat_.S_ISDIR(mode):
|
||||||
|
if fname != "." and fname != "..":
|
||||||
|
dirs.append(fname)
|
||||||
|
else:
|
||||||
|
files.append(fname)
|
||||||
|
if topdown:
|
||||||
|
yield top, dirs, files
|
||||||
|
for d in dirs:
|
||||||
|
yield from walk(top + "/" + d, topdown)
|
||||||
|
if not topdown:
|
||||||
|
yield top, dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def open(n, flags, mode=0o777):
|
||||||
|
r = open_(n, flags, mode)
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def read(fd, n):
|
||||||
|
buf = bytearray(n)
|
||||||
|
r = read_(fd, buf, n)
|
||||||
|
check_error(r)
|
||||||
|
return bytes(buf[:r])
|
||||||
|
|
||||||
|
|
||||||
|
def write(fd, buf):
|
||||||
|
r = write_(fd, buf, len(buf))
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def close(fd):
|
||||||
|
r = close_(fd)
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def dup(fd):
|
||||||
|
r = dup_(fd)
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def access(path, mode):
|
||||||
|
return access_(path, mode) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def chdir(dir):
|
||||||
|
r = chdir_(dir)
|
||||||
|
check_error(r)
|
||||||
|
|
||||||
|
|
||||||
|
def fork():
|
||||||
|
r = fork_()
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def pipe():
|
||||||
|
a = array.array('i', [0, 0])
|
||||||
|
r = pipe_(a)
|
||||||
|
check_error(r)
|
||||||
|
return a[0], a[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _exit(n):
|
||||||
|
_exit_(n)
|
||||||
|
|
||||||
|
|
||||||
|
def execvp(f, args):
|
||||||
|
import uctypes
|
||||||
|
args_ = array.array("P", [0] * (len(args) + 1))
|
||||||
|
i = 0
|
||||||
|
for a in args:
|
||||||
|
args_[i] = uctypes.addressof(a)
|
||||||
|
i += 1
|
||||||
|
r = execvp_(f, uctypes.addressof(args_))
|
||||||
|
check_error(r)
|
||||||
|
|
||||||
|
|
||||||
|
def getpid():
|
||||||
|
return getpid_()
|
||||||
|
|
||||||
|
|
||||||
|
def waitpid(pid, opts):
|
||||||
|
a = array.array('i', [0])
|
||||||
|
r = waitpid_(pid, a, opts)
|
||||||
|
check_error(r)
|
||||||
|
return (r, a[0])
|
||||||
|
|
||||||
|
|
||||||
|
def kill(pid, sig):
|
||||||
|
r = kill_(pid, sig)
|
||||||
|
check_error(r)
|
||||||
|
|
||||||
|
|
||||||
|
def system(command):
|
||||||
|
r = system_(command)
|
||||||
|
check_error(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def getenv(var, default=None):
|
||||||
|
var = getenv_(var)
|
||||||
|
if var is None:
|
||||||
|
return default
|
||||||
|
return var
|
||||||
|
|
||||||
|
|
||||||
|
def fsencode(s):
|
||||||
|
if type(s) is bytes:
|
||||||
|
return s
|
||||||
|
return bytes(s, "utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def fsdecode(s):
|
||||||
|
if type(s) is str:
|
||||||
|
return s
|
||||||
|
return str(s, "utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def urandom(n):
|
||||||
|
import builtins
|
||||||
|
with builtins.open("/dev/urandom", "rb") as f:
|
||||||
|
return f.read(n)
|
||||||
|
|
||||||
|
|
||||||
|
def popen(cmd, mode="r"):
|
||||||
|
import builtins
|
||||||
|
i, o = pipe()
|
||||||
|
if mode[0] == "w":
|
||||||
|
i, o = o, i
|
||||||
|
pid = fork()
|
||||||
|
if not pid:
|
||||||
|
if mode[0] == "r":
|
||||||
|
close(1)
|
||||||
|
else:
|
||||||
|
close(0)
|
||||||
|
close(i)
|
||||||
|
dup(o)
|
||||||
|
close(o)
|
||||||
|
s = system(cmd)
|
||||||
|
_exit(s)
|
||||||
|
else:
|
||||||
|
close(o)
|
||||||
|
return builtins.open(i, mode)
|
||||||
79
micropython/serial.py
Normal file
79
micropython/serial.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#
|
||||||
|
# serial - pySerial-like interface for Micropython
|
||||||
|
# based on https://github.com/pfalcon/pycopy-serial
|
||||||
|
#
|
||||||
|
# Copyright (c) 2014 Paul Sokolovsky
|
||||||
|
# Licensed under MIT license
|
||||||
|
#
|
||||||
|
import os_compat as os
|
||||||
|
import termios
|
||||||
|
import ustruct
|
||||||
|
import fcntl
|
||||||
|
import uselect
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
FIONREAD = const(0x541b)
|
||||||
|
F_GETFD = const(1)
|
||||||
|
|
||||||
|
|
||||||
|
class Serial:
|
||||||
|
|
||||||
|
BAUD_MAP = {
|
||||||
|
9600: termios.B9600,
|
||||||
|
# From Linux asm-generic/termbits.h
|
||||||
|
19200: 14,
|
||||||
|
57600: termios.B57600,
|
||||||
|
115200: termios.B115200
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, port, baudrate, timeout=None, **kwargs):
|
||||||
|
self.port = port
|
||||||
|
self.baudrate = baudrate
|
||||||
|
self.timeout = -1 if timeout is None else timeout * 1000
|
||||||
|
self.open()
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
self.fd = os.open(self.port, os.O_RDWR | os.O_NOCTTY)
|
||||||
|
termios.setraw(self.fd)
|
||||||
|
iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(
|
||||||
|
self.fd)
|
||||||
|
baudrate = self.BAUD_MAP[self.baudrate]
|
||||||
|
termios.tcsetattr(self.fd, termios.TCSANOW,
|
||||||
|
[iflag, oflag, cflag, lflag, baudrate, baudrate, cc])
|
||||||
|
self.poller = uselect.poll()
|
||||||
|
self.poller.register(self.fd, uselect.POLLIN | uselect.POLLHUP)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.fd:
|
||||||
|
os.close(self.fd)
|
||||||
|
self.fd = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def in_waiting(self):
|
||||||
|
"""Can throw an OSError or TypeError"""
|
||||||
|
buf = ustruct.pack('I', 0)
|
||||||
|
fcntl.ioctl(self.fd, FIONREAD, buf, True)
|
||||||
|
return ustruct.unpack('I', buf)[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_open(self):
|
||||||
|
"""Can throw an OSError or TypeError"""
|
||||||
|
return fcntl.fcntl(self.fd, F_GETFD) == 0
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
if self.fd:
|
||||||
|
os.write(self.fd, data)
|
||||||
|
|
||||||
|
def read(self, size=1):
|
||||||
|
buf = b''
|
||||||
|
while self.fd and size > 0:
|
||||||
|
if not self.poller.poll(self.timeout):
|
||||||
|
break
|
||||||
|
chunk = os.read(self.fd, size)
|
||||||
|
l = len(chunk)
|
||||||
|
if l == 0: # port has disappeared
|
||||||
|
self.close()
|
||||||
|
return buf
|
||||||
|
size -= l
|
||||||
|
buf += bytes(chunk)
|
||||||
|
return buf
|
||||||
142
micropython/stat.py
Normal file
142
micropython/stat.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
"""Constants/functions for interpreting results of os.stat() and os.lstat().
|
||||||
|
|
||||||
|
Suggested usage: from stat import *
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Indices for stat struct members in the tuple returned by os.stat()
|
||||||
|
|
||||||
|
ST_MODE = 0
|
||||||
|
ST_INO = 1
|
||||||
|
ST_DEV = 2
|
||||||
|
ST_NLINK = 3
|
||||||
|
ST_UID = 4
|
||||||
|
ST_GID = 5
|
||||||
|
ST_SIZE = 6
|
||||||
|
ST_ATIME = 7
|
||||||
|
ST_MTIME = 8
|
||||||
|
ST_CTIME = 9
|
||||||
|
|
||||||
|
# Extract bits from the mode
|
||||||
|
|
||||||
|
|
||||||
|
def S_IMODE(mode):
|
||||||
|
"""Return the portion of the file's mode that can be set by
|
||||||
|
os.chmod().
|
||||||
|
"""
|
||||||
|
return mode & 0o7777
|
||||||
|
|
||||||
|
|
||||||
|
def S_IFMT(mode):
|
||||||
|
"""Return the portion of the file's mode that describes the
|
||||||
|
file type.
|
||||||
|
"""
|
||||||
|
return mode & 0o170000
|
||||||
|
|
||||||
|
|
||||||
|
# Constants used as S_IFMT() for various file types
|
||||||
|
# (not all are implemented on all systems)
|
||||||
|
|
||||||
|
S_IFDIR = 0o040000 # directory
|
||||||
|
S_IFCHR = 0o020000 # character device
|
||||||
|
S_IFBLK = 0o060000 # block device
|
||||||
|
S_IFREG = 0o100000 # regular file
|
||||||
|
S_IFIFO = 0o010000 # fifo (named pipe)
|
||||||
|
S_IFLNK = 0o120000 # symbolic link
|
||||||
|
S_IFSOCK = 0o140000 # socket file
|
||||||
|
|
||||||
|
# Functions to test for each file type
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISDIR(mode):
|
||||||
|
"""Return True if mode is from a directory."""
|
||||||
|
return S_IFMT(mode) == S_IFDIR
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISCHR(mode):
|
||||||
|
"""Return True if mode is from a character special device file."""
|
||||||
|
return S_IFMT(mode) == S_IFCHR
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISBLK(mode):
|
||||||
|
"""Return True if mode is from a block special device file."""
|
||||||
|
return S_IFMT(mode) == S_IFBLK
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISREG(mode):
|
||||||
|
"""Return True if mode is from a regular file."""
|
||||||
|
return S_IFMT(mode) == S_IFREG
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISFIFO(mode):
|
||||||
|
"""Return True if mode is from a FIFO (named pipe)."""
|
||||||
|
return S_IFMT(mode) == S_IFIFO
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISLNK(mode):
|
||||||
|
"""Return True if mode is from a symbolic link."""
|
||||||
|
return S_IFMT(mode) == S_IFLNK
|
||||||
|
|
||||||
|
|
||||||
|
def S_ISSOCK(mode):
|
||||||
|
"""Return True if mode is from a socket."""
|
||||||
|
return S_IFMT(mode) == S_IFSOCK
|
||||||
|
|
||||||
|
|
||||||
|
# Names for permission bits
|
||||||
|
|
||||||
|
S_ISUID = 0o4000 # set UID bit
|
||||||
|
S_ISGID = 0o2000 # set GID bit
|
||||||
|
S_ENFMT = S_ISGID # file locking enforcement
|
||||||
|
S_ISVTX = 0o1000 # sticky bit
|
||||||
|
S_IREAD = 0o0400 # Unix V7 synonym for S_IRUSR
|
||||||
|
S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR
|
||||||
|
S_IEXEC = 0o0100 # Unix V7 synonym for S_IXUSR
|
||||||
|
S_IRWXU = 0o0700 # mask for owner permissions
|
||||||
|
S_IRUSR = 0o0400 # read by owner
|
||||||
|
S_IWUSR = 0o0200 # write by owner
|
||||||
|
S_IXUSR = 0o0100 # execute by owner
|
||||||
|
S_IRWXG = 0o0070 # mask for group permissions
|
||||||
|
S_IRGRP = 0o0040 # read by group
|
||||||
|
S_IWGRP = 0o0020 # write by group
|
||||||
|
S_IXGRP = 0o0010 # execute by group
|
||||||
|
S_IRWXO = 0o0007 # mask for others (not in group) permissions
|
||||||
|
S_IROTH = 0o0004 # read by others
|
||||||
|
S_IWOTH = 0o0002 # write by others
|
||||||
|
S_IXOTH = 0o0001 # execute by others
|
||||||
|
|
||||||
|
# Names for file flags
|
||||||
|
|
||||||
|
UF_NODUMP = 0x00000001 # do not dump file
|
||||||
|
UF_IMMUTABLE = 0x00000002 # file may not be changed
|
||||||
|
UF_APPEND = 0x00000004 # file may only be appended to
|
||||||
|
UF_OPAQUE = 0x00000008 # directory is opaque when viewed through a union stack
|
||||||
|
UF_NOUNLINK = 0x00000010 # file may not be renamed or deleted
|
||||||
|
UF_COMPRESSED = 0x00000020 # OS X: file is hfs-compressed
|
||||||
|
UF_HIDDEN = 0x00008000 # OS X: file should not be displayed
|
||||||
|
SF_ARCHIVED = 0x00010000 # file may be archived
|
||||||
|
SF_IMMUTABLE = 0x00020000 # file may not be changed
|
||||||
|
SF_APPEND = 0x00040000 # file may only be appended to
|
||||||
|
SF_NOUNLINK = 0x00100000 # file may not be renamed or deleted
|
||||||
|
SF_SNAPSHOT = 0x00200000 # file is a snapshot file
|
||||||
|
|
||||||
|
_filemode_table = (((S_IFLNK, "l"), (S_IFREG, "-"), (S_IFBLK, "b"),
|
||||||
|
(S_IFDIR, "d"), (S_IFCHR, "c"),
|
||||||
|
(S_IFIFO, "p")), ((S_IRUSR, "r"), ), ((S_IWUSR, "w"), ),
|
||||||
|
((S_IXUSR | S_ISUID, "s"), (S_ISUID, "S"),
|
||||||
|
(S_IXUSR, "x")), ((S_IRGRP, "r"), ), ((S_IWGRP, "w"), ),
|
||||||
|
((S_IXGRP | S_ISGID, "s"), (S_ISGID, "S"),
|
||||||
|
(S_IXGRP, "x")), ((S_IROTH, "r"), ), ((S_IWOTH, "w"), ),
|
||||||
|
((S_IXOTH | S_ISVTX, "t"), (S_ISVTX, "T"), (S_IXOTH, "x")))
|
||||||
|
|
||||||
|
|
||||||
|
def filemode(mode):
|
||||||
|
"""Convert a file's mode to a string of the form '-rwxrwxrwx'."""
|
||||||
|
perm = []
|
||||||
|
for table in _filemode_table:
|
||||||
|
for bit, char in table:
|
||||||
|
if mode & bit == bit:
|
||||||
|
perm.append(char)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
perm.append("-")
|
||||||
|
return "".join(perm)
|
||||||
79
micropython/time.py
Normal file
79
micropython/time.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
from utime import *
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
_TS_YEAR = const(0)
|
||||||
|
_TS_MON = const(1)
|
||||||
|
_TS_MDAY = const(2)
|
||||||
|
_TS_HOUR = const(3)
|
||||||
|
_TS_MIN = const(4)
|
||||||
|
_TS_SEC = const(5)
|
||||||
|
_TS_WDAY = const(6)
|
||||||
|
_TS_YDAY = const(7)
|
||||||
|
_TS_ISDST = const(8)
|
||||||
|
|
||||||
|
_WDAY = const(("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
|
||||||
|
_MDAY = const(
|
||||||
|
(
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def strftime(datefmt, ts):
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
|
fmtsp = False
|
||||||
|
ftime = StringIO()
|
||||||
|
for k in datefmt:
|
||||||
|
if fmtsp:
|
||||||
|
if k == "a":
|
||||||
|
ftime.write(_WDAY[ts[_TS_WDAY]][0:3])
|
||||||
|
elif k == "A":
|
||||||
|
ftime.write(_WDAY[ts[_TS_WDAY]])
|
||||||
|
elif k == "b":
|
||||||
|
ftime.write(_MDAY[ts[_TS_MON] - 1][0:3])
|
||||||
|
elif k == "B":
|
||||||
|
ftime.write(_MDAY[ts[_TS_MON] - 1])
|
||||||
|
elif k == "d":
|
||||||
|
ftime.write("%02d" % ts[_TS_MDAY])
|
||||||
|
elif k == "H":
|
||||||
|
ftime.write("%02d" % ts[_TS_HOUR])
|
||||||
|
elif k == "I":
|
||||||
|
ftime.write("%02d" % (ts[_TS_HOUR] % 12))
|
||||||
|
elif k == "j":
|
||||||
|
ftime.write("%03d" % ts[_TS_YDAY])
|
||||||
|
elif k == "m":
|
||||||
|
ftime.write("%02d" % ts[_TS_MON])
|
||||||
|
elif k == "M":
|
||||||
|
ftime.write("%02d" % ts[_TS_MIN])
|
||||||
|
elif k == "P":
|
||||||
|
ftime.write("AM" if ts[_TS_HOUR] < 12 else "PM")
|
||||||
|
elif k == "S":
|
||||||
|
ftime.write("%02d" % ts[_TS_SEC])
|
||||||
|
elif k == "w":
|
||||||
|
ftime.write(str(ts[_TS_WDAY]))
|
||||||
|
elif k == "y":
|
||||||
|
ftime.write("%02d" % (ts[_TS_YEAR] % 100))
|
||||||
|
elif k == "Y":
|
||||||
|
ftime.write(str(ts[_TS_YEAR]))
|
||||||
|
else:
|
||||||
|
ftime.write(k)
|
||||||
|
fmtsp = False
|
||||||
|
elif k == "%":
|
||||||
|
fmtsp = True
|
||||||
|
else:
|
||||||
|
ftime.write(k)
|
||||||
|
val = ftime.getvalue()
|
||||||
|
ftime.close()
|
||||||
|
return val
|
||||||
BIN
micropython/traceback.mpy
Normal file
BIN
micropython/traceback.mpy
Normal file
Binary file not shown.
16
scripts/build_modem_status
Normal file
16
scripts/build_modem_status
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
# Send request to modem and wait 5 seconds for data
|
||||||
|
echo -en "AT+QSPN;+CEREG=2;+CEREG?;+CEREG=0;+C5GREG=2;+C5GREG?;+C5GREG=0;+CSQ;+QENG=\"servingcell\";+QRSRP;+QCAINFO;+QNWPREFCFG=\"mode_pref\";+QTEMP\r\n" \
|
||||||
|
| microcom -t 3000 /dev/ttyOUT > /tmp/modemstatus.txt
|
||||||
|
if [ $? -eq 0 ]
|
||||||
|
then
|
||||||
|
# Parse
|
||||||
|
if [ -f /tmp/modemstatus.txt ]
|
||||||
|
then
|
||||||
|
/usrdata/simpleadmin/scripts/modemstatus_parse.sh
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 25 # Add a sleep to avoid CPU overload
|
||||||
|
done
|
||||||
31
scripts/doAT.py
Normal file
31
scripts/doAT.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usrdata/micropython/micropython
|
||||||
|
|
||||||
|
# Add the /usrdata/micropython directory to sys.path so we can find the external modules.
|
||||||
|
# TODO: Move external modules to lib?
|
||||||
|
# TODO: Recompile Micropython with a syspath set up for our use case.
|
||||||
|
import sys
|
||||||
|
# Remove the home directory from sys.path.
|
||||||
|
if "~/.micropython/lib" in sys.path:
|
||||||
|
sys.path.remove("~/.micropython/lib")
|
||||||
|
sys.path.append("/usrdata/micropython")
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import uos
|
||||||
|
|
||||||
|
|
||||||
|
atcmd = sys.argv[1]
|
||||||
|
|
||||||
|
ser = serial.Serial("/dev/ttyOUT", baudrate=115200)
|
||||||
|
ser.write(atcmd + "\r\n")
|
||||||
|
|
||||||
|
uos.system("sleep 0.025s")
|
||||||
|
# wait for an OK
|
||||||
|
out=r''
|
||||||
|
while ser.in_waiting > 0:
|
||||||
|
out += ser.read(1)
|
||||||
|
|
||||||
|
if "OK" not in str(out):
|
||||||
|
print('Error NOT OK')
|
||||||
|
|
||||||
|
print(out.decode('utf-8'))
|
||||||
|
ser.close()
|
||||||
470
scripts/modemstatus_parse.sh
Normal file
470
scripts/modemstatus_parse.sh
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Adapted to work with RJ45 / Quectel Board Dev
|
||||||
|
# Quectel AT Parsing Original source ROOter2203
|
||||||
|
# https://github.com/ofmodemsandmen/ROOterSource2203/blob/6636758b945ff16b6c5b54494de04b74b011c204/package/rooter/ext-rooter-basic/files/usr/lib/rooter/common/quecteldata.sh
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
rspr2rssi() {
|
||||||
|
echo ${RSCP} ${BW_N} | awk '{printf "%.0f\n", (($1+10*log(12*$2)/log(10)))}'
|
||||||
|
}
|
||||||
|
|
||||||
|
lte_bw() {
|
||||||
|
BW=$(echo $BW | grep -o "[0-5]\{1\}")
|
||||||
|
case $BW in
|
||||||
|
"0")
|
||||||
|
BW="1.4" ;;
|
||||||
|
"1")
|
||||||
|
BW="3" ;;
|
||||||
|
"2"|"3"|"4"|"5")
|
||||||
|
BW=$((($(echo $BW) - 1) * 5)) ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
nr_bw() {
|
||||||
|
BW=$(echo $BW | grep -o "[0-9]\{1,2\}")
|
||||||
|
case $BW in
|
||||||
|
"0"|"1"|"2"|"3"|"4"|"5")
|
||||||
|
BW=$((($(echo $BW) + 1) * 5)) ;;
|
||||||
|
"6"|"7"|"8"|"9"|"10"|"11"|"12")
|
||||||
|
BW=$((($(echo $BW) - 2) * 10)) ;;
|
||||||
|
"13")
|
||||||
|
BW="200" ;;
|
||||||
|
"14")
|
||||||
|
BW="400" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f /tmp/modemstatus.txt ]
|
||||||
|
then
|
||||||
|
/usrdata/simpleadmin/scripts/get_modem_data.py > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read File
|
||||||
|
OX=$(</tmp/modemstatus.txt)
|
||||||
|
|
||||||
|
OX=$(echo $OX | tr 'a-z' 'A-Z')
|
||||||
|
|
||||||
|
RSRP=""
|
||||||
|
RSRQ=""
|
||||||
|
CHANNEL="-"
|
||||||
|
ECIO="-"
|
||||||
|
RSCP="-"
|
||||||
|
ECIO1=" "
|
||||||
|
RSCP1=" "
|
||||||
|
MODE="-"
|
||||||
|
MODTYPE="-"
|
||||||
|
NETMODE="-"
|
||||||
|
LBAND="-"
|
||||||
|
PCI="-"
|
||||||
|
CTEMP="-"
|
||||||
|
SINR="-"
|
||||||
|
COPS="-"
|
||||||
|
COPS_MCC="-"
|
||||||
|
COPS_MNC="-"
|
||||||
|
CID=""
|
||||||
|
CID5=""
|
||||||
|
RAT=""
|
||||||
|
QSPN=$(echo $OX | grep -o '+QSPN: "[^"]*","[^"]*","[^"]*",[^"]*,"[^"]*"' | cut -c 8-)
|
||||||
|
PROVIDER=$(echo $QSPN | cut -d, -f1 | tr -d '"')
|
||||||
|
PROVIDER_ID=$(echo $QSPN | cut -d, -f5 | tr -d '"')
|
||||||
|
CSQ=$(echo $OX | grep -o "+CSQ: [0-9]\{1,2\}" | grep -o "[0-9]\{1,2\}")
|
||||||
|
if [ "$CSQ" = "99" ]; then
|
||||||
|
CSQ=""
|
||||||
|
fi
|
||||||
|
if [ -n "$CSQ" ]; then
|
||||||
|
CSQ_PER=$(($CSQ * 100/31))"%"
|
||||||
|
CSQ_RSSI=$((2 * CSQ - 113))" dBm"
|
||||||
|
else
|
||||||
|
CSQ="-"
|
||||||
|
CSQ_PER="-"
|
||||||
|
CSQ_RSSI="-"
|
||||||
|
fi
|
||||||
|
NR_NSA=$(echo $OX | grep -o "+QENG:[ ]\?\"NR5G-NSA\",")
|
||||||
|
NR_SA=$(echo $OX | grep -o "+QENG: \"SERVINGCELL\",[^,]\+,\"NR5G-SA\",\"[DFT]\{3\}\",")
|
||||||
|
if [ -n "$NR_NSA" ]; then
|
||||||
|
QENG=",,"$(echo $OX" " | grep -o "+QENG: \"LTE\".\+\"NR5G-NSA\"," | tr " " ",")
|
||||||
|
if [ -z "$QENG5" ]; then
|
||||||
|
QENG5=$(echo $OX | grep -o "+QENG:[ ]\?\"NR5G-NSA\",[0-9]\{3\},[0-9]\{2,3\},[0-9]\{1,5\},-[0-9]\{2,3\},[-0-9]\{1,3\},-[0-9]\{2,3\}")
|
||||||
|
if [ -n "$QENG5" ]; then
|
||||||
|
QENG5=$QENG5",,"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ -n "$NR_SA" ]; then
|
||||||
|
QENG=$(echo $NR_SA | tr " " ",")
|
||||||
|
QENG5=$(echo $OX | grep -o "+QENG: \"SERVINGCELL\",[^,]\+,\"NR5G-SA\",\"[DFT]\{3\}\",[ 0-9]\{3,4\},[0-9]\{2,3\},[0-9A-F]\{1,10\},[0-9]\{1,5\},[0-9A-F]\{2,6\},[0-9]\{6,7\},[0-9]\{1,3\},[0-9]\{1,2\},-[0-9]\{2,5\},-[0-9]\{2,3\},[-0-9]\{1,3\}")
|
||||||
|
else
|
||||||
|
QENG=$(echo $OX" " | grep -o "+QENG: [^ ]\+ " | tr " " ",")
|
||||||
|
fi
|
||||||
|
QCA=$(echo $OX" " | grep -o "+QCAINFO: \"S[CS]\{2\}\".\+NWSCANMODE" | tr " " ",")
|
||||||
|
QNSM=$(echo $OX | grep -o "+QCFG: \"NWSCANMODE\",[0-9]")
|
||||||
|
QNWP=$(echo $OX | grep -o "+QNWPREFCFG: \"MODE_PREF\",[A-Z5:]\+" | cut -d, -f2)
|
||||||
|
QTEMP=$(echo $OX | grep -o "+QTEMP: [0-9]\{1,3\}")
|
||||||
|
if [ -z "$QTEMP" ]; then
|
||||||
|
QTEMP=$(echo $OX | grep -o "+QTEMP:[ ]\?\"XO[_-]THERM[_-][^,]\+,[\"]\?[0-9]\{1,3\}" | grep -o "[0-9]\{1,3\}")
|
||||||
|
fi
|
||||||
|
if [ -z "$QTEMP" ]; then
|
||||||
|
QTEMP=$(echo $OX | grep -o "+QTEMP:[ ]\?\"MDM-CORE-USR.\+[0-9]\{1,3\}\"" | cut -d\" -f4)
|
||||||
|
fi
|
||||||
|
if [ -z "$QTEMP" ]; then
|
||||||
|
QTEMP=$(echo $OX | grep -o "+QTEMP:[ ]\?\"MDMSS.\+[0-9]\{1,3\}\"" | cut -d\" -f4)
|
||||||
|
fi
|
||||||
|
if [ -n "$QTEMP" ]; then
|
||||||
|
CTEMP=$(echo $QTEMP | grep -o "[0-9]\{1,3\}")$(printf "\xc2\xb0")"C"
|
||||||
|
fi
|
||||||
|
RAT=$(echo $QENG | cut -d, -f4 | grep -o "[-A-Z5]\{3,7\}")
|
||||||
|
|
||||||
|
rm -f /tmp/modnetwork
|
||||||
|
case $RAT in
|
||||||
|
"GSM")
|
||||||
|
MODE="GSM"
|
||||||
|
;;
|
||||||
|
"WCDMA")
|
||||||
|
MODE="WCDMA"
|
||||||
|
CHANNEL=$(echo $QENG | cut -d, -f9)
|
||||||
|
RSCP=$(echo $QENG | cut -d, -f12)
|
||||||
|
RSCP="-"$(echo $RSCP | grep -o "[0-9]\{1,3\}")
|
||||||
|
ECIO=$(echo $QENG | cut -d, -f13)
|
||||||
|
ECIO="-"$(echo $ECIO | grep -o "[0-9]\{1,3\}")
|
||||||
|
;;
|
||||||
|
"LTE"|"CAT-M"|"CAT-NB")
|
||||||
|
MODE=$(echo $QENG | cut -d, -f5 | grep -o "[DFT]\{3\}")
|
||||||
|
if [ -n "$MODE" ]; then
|
||||||
|
MODE="$RAT $MODE"
|
||||||
|
else
|
||||||
|
MODE="$RAT"
|
||||||
|
fi
|
||||||
|
PCI=$(echo $QENG | cut -d, -f9)
|
||||||
|
CHANNEL=$(echo $QENG | cut -d, -f10)
|
||||||
|
LBAND=$(echo $QENG | cut -d, -f11 | grep -o "[0-9]\{1,3\}")
|
||||||
|
BW=$(echo $QENG | cut -d, -f12)
|
||||||
|
lte_bw
|
||||||
|
BWU=$BW
|
||||||
|
BW=$(echo $QENG | cut -d, -f13)
|
||||||
|
lte_bw
|
||||||
|
BWD=$BW
|
||||||
|
if [ -z "$BWD" ]; then
|
||||||
|
BWD="unknown"
|
||||||
|
fi
|
||||||
|
if [ -z "$BWU" ]; then
|
||||||
|
BWU="unknown"
|
||||||
|
fi
|
||||||
|
if [ -n "$LBAND" ]; then
|
||||||
|
LBAND="B"$LBAND" (Bandwidth $BWD MHz Down | $BWU MHz Up)"
|
||||||
|
fi
|
||||||
|
RSRP=$(echo $QENG | cut -d, -f15 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$RSRP" ]; then
|
||||||
|
RSCP="-"$RSRP
|
||||||
|
RSRPLTE=$RSCP
|
||||||
|
fi
|
||||||
|
RSRQ=$(echo $QENG | cut -d, -f16 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$RSRQ" ]; then
|
||||||
|
ECIO="-"$RSRQ
|
||||||
|
fi
|
||||||
|
RSSI=$(echo $QENG | cut -d, -f17 | grep -o "\-[0-9]\{1,3\}")
|
||||||
|
if [ -n "$RSSI" ]; then
|
||||||
|
CSQ_RSSI=$RSSI" dBm"
|
||||||
|
fi
|
||||||
|
SINRR=$(echo $QENG | cut -d, -f18 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$SINRR" ]; then
|
||||||
|
if [ $SINRR -le 25 ]; then
|
||||||
|
SINR=$((($(echo $SINRR) * 2) -20))" dB"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -n "$(echo $QENG | cut -d, -f21)" ]; then
|
||||||
|
CQI=$(echo $QENG | cut -d, -f19 | grep "^[0-9]\+$")
|
||||||
|
if [ -n "$SINR" -a -n "$CQI" -a "$CQI" != "0" ]; then
|
||||||
|
SINR=$SINR" (CQI $CQI)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -n "$NR_NSA" ]; then
|
||||||
|
MODE="LTE/NR EN-DC"
|
||||||
|
echo "0" > /tmp/modnetwork
|
||||||
|
if [ -n "$QENG5" ] && [ -n "$LBAND" ] && [ "$RSCP" != "-" ] && [ "$ECIO" != "-" ]; then
|
||||||
|
PCI="$PCI, "$(echo $QENG5 | cut -d, -f4)
|
||||||
|
SCHV=$(echo $QENG5 | cut -d, -f8)
|
||||||
|
SLBV=$(echo $QENG5 | cut -d, -f9)
|
||||||
|
BW=$(echo $QENG5 | cut -d, -f10 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$SLBV" ]; then
|
||||||
|
LBAND=$LBAND"<br />n"$SLBV
|
||||||
|
if [ -n "$BW" ]; then
|
||||||
|
nr_bw
|
||||||
|
LBAND=$LBAND" (Bandwidth $BW MHz)"
|
||||||
|
fi
|
||||||
|
if [ "$SCHV" -ge 123400 ]; then
|
||||||
|
CHANNEL=$CHANNEL", "$SCHV
|
||||||
|
else
|
||||||
|
CHANNEL=$CHANNEL", -"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LBAND=$LBAND"<br />nxx (unknown NR5G band)"
|
||||||
|
CHANNEL=$CHANNEL", -"
|
||||||
|
fi
|
||||||
|
RSCP=$RSCP" dBm<br />"$(echo $QENG5 | cut -d, -f5)
|
||||||
|
SINRR=$(echo $QENG5 | cut -d, -f6 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$SINRR" ]; then
|
||||||
|
if [ $SINRR -le 30 ]; then
|
||||||
|
SINR=$SINR"<br />"$((($(echo $SINRR) * 2) -20))" dB"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
ECIO=$ECIO" (4G) dB<br />"$(echo $QENG5 | cut -d, -f7)" (5G) "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ -z "$LBAND" ]; then
|
||||||
|
LBAND="-"
|
||||||
|
else
|
||||||
|
if [ -n "$QCA" ]; then
|
||||||
|
QCA=$(echo $QCA | grep -o "\"S[CS]\{2\}\"[-0-9A-Z,\"]\+")
|
||||||
|
for QCAL in $(echo "$QCA"); do
|
||||||
|
if [ $(echo "$QCAL" | cut -d, -f7) = "2" ]; then
|
||||||
|
SCHV=$(echo $QCAL | cut -d, -f2 | grep -o "[0-9]\+")
|
||||||
|
SRATP="B"
|
||||||
|
if [ -n "$SCHV" ]; then
|
||||||
|
CHANNEL="$CHANNEL, $SCHV"
|
||||||
|
if [ "$SCHV" -gt 123400 ]; then
|
||||||
|
SRATP="n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
SLBV=$(echo $QCAL | cut -d, -f6 | grep -o "[0-9]\{1,2\}")
|
||||||
|
if [ -n "$SLBV" ]; then
|
||||||
|
LBAND=$LBAND"<br />"$SRATP$SLBV
|
||||||
|
BWD=$(echo $QCAL | cut -d, -f3 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$BWD" ]; then
|
||||||
|
UPDOWN=$(echo $QCAL | cut -d, -f13)
|
||||||
|
case "$UPDOWN" in
|
||||||
|
"UL" )
|
||||||
|
CATYPE="CA"$(printf "\xe2\x86\x91") ;;
|
||||||
|
"DL" )
|
||||||
|
CATYPE="CA"$(printf "\xe2\x86\x93") ;;
|
||||||
|
* )
|
||||||
|
CATYPE="CA" ;;
|
||||||
|
esac
|
||||||
|
if [ $BWD -gt 14 ]; then
|
||||||
|
LBAND=$LBAND" ("$CATYPE", Bandwidth "$(($(echo $BWD) / 5))" MHz)"
|
||||||
|
else
|
||||||
|
LBAND=$LBAND" ("$CATYPE", Bandwidth 1.4 MHz)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LBAND=$LBAND
|
||||||
|
fi
|
||||||
|
PCI="$PCI, "$(echo $QCAL | cut -d, -f8)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ $RAT = "CAT-M" ] || [ $RAT = "CAT-NB" ]; then
|
||||||
|
LBAND="B$(echo $QENG | cut -d, -f11) ($RAT)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"NR5G-SA")
|
||||||
|
MODE="NR5G-SA"
|
||||||
|
echo "0" > /tmp/modnetwork
|
||||||
|
if [ -n "$QENG5" ]; then
|
||||||
|
MODE="$RAT $(echo $QENG5 | cut -d, -f4)"
|
||||||
|
PCI=$(echo $QENG5 | cut -d, -f8)
|
||||||
|
CHANNEL=$(echo $QENG5 | cut -d, -f10)
|
||||||
|
LBAND=$(echo $QENG5 | cut -d, -f11)
|
||||||
|
BW=$(echo $QENG5 | cut -d, -f12)
|
||||||
|
LBAND="n"$LBAND" (Bandwidth $BW MHz)"
|
||||||
|
RSCP=$(echo $QENG5 | cut -d, -f13)
|
||||||
|
ECIO=$(echo $QENG5 | cut -d, -f14)
|
||||||
|
if [ "$CSQ_PER" = "-" ]; then
|
||||||
|
BW_N=($BW * 5)
|
||||||
|
RSSI=$(rspr2rssi)
|
||||||
|
CSQ_PER=$((100 - (($RSSI + 51) * 100/-62)))"%"
|
||||||
|
CSQ=$((($RSSI + 113) / 2))
|
||||||
|
CSQ_RSSI=$RSSI" dBm"
|
||||||
|
fi
|
||||||
|
SINRR=$(echo $QENG5 | cut -d, -f15 | grep -o "[0-9]\{1,3\}")
|
||||||
|
if [ -n "$SINRR" ]; then
|
||||||
|
if [ $SINRR -le 30 ]; then
|
||||||
|
SINR=$((($(echo $SINRR) * 2) -20))" dB"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
QRSRP=$(echo "$OX" | grep -o "+QRSRP:[^,]\+,-[0-9]\{1,5\},-[0-9]\{1,5\},-[0-9]\{1,5\}[^ ]*")
|
||||||
|
if [ -n "$QRSRP" ] && [ "$RAT" != "WCDMA" ]; then
|
||||||
|
QRSRP1=$(echo $QRSRP | cut -d, -f1 | grep -o "[-0-9]\+")
|
||||||
|
QRSRP2=$(echo $QRSRP | cut -d, -f2)
|
||||||
|
QRSRP3=$(echo $QRSRP | cut -d, -f3)
|
||||||
|
QRSRP4=$(echo $QRSRP | cut -d, -f4)
|
||||||
|
QRSRPtype=$(echo $QRSRP | cut -d, -f5)
|
||||||
|
if [ "$QRSRPtype" == "NR5G" ]; then
|
||||||
|
if [ -n "$NR_SA" ]; then
|
||||||
|
RSCP=$QRSRP1
|
||||||
|
if [ -n "$QRSRP2" -a "$QRSRP2" != "-32768" ]; then
|
||||||
|
RSCP1="RxD "$QRSRP2
|
||||||
|
fi
|
||||||
|
if [ -n "$QRSRP3" -a "$QRSRP3" != "-32768" -a "$QRSRP3" != "-44" ]; then
|
||||||
|
RSCP=$RSCP" dBm<br />"$QRSRP3
|
||||||
|
fi
|
||||||
|
if [ -n "$QRSRP4" -a "$QRSRP4" != "-32768" -a "$QRSRP4" != "-44" ]; then
|
||||||
|
RSCP1="RxD "$QRSRP4
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
RSCP=$RSRPLTE
|
||||||
|
if [ -n "$QRSRP1" -a "$QRSRP1" != "-32768" -a "$QRSRP1" != "-44" ]; then
|
||||||
|
RSCP=$RSCP" (4G) dBm<br />"$QRSRP1
|
||||||
|
if [ -n "$QRSRP2" -a "$QRSRP2" != "-32768" -a "$QRSRP2" != "-44" ]; then
|
||||||
|
RSCP="$RSCP, $QRSRP2"
|
||||||
|
if [ -n "$QRSRP3" -a "$QRSRP3" != "-32768" -a "$QRSRP3" != "-44" ]; then
|
||||||
|
RSCP="$RSCP, $QRSRP3"
|
||||||
|
if [ -n "$QRSRP4" -a "$QRSRP4" != "-32768" -a "$QRSRP4" != "-44" ]; then
|
||||||
|
RSCP="$RSCP, $QRSRP4"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
RSCP=$RSCP" (5G) "
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ "$QRSRP2$QRSRP3$QRSRP4" != "-44-44-44" -a -z "$QENG5" ]; then
|
||||||
|
RSCP=$QRSRP1
|
||||||
|
if [ "$QRSRP3$QRSRP4" == "-140-140" -o "$QRSRP3$QRSRP4" == "-44-44" -o "$QRSRP3$QRSRP4" == "-32768-32768" ]; then
|
||||||
|
RSCP1="RxD "$(echo $QRSRP | cut -d, -f2)
|
||||||
|
else
|
||||||
|
RSCP=$RSCP" dBm (RxD "$QRSRP2" dBm)<br />"$QRSRP3
|
||||||
|
RSCP1="RxD "$QRSRP4
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
QNSM=$(echo "$QNSM" | grep -o "[0-9]")
|
||||||
|
if [ -n "$QNSM" ]; then
|
||||||
|
MODTYPE="6"
|
||||||
|
case $QNSM in
|
||||||
|
"0" )
|
||||||
|
NETMODE="1" ;;
|
||||||
|
"1" )
|
||||||
|
NETMODE="3" ;;
|
||||||
|
"2"|"5" )
|
||||||
|
NETMODE="5" ;;
|
||||||
|
"3" )
|
||||||
|
NETMODE="7" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ -n "$QNWP" ]; then
|
||||||
|
MODTYPE="6"
|
||||||
|
case $QNWP in
|
||||||
|
"AUTO" )
|
||||||
|
NETMODE="1" ;;
|
||||||
|
"WCDMA" )
|
||||||
|
NETMODE="5" ;;
|
||||||
|
"LTE" )
|
||||||
|
NETMODE="7" ;;
|
||||||
|
"LTE:NR5G" )
|
||||||
|
NETMODE="8" ;;
|
||||||
|
"NR5G" )
|
||||||
|
NETMODE="9" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
OX=$(echo "${OX//[ \"]/}")
|
||||||
|
|
||||||
|
REGV=$(echo "$OX" | grep -o "+C5GREG:2,[0-9],[A-F0-9]\{2,6\},[A-F0-9]\{5,10\},[0-9]\{1,2\}")
|
||||||
|
if [ -n "$REGV" ]; then
|
||||||
|
LAC5=$(echo "$REGV" | cut -d, -f3)
|
||||||
|
LAC5=$LAC5" ($(printf "%d" 0x$LAC5))"
|
||||||
|
CID5=$(echo "$REGV" | cut -d, -f4)
|
||||||
|
CID5L=$(printf "%010X" 0x$CID5)
|
||||||
|
RNC5=${CID5L:1:6}
|
||||||
|
RNC5=$RNC5" ($(printf "%d" 0x$RNC5))"
|
||||||
|
CID5=${CID5L:7:3}
|
||||||
|
CID5="Short $(printf "%X" 0x$CID5) ($(printf "%d" 0x$CID5)), Long $(printf "%X" 0x$CID5L) ($(printf "%d" 0x$CID5L))"
|
||||||
|
RAT=$(echo "$REGV" | cut -d, -f5)
|
||||||
|
fi
|
||||||
|
REGV=$(echo "$OX" | grep -o "+CEREG:2,[0-9],[A-F0-9]\{2,4\},[A-F0-9]\{5,8\}")
|
||||||
|
REGFMT="3GPP"
|
||||||
|
if [ -z "$REGV" ]; then
|
||||||
|
REGV=$(echo "$OX" | grep -o "+CEREG:2,[0-9],[A-F0-9]\{2,4\},[A-F0-9]\{1,3\},[A-F0-9]\{5,8\}")
|
||||||
|
REGFMT="SW"
|
||||||
|
fi
|
||||||
|
if [ -n "$REGV" ]; then
|
||||||
|
LAC=$(echo "$REGV" | cut -d, -f3)
|
||||||
|
LAC=$(printf "%04X" 0x$LAC)" ($(printf "%d" 0x$LAC))"
|
||||||
|
if [ $REGFMT = "3GPP" ]; then
|
||||||
|
CID=$(echo "$REGV" | cut -d, -f4)
|
||||||
|
else
|
||||||
|
CID=$(echo "$REGV" | cut -d, -f5)
|
||||||
|
fi
|
||||||
|
CIDL=$(printf "%08X" 0x$CID)
|
||||||
|
RNC=${CIDL:1:5}
|
||||||
|
RNC=$RNC" ($(printf "%d" 0x$RNC))"
|
||||||
|
CID=${CIDL:6:2}
|
||||||
|
CID="Short $(printf "%X" 0x$CID) ($(printf "%d" 0x$CID)), Long $(printf "%X" 0x$CIDL) ($(printf "%d" 0x$CIDL))"
|
||||||
|
|
||||||
|
else
|
||||||
|
REGV=$(echo "$OX" | grep -o "+CREG:2,[0-9],[A-F0-9]\{2,4\},[A-F0-9]\{2,8\}")
|
||||||
|
if [ -n "$REGV" ]; then
|
||||||
|
LAC=$(echo "$REGV" | cut -d, -f3)
|
||||||
|
CID=$(echo "$REGV" | cut -d, -f4)
|
||||||
|
if [ ${#CID} -gt 4 ]; then
|
||||||
|
LAC=$(printf "%04X" 0x$LAC)" ($(printf "%d" 0x$LAC))"
|
||||||
|
CIDL=$(printf "%08X" 0x$CID)
|
||||||
|
RNC=${CIDL:1:3}
|
||||||
|
CID=${CIDL:4:4}
|
||||||
|
CID="Short $(printf "%X" 0x$CID) ($(printf "%d" 0x$CID)), Long $(printf "%X" 0x$CIDL) ($(printf "%d" 0x$CIDL))"
|
||||||
|
else
|
||||||
|
LAC=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
LAC=""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
REGSTAT=$(echo "$REGV" | cut -d, -f2)
|
||||||
|
if [ "$REGSTAT" == "5" -a "$COPS" != "-" ]; then
|
||||||
|
COPS_MNC=$COPS_MNC" (Roaming)"
|
||||||
|
fi
|
||||||
|
if [ -n "$CID" -a -n "$CID5" ] && [ "$RAT" == "13" -o "$RAT" == "10" ]; then
|
||||||
|
LAC="4G $LAC, 5G $LAC5"
|
||||||
|
CID="4G $CID<br />5G $CID5"
|
||||||
|
RNC="4G $RNC, 5G $RNC5"
|
||||||
|
elif [ -n "$CID5" ]; then
|
||||||
|
LAC=$LAC5
|
||||||
|
CID=$CID5
|
||||||
|
RNC=$RNC5
|
||||||
|
fi
|
||||||
|
if [ -z "$LAC" ]; then
|
||||||
|
LAC="-"
|
||||||
|
CID="-"
|
||||||
|
RNC="-"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
LUPDATE=$(date +%s)
|
||||||
|
rm -fR /tmp/signal.txt
|
||||||
|
MODEZ=$(echo $MODE | tr -d '"')
|
||||||
|
{
|
||||||
|
echo 'PROVIDER="'"$PROVIDER"'"'
|
||||||
|
echo 'CSQ="'"$CSQ"'"'
|
||||||
|
echo 'CSQ_PER="'"$CSQ_PER"'"'
|
||||||
|
echo 'CSQ_RSSI="'"$CSQ_RSSI"'"'
|
||||||
|
echo 'ECIO="'"$ECIO"'"'
|
||||||
|
echo 'RSCP="'"$RSCP"'"'
|
||||||
|
echo 'ECIO1="'"$ECIO1"'"'
|
||||||
|
echo 'RSCP1="'"$RSCP1"'"'
|
||||||
|
echo 'MODE="'"$MODEZ"'"'
|
||||||
|
echo 'MODTYPE="'"$MODTYPE"'"'
|
||||||
|
echo 'NETMODE="'"$NETMODE"'"'
|
||||||
|
echo 'CHANNEL="'"$CHANNEL"'"'
|
||||||
|
echo 'LBAND="'"$LBAND"'"'
|
||||||
|
echo 'PCI="'"$PCI"'"'
|
||||||
|
echo 'TEMP="'"$CTEMP"'"'
|
||||||
|
echo 'SINR="'"$SINR"'"'
|
||||||
|
echo 'LASTUPDATE="'"$LUPDATE"'"'
|
||||||
|
echo 'COPS="'"$COPS"'"'
|
||||||
|
echo 'COPS_MCC="'"$COPS_MCC"'"'
|
||||||
|
echo 'COPS_MNC="'"$COPS_MNC"'"'
|
||||||
|
echo 'LAC="'"$LAC"'"'
|
||||||
|
echo 'LAC_NUM="'""'"'
|
||||||
|
echo 'CID="'"$CID"'"'
|
||||||
|
echo 'CID_NUM="'""'"'
|
||||||
|
echo 'RNC="'"$RNC"'"'
|
||||||
|
echo 'RNC_NUM="'""'"'
|
||||||
|
} > /tmp/signal.txt
|
||||||
|
|
||||||
|
# Pregenerate JSON File
|
||||||
|
/usrdata/simpleadmin/scripts/tojson.sh /tmp/signal.txt > /tmp/modemstatus.json
|
||||||
22
scripts/tojson.sh
Normal file
22
scripts/tojson.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# sarav (hello@grity.com)
|
||||||
|
# convert key=value to json
|
||||||
|
# Created at Gritfy ( Devops Junction )
|
||||||
|
#
|
||||||
|
|
||||||
|
file_name=$1
|
||||||
|
last_line=$(wc -l < $file_name)
|
||||||
|
current_line=0
|
||||||
|
|
||||||
|
echo "{"
|
||||||
|
while read line
|
||||||
|
do
|
||||||
|
current_line=$(($current_line + 1))
|
||||||
|
if [[ $current_line -ne $last_line ]]; then
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
echo $line|awk -F'=' '{ print " \""$1"\" : "$2","}'|grep -iv '\"#'
|
||||||
|
else
|
||||||
|
echo $line|awk -F'=' '{ print " \""$1"\" : "$2""}'|grep -iv '\"#'
|
||||||
|
fi
|
||||||
|
done < $file_name
|
||||||
|
echo "}"
|
||||||
10
systemd/simpleadmin_generate_status.service
Normal file
10
systemd/simpleadmin_generate_status.service
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Simpleadmin service to generate status from modem
|
||||||
|
Requires=socat-smd11.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usrdata/simpleadmin/scripts/build_modem_status
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
11
systemd/simpleadmin_httpd.service
Normal file
11
systemd/simpleadmin_httpd.service
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=SimpleAdmin httpd service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=/usr/sbin/httpd -f -h /usrdata/simpleadmin/www -p 8080
|
||||||
|
ExecStop=/bin/kill -WINCH ${MAINPID}
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
12
systemd/ttl-override.service
Normal file
12
systemd/ttl-override.service
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=TTL Override
|
||||||
|
After=ql-netd.service
|
||||||
|
DefaultDependencies=no
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usrdata/simpleadmin/ttl/ttl-override start
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
53
ttl/ttl-override
Normal file
53
ttl/ttl-override
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
# Adapted from https://github.com/natecarlson/quectel-rgmii-configuration-notes/blob/main/files/ttl-override
|
||||||
|
# Uses ttlvalue file to read what ttl should be set to
|
||||||
|
|
||||||
|
|
||||||
|
if [ -f /usrdata/simpleadmin/ttl/ttlvalue ];
|
||||||
|
then
|
||||||
|
ttlfile=$(</usrdata/simpleadmin/ttl/ttlvalue)
|
||||||
|
TTLVALUE=$(echo $ttlfile | grep -o "[0-9]\{1,3\}")
|
||||||
|
|
||||||
|
if [ -z "${TTLVALUE}" ]; then
|
||||||
|
echo "Couldnt get proper ttl value from file" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Couldnt find ttlvalue file, lets generate one with 0 ttlvalue (0 = disabled)
|
||||||
|
touch /usrdata/simpleadmin/ttl/ttlvalue && echo '0' > /usrdata/simpleadmin/ttl/ttlvalue
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
if (( $TTLVALUE > 0 )); then
|
||||||
|
echo "Adding TTL override rules: "
|
||||||
|
iptables -t mangle -I POSTROUTING -o rmnet+ -j TTL --ttl-set ${TTLVALUE}
|
||||||
|
ip6tables -t mangle -I POSTROUTING -o rmnet+ -j HL --hl-set ${TTLVALUE}
|
||||||
|
else
|
||||||
|
echo "TTLVALUE set to 0, nothing to do..."
|
||||||
|
fi
|
||||||
|
echo "done"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
if (( $TTLVALUE > 0 )); then
|
||||||
|
echo "Removing TTL override rules: "
|
||||||
|
iptables -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set ${TTLVALUE} &>/dev/null || true
|
||||||
|
ip6tables -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set ${TTLVALUE} &>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "TTLVALUE set to 0, nothing to do..."
|
||||||
|
fi
|
||||||
|
echo "done"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage ttl-override { start | stop | restart }" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
1
ttl/ttlvalue
Normal file
1
ttl/ttlvalue
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
49
uninstall_on_modem.sh
Normal file
49
uninstall_on_modem.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Removes SimpleAdmin
|
||||||
|
#
|
||||||
|
|
||||||
|
read -p "Do you want to uninstall SimpleAdmin (yes/no) " yn
|
||||||
|
|
||||||
|
case $yn in
|
||||||
|
yes ) echo ok, we will proceed;;
|
||||||
|
no ) echo exiting...;
|
||||||
|
exit;;
|
||||||
|
* ) echo invalid response;
|
||||||
|
exit 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ExecStop
|
||||||
|
|
||||||
|
systemctl stop simpleadmin_generate_status.timer
|
||||||
|
systemctl stop simpleadmin_generate_status
|
||||||
|
systemctl stop simpleadmin_httpd
|
||||||
|
systemctl stop ttl-override
|
||||||
|
|
||||||
|
#Remove from /usrdata
|
||||||
|
|
||||||
|
rm -rf /usrdata/simpleadmin
|
||||||
|
|
||||||
|
# Remount
|
||||||
|
mount -o remount,rw /
|
||||||
|
|
||||||
|
# Copy systemd init files & reload
|
||||||
|
#remove links
|
||||||
|
rm /lib/systemd/system/multi-user.target.wants/simpleadmin_httpd.service
|
||||||
|
rm /lib/systemd/system/multi-user.target.wants/simpleadmin_generate_status.service
|
||||||
|
rm /lib/systemd/system/timers.target.wants/simpleadmin_generate_status.timer
|
||||||
|
rm /lib/systemd/system/multi-user.target.wants/ttl-override.service
|
||||||
|
|
||||||
|
#remove files
|
||||||
|
rm /lib/systemd/system/simpleadmin_generate_status.timer
|
||||||
|
rm /lib/systemd/system/simpleadmin_httpd.service
|
||||||
|
rm /lib/systemd/system/simpleadmin_generate_status.service
|
||||||
|
rm /lib/systemd/system/ttl-override.service
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Link systemd files
|
||||||
|
|
||||||
|
# Remount readonly
|
||||||
|
mount -o remount,ro /
|
||||||
123
www/atcommander.html
Normal file
123
www/atcommander.html
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Quectel Simple Admin</title>
|
||||||
|
|
||||||
|
<script src="/js/alpinejs.min.js" defer></script>
|
||||||
|
<link rel="stylesheet" href="/css/bulma.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/admin.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- START NAV -->
|
||||||
|
<nav class="navbar is-white" x-data="{ isOpen: false }">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item brand-text" href="/">
|
||||||
|
Quectel Simple Admin
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger burger" @click="isOpen = !isOpen">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="navMenu" class="navbar-menu" :class="isOpen ? 'is-active' : ''">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/atcommander.html">
|
||||||
|
ATI Commander
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ttl.html">
|
||||||
|
TTL Enabler
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- END NAV -->
|
||||||
|
<div class="container" x-data="atCommands()">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
AT Command
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">AT Command</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" placeholder="ATI" x-model="atcmd">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-success" @click="sendAtCommand()">
|
||||||
|
Send AT Command
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-8">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
ATI Response
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<textarea class="textarea" placeholder="ATI Responses Will Appear Here" rows="10"
|
||||||
|
x-text="atCommandResponse"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function atCommands() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
atcmd: null,
|
||||||
|
atCommandResponse: null,
|
||||||
|
sendAtCommand() {
|
||||||
|
fetch('/cgi-bin/get_atcommand?' + new URLSearchParams({
|
||||||
|
atcmd: this.atcmd,
|
||||||
|
}))
|
||||||
|
.then((res) => {
|
||||||
|
return res.text();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
this.atCommandResponse = data;
|
||||||
|
this.isLoading = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
30
www/cgi-bin/get_atcommand
Normal file
30
www/cgi-bin/get_atcommand
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g')
|
||||||
|
function urldecode() { : "${*//+/ }"; echo -e "${_//%/\\x}"; }
|
||||||
|
|
||||||
|
if [ "${QUERY_STRING}" ]; then
|
||||||
|
|
||||||
|
export IFS="&"
|
||||||
|
for cmd in ${QUERY_STRING}; do
|
||||||
|
|
||||||
|
if [ "$(echo $cmd | grep '=')" ]; then
|
||||||
|
key=$(echo $cmd | awk -F '=' '{print $1}')
|
||||||
|
value=$(echo $cmd | awk -F '=' '{print $2}')
|
||||||
|
eval $key=$value
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
MYATCMD=$(printf '%b\n' "${atcmd//%/\\x}")
|
||||||
|
if [ -n "${MYATCMD}" ]; then
|
||||||
|
x=$(urldecode "$atcmd")
|
||||||
|
runcmd=$(echo -en "$x\r\n" | microcom -t 2000 /dev/ttyOUT)
|
||||||
|
# runcmd=$(/usrdata/simpleadmin/scripts/doAT.py "$MYATCMD")
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content-type: text/plain"
|
||||||
|
echo $x
|
||||||
|
echo ""
|
||||||
|
echo $runcmd
|
||||||
13
www/cgi-bin/get_csq
Normal file
13
www/cgi-bin/get_csq
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ ! -f /tmp/modemstatus.json ]
|
||||||
|
then
|
||||||
|
/usrdata/simpleadmin/scripts/modemstatus_parse.sh > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
runcmd=$(</tmp/modemstatus.json)
|
||||||
|
|
||||||
|
echo "Content-type: text/json"
|
||||||
|
echo ""
|
||||||
|
cat <<EOT
|
||||||
|
$runcmd
|
||||||
19
www/cgi-bin/get_ttl_status
Normal file
19
www/cgi-bin/get_ttl_status
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check iptables for ttlvalue
|
||||||
|
ttlvalue=$(iptables -t mangle -vnL | grep TTL | awk '{print $13}')
|
||||||
|
ttlenabled=true;
|
||||||
|
|
||||||
|
# Set Variables
|
||||||
|
if [ -z "${ttlvalue}" ]; then
|
||||||
|
ttlvalue=0
|
||||||
|
ttlenabled=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content-type: text/json"
|
||||||
|
echo ""
|
||||||
|
cat <<EOT
|
||||||
|
{
|
||||||
|
"isEnabled": $ttlenabled,
|
||||||
|
"ttl": $ttlvalue
|
||||||
|
}
|
||||||
61
www/cgi-bin/set_ttl
Normal file
61
www/cgi-bin/set_ttl
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Get query
|
||||||
|
QUERY_STRING=$(echo "${QUERY_STRING}" | sed 's/;//g')
|
||||||
|
|
||||||
|
if [ "${QUERY_STRING}" ]; then
|
||||||
|
|
||||||
|
export IFS="&"
|
||||||
|
for cmd in ${QUERY_STRING}; do
|
||||||
|
|
||||||
|
if [ "$(echo $cmd | grep '=')" ]; then
|
||||||
|
key=$(echo $cmd | awk -F '=' '{print $1}')
|
||||||
|
value=$(echo $cmd | awk -F '=' '{print $2}')
|
||||||
|
eval $key=$value
|
||||||
|
fi
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
setTTL=$(printf '%b\n' "${ttlvalue//%/\\x}")
|
||||||
|
|
||||||
|
if [ -n "${setTTL}" ]; then
|
||||||
|
# Stop Service To Remove Rules
|
||||||
|
/usrdata/simpleadmin/ttl/ttl-override stop
|
||||||
|
|
||||||
|
# Check iptables is still set
|
||||||
|
ttlcheck=$(iptables -t mangle -vnL | grep TTL | awk '{print $13}')
|
||||||
|
|
||||||
|
# If TTL is still set manually remove values
|
||||||
|
if [ !-z "${ttlcheck}" ]; then
|
||||||
|
iptables -t mangle -D POSTROUTING -o rmnet+ -j TTL --ttl-set ${ttlcheck} &>/dev/null || true
|
||||||
|
ip6tables -t mangle -D POSTROUTING -o rmnet+ -j HL --hl-set ${ttlcheck} &>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Echo TTL to file
|
||||||
|
echo $setTTL > /usrdata/simpleadmin/ttl/ttlvalue
|
||||||
|
|
||||||
|
# Set Start Service
|
||||||
|
/usrdata/simpleadmin/ttl/ttl-override start
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Check iptables for ttlvalue
|
||||||
|
ttlvalue=$(iptables -t mangle -vnL | grep TTL | awk '{print $13}')
|
||||||
|
ttlenabled=true;
|
||||||
|
|
||||||
|
# Set Variables
|
||||||
|
if [ -z "${ttlvalue}" ]; then
|
||||||
|
ttlvalue=0
|
||||||
|
ttlenabled=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Content-type: text/json"
|
||||||
|
echo ""
|
||||||
|
cat <<EOT
|
||||||
|
{
|
||||||
|
"isEnabled": $ttlenabled,
|
||||||
|
"ttl": $ttlvalue
|
||||||
|
}
|
||||||
86
www/css/admin.css
Normal file
86
www/css/admin.css
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
:root{
|
||||||
|
::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#efefef;border-radius:6px}::-webkit-scrollbar-thumb{background:#d5d5d5;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#c4c4c4}
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
height: 100%;
|
||||||
|
background: #ECF0F3;
|
||||||
|
}
|
||||||
|
nav.navbar {
|
||||||
|
border-top: 4px solid #276cda;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.navbar-item.brand-text {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
.navbar-item, .navbar-link {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.columns {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
.menu-label {
|
||||||
|
color: #8F99A3;
|
||||||
|
letter-spacing: 1.3;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.menu-list a {
|
||||||
|
color: #0F1D38;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.menu-list a:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #276cda;
|
||||||
|
}
|
||||||
|
.menu-list a.is-active {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #276cda;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.18);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.card-header-title {
|
||||||
|
color: #8F99A3;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
.info-tiles {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
.info-tiles .subtitle {
|
||||||
|
font-weight: 300;
|
||||||
|
color: #8F99A3;
|
||||||
|
}
|
||||||
|
.hero.welcome.is-info {
|
||||||
|
background: #36D1DC;
|
||||||
|
background: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);
|
||||||
|
background: linear-gradient(to right, #5B86E5, #36D1DC);
|
||||||
|
}
|
||||||
|
.hero.welcome .title, .hero.welcome .subtitle {
|
||||||
|
color: hsl(192, 17%, 99%);
|
||||||
|
}
|
||||||
|
.card .content {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.card-footer-item {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #8F99A3;
|
||||||
|
}
|
||||||
|
.card-footer-item:hover {
|
||||||
|
}
|
||||||
|
.card-table .table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.events-card .card-table {
|
||||||
|
height: 330px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
1
www/css/bulma.css
vendored
Normal file
1
www/css/bulma.css
vendored
Normal file
File diff suppressed because one or more lines are too long
232
www/index.html
Normal file
232
www/index.html
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Quectel Simple Admin</title>
|
||||||
|
<script src="/js/alpinejs.min.js" defer></script>
|
||||||
|
<link rel="stylesheet" href="/css/bulma.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/admin.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- START NAV -->
|
||||||
|
<nav class="navbar is-white" x-data="{ isOpen: false }">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item brand-text" href="/">
|
||||||
|
Quectel Simple Admin
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger burger" @click="isOpen = !isOpen">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="navMenu" class="navbar-menu" :class="isOpen ? 'is-active' : ''">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/atcommander.html">
|
||||||
|
ATI Commander
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ttl.html">
|
||||||
|
TTL Enabler
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- END NAV -->
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-12" x-data="getSignalData()" x-init="init()">
|
||||||
|
<section class="hero is-info welcome is-small">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
Welcome to Quectel RJ45 Board Simple Admin
|
||||||
|
</h1>
|
||||||
|
<h2 class="subtitle">
|
||||||
|
Data Updated: <span x-text="lastUpdate"></span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="info-tiles">
|
||||||
|
<div class="tile is-ancestor has-text-centered">
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child box">
|
||||||
|
<p class="title" x-text="csqData.MODE"></p>
|
||||||
|
<p class="subtitle">Network</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child box">
|
||||||
|
<p class="title" x-text="csqData.CSQ_PER"></p>
|
||||||
|
<p class="subtitle">Signal Strength</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child box">
|
||||||
|
<p class="title" x-text="csqData.TEMP"></p>
|
||||||
|
<p class="subtitle">Modem Temperature</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div class="tile is-parent">
|
||||||
|
<article class="tile is-child box">
|
||||||
|
<p class="title" x-html="csqData.LBAND"></p>
|
||||||
|
<p class="subtitle">Band</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-6">
|
||||||
|
<div class="card events-card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
Signal Information
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-table">
|
||||||
|
<div class="content">
|
||||||
|
<table class="table is-fullwidth is-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Provider</th>
|
||||||
|
<td x-text="csqData.PROVIDER">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>CSQ</th>
|
||||||
|
<td x-text="csqData.CSQ">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Signal Strength</th>
|
||||||
|
<td x-text="csqData.CSQ_PER">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>RSSI</th>
|
||||||
|
<td x-text="csqData.CSQ_RSSI">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>ECIO<sup>3G</sup>/RSRQ<sup>4G</sup>/SS_RSRQ<sup>5G</sup></th>
|
||||||
|
<td x-html="csqData.ECIO">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>RSCP<sup>3G</sup>/RSRP<sup>4G</sup>/SS_RSRP<sup>5G</sup></th>
|
||||||
|
<td x-html="csqData.RSCP">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>SINR</th>
|
||||||
|
<td x-html="csqData.SINR">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-6">
|
||||||
|
<div class="card events-card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
Cell Information
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-table">
|
||||||
|
<div class="content">
|
||||||
|
<table class="table is-fullwidth is-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>MCC MNC</th>
|
||||||
|
<td>
|
||||||
|
<span x-text="csqData.COPS_MCC"></span>
|
||||||
|
/
|
||||||
|
<span x-text="csqData.COPS_MNC"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>RNC<sup>3G</sup>/eNB ID<sup>4G/5G</sup></th>
|
||||||
|
<td>
|
||||||
|
<span x-text="csqData.RNC"></span>
|
||||||
|
<span x-text="csqData.RNC_NUM"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Lag<sup>3G</sup>/TAC<sup>4G/5G</sup></th>
|
||||||
|
<td>
|
||||||
|
<span x-text="csqData.LAC"></span>
|
||||||
|
<span x-text="csqData.LAC_NUM"></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Cell ID</th>
|
||||||
|
<td x-text="csqData.CID">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Band</th>
|
||||||
|
<td x-html="csqData.LBAND">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Channel</th>
|
||||||
|
<td x-text="csqData.CHANNEL">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>PCI</th>
|
||||||
|
<td x-text="csqData.PCI">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function getSignalData() {
|
||||||
|
return {
|
||||||
|
csqData: {},
|
||||||
|
lastUpdate: new Date().toLocaleString(),
|
||||||
|
getcsq() {
|
||||||
|
fetch('/cgi-bin/get_csq')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.csqData = data;
|
||||||
|
this.lastUpdate = new Date(data.LASTUPDATE * 1000).toLocaleString();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
init() {
|
||||||
|
this.getcsq();
|
||||||
|
setInterval(() => {
|
||||||
|
this.getcsq();
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
5
www/js/alpinejs.min.js
vendored
Normal file
5
www/js/alpinejs.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
134
www/ttl.html
Normal file
134
www/ttl.html
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Quectel Simple Admin</title>
|
||||||
|
|
||||||
|
<script src="/js/alpinejs.min.js" defer></script>
|
||||||
|
<link rel="stylesheet" href="/css/bulma.css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/admin.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- START NAV -->
|
||||||
|
<nav class="navbar is-white" x-data="{ isOpen: false }">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item brand-text" href="/">
|
||||||
|
Quectel Simple Admin
|
||||||
|
</a>
|
||||||
|
<a role="button" class="navbar-burger burger" @click="isOpen = !isOpen">
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
<span aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div id="navMenu" class="navbar-menu" :class="isOpen ? 'is-active' : ''">
|
||||||
|
<div class="navbar-start">
|
||||||
|
<a class="navbar-item" href="/">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/atcommander.html">
|
||||||
|
ATI Commander
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ttl.html">
|
||||||
|
TTL Enabler
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<!-- END NAV -->
|
||||||
|
<div class="container" x-data="ttlCommands()" x-init="init">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column is-8">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
TTL Enabler
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<p>
|
||||||
|
<h2>TTL Status</h2> <br>
|
||||||
|
TTL is <span class="tag is-large" :class="ttldata.isEnabled ? 'is-success' : 'is-danger'" x-text="ttldata.isEnabled == true ? 'ON' : 'OFF'"></span>
|
||||||
|
<br />
|
||||||
|
TTL Set to <span x-text="ttldata.ttl"></span>
|
||||||
|
</p>
|
||||||
|
<div class="field">
|
||||||
|
<label class="label">Set TTL</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="number" placeholder="64"
|
||||||
|
x-model="newTTL">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-link" @click="setTTL()">Set TTL</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="column is-4">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
Common TTL For Providers
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
<ul>
|
||||||
|
<li>Magenta: 65</li>
|
||||||
|
<li>Red: 88</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function ttlCommands() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
ttldata: null,
|
||||||
|
newTTL: 0,
|
||||||
|
init() {
|
||||||
|
fetch('/cgi-bin/get_ttl_status')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.ttldata = data
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setTTL() {
|
||||||
|
fetch('/cgi-bin/set_ttl?' + new URLSearchParams({
|
||||||
|
ttlvalue: this.newTTL,
|
||||||
|
}))
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.ttldata = data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
Reference in New Issue
Block a user