Compare commits

...

3 Commits

Author SHA1 Message Date
99b0ca4fcc Add prometheus exporter 2025-09-03 22:46:13 +02:00
50f530889b Add state information endpoint 2025-09-03 21:19:53 +02:00
f11ee2af7e Restructure command line 2025-09-03 21:03:34 +02:00
3 changed files with 216 additions and 16 deletions

View File

@@ -1,4 +1,5 @@
from . import Mu5001Tool
from .prometheus_exporter import prometheus_exporter
import argparse
from pprint import pprint
@@ -8,28 +9,55 @@ parser = argparse.ArgumentParser(prog="mu5001tool")
parser.add_argument("--stok", dest="stok", help="Initial session token to use for commands")
parser.add_argument("--password", dest="password", help="Password for authentication against the device")
def main():
subparsers = parser.add_subparsers()
args = parser.parse_args()
h = Mu5001Tool()
if args.stok is not None:
h.set_stok(args.stok)
if args.password is not None:
print(h.login(args.password))
if h.is_logged_in():
def run_status(m):
if m.is_logged_in():
print("Is logged in")
else:
print("Is not logged in")
pprint(h.status())
pprint(m.status())
pprint(h.network_information())
pprint(m.network_information())
pprint(h.apn_info())
pprint(m.apn_info())
pprint(m.state_information())
sp_status = subparsers.add_parser("status", help="General modem status information")
sp_status.set_defaults(func=run_status)
def run_prometheus_exporter(m):
prometheus_exporter(m)
sp_prometheus_exporter = subparsers.add_parser("prometheus-exporter", help="Serve metrics as prometheus exporter")
sp_prometheus_exporter.set_defaults(func=run_prometheus_exporter)
def main():
args = parser.parse_args()
if "func" not in args:
parser.print_help()
exit()
function_arguments = dict(vars(args))
function_arguments.pop("func")
function_arguments.pop("stok")
function_arguments.pop("password")
m = Mu5001Tool()
if args.stok is not None:
m.set_stok(args.stok)
if args.password is not None:
m.set_password(args.password)
m.login()
args.func(m=m, **function_arguments)
if __name__ == "__main__":
main()

View File

@@ -5,6 +5,8 @@ import urllib
class Mu5001Tool:
def __init__(self, host="http://192.168.0.1"):
self._password = None
self.host = host
self.session = requests.Session()
@@ -36,6 +38,9 @@ class Mu5001Tool:
"cmd": "LD",
}).get("LD")
def set_password(self, password):
self._password = password
def hashed_login_password(self, password):
ld = self.get_ld()
@@ -44,7 +49,13 @@ class Mu5001Tool:
return login_sha256(login_sha256(password) + ld)
def login(self, password):
def login(self, password=None):
if password is None:
password = self._password
if password is None:
raise ValueError("No password provided")
return self.set_cmd_process({
"goformId": "LOGIN",
"password": self.hashed_login_password(password),
@@ -167,3 +178,9 @@ class Mu5001Tool:
"multi_data": 1,
"cmd": "network_type,rssi,lte_rssi,rscp,lte_rsrp,Z5g_snr,Z5g_rsrp,ZCELLINFO_band,Z5g_dlEarfcn,lte_ca_pcell_arfcn,lte_ca_pcell_band,lte_ca_scell_band,lte_ca_pcell_bandwidth,lte_ca_scell_info,lte_ca_scell_bandwidth,wan_lte_ca,lte_pci,Z5g_CELL_ID,Z5g_SINR,cell_id,wan_lte_ca,lte_ca_pcell_band,lte_ca_pcell_bandwidth,lte_ca_scell_band,lte_ca_scell_bandwidth,lte_ca_pcell_arfcn,lte_ca_scell_arfcn,lte_multi_ca_scell_info,wan_active_band,nr5g_pci,nr5g_action_band,nr5g_cell_id,lte_snr,ecio,wan_active_channel,nr5g_action_channel",
})
def state_information(self):
return self.get_cmd_process({
"multi_data": 1,
"cmd": "modem_main_state,pin_status,opms_wan_mode,opms_wan_auto_mode,loginfo,new_version_state,current_upgrade_state,is_mandatory,wifi_dfs_status,battery_value,ppp_dial_conn_fail_counter,dhcp_wan_status,wifi_chip1_ssid1_auth_mode,wifi_chip2_ssid1_auth_mode,signalbar,network_type,network_provider,ppp_status,simcard_roam,spn_name_data,spn_b1_flag,spn_b2_flag,wifi_onoff_state,wifi_chip1_ssid1_ssid,wifi_chip2_ssid1_ssid,wan_lte_ca,monthly_rx_bytes,monthly_tx_bytes,pppoe_status,dhcp_wan_status,static_wan_status,rmcc,rmnc,mdm_mcc,battery_charg_type,external_charging_flag,mode_main_state,battery_temp,EX_SSID1,sta_ip_status,EX_wifi_profile,m_ssid_enable,RadioOff,wifi_chip1_ssid1_access_sta_num,wifi_chip2_ssid1_access_sta_num,lan_ipaddr,station_mac,wifi_access_sta_num,battery_charging,battery_vol_percent,battery_pers,realtime_tx_bytes,realtime_rx_bytes,realtime_time,realtime_tx_thrpt,realtime_rx_thrpt,monthly_time,date_month,data_volume_limit_switch,data_volume_limit_size,data_volume_alert_percent,data_volume_limit_unit,roam_setting_option,upg_roam_switch,ssid,wifi_enable,wifi_5g_enable,check_web_conflict,dial_mode,ppp_dial_conn_fail_counter,privacy_read_flag,is_night_mode,vpn_conn_status,wan_connect_status,sms_received_flag,sts_received_flag,sms_unread_num,wifi_chip1_ssid2_access_sta_num,wifi_chip2_ssid2_access_sta_num",
})

View File

@@ -0,0 +1,155 @@
from . import Mu5001Tool
from http.server import BaseHTTPRequestHandler, HTTPServer, HTTPStatus
import socket
import traceback
class HTTPServerV6(HTTPServer):
address_family = socket.AF_INET6
def make_prometheus_exporter_request_handler(m):
class PrometheusExporterRequestHandler(BaseHTTPRequestHandler):
def do_GET(self, head_only=False):
if self.path == "/":
self.make_response(
"Prometheus Exporter for MU5001",
head_only=head_only
)
elif self.path == "/metrics":
try:
self.make_response(
self.export(),
head_only=head_only
)
except Exception as e:
traceback.print_exc()
self.send_error(HTTPStatus.INTERNAL_SERVER_ERROR, "Failed to fetch metrics")
else:
self.send_error(HTTPStatus.NOT_FOUND, "File not found")
def do_HEAD(self):
self.do_GET(head_only=True)
def make_response(self, content, head_only=False):
encoded = content.encode("utf-8")
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Conten-Length", str(len(encoded)))
self.end_headers()
if not head_only:
self.wfile.write(encoded)
def export(self):
cmds_as_metric_value = [
"adjfsdfj",
"battery_charging",
"battery_pers",
"battery_temp",
"battery_value",
"battery_vol_percent",
"data_volume_alert_percent",
"data_volume_limit_size",
"data_volume_limit_switch",
"dhcp_wan_status",
"lte_ca_pcell_band",
"lte_ca_pcell_bandwidth",
"lte_ca_scell_band",
"lte_ca_scell_bandwidth",
"lte_rsrp",
"lte_rssi",
"lte_snr",
"mdm_mcc",
"monthly_rx_bytes",
"monthly_time",
"monthly_tx_bytes",
"nr5g_action_channel",
"pin_status",
"ppp_dial_conn_fail_counter",
"realtime_rx_bytes",
"realtime_rx_thrpt",
"realtime_time",
"realtime_tx_bytes",
"realtime_tx_thrpt",
"rmcc",
"rmnc",
"signalbar",
"sms_unread_num",
"wan_active_channel",
"wifi_5g_enable",
"wifi_access_sta_num",
"wifi_chip1_ssid1_access_sta_num",
"wifi_chip1_ssid2_access_sta_num",
"wifi_chip2_ssid1_access_sta_num",
"wifi_chip2_ssid2_access_sta_num",
"wifi_onoff_state",
]
cmds_as_metric_label = [
"battery_charg_type",
"data_volume_limit_unit",
"dial_mode",
"mode_main_state",
"modem_main_state",
"network_provider",
"network_type",
"opms_wan_auto_mode",
"opms_wan_mode",
"ppp_status",
"roam_setting_option",
"simcard_roam",
"spn_name_data",
"wan_connect_status",
"wan_lte_ca",
"wifi_chip1_ssid1_auth_mode",
"wifi_chip1_ssid1_ssid",
"wifi_chip2_ssid1_auth_mode",
"wifi_chip2_ssid1_ssid",
"cell_id",
"lte_pci",
"nr5g_action_band",
"nr5g_pci",
"wan_active_band",
]
data = m.get_cmd_process({
"multi_data": 1,
"cmd": ",".join(cmds_as_metric_value + cmds_as_metric_label),
})
out = []
for cmd in cmds_as_metric_value:
d = data.get(cmd)
if d is None:
continue
try:
v = int(d)
except ValueError as e:
try:
v = float(d)
except ValueError as e:
raise ValueError(f"cmd: {cmd}: {e}")
out.append(f"mu5001_{cmd} {v}")
for cmd in cmds_as_metric_label:
d = data.get(cmd)
if d is None or d == "":
continue
out.append(f"mu5001_{cmd}{{{cmd}=\"{d}\"}} 1")
out.sort()
return "\n".join(out)
return PrometheusExporterRequestHandler
def prometheus_exporter(m):
with HTTPServerV6(("::1", 9242), make_prometheus_exporter_request_handler(m)) as httpd:
print("Starting prometheus exporter on http://[{}]:{}".format(*httpd.socket.getsockname()[:2]))
httpd.serve_forever()