Merge pull request #80 from pavelmachek/m_12_cellular

cellular: simple cellular-network example
This commit is contained in:
Thomas Farstrike
2026-03-11 09:35:34 +01:00
committed by GitHub
4 changed files with 342 additions and 0 deletions
@@ -0,0 +1,24 @@
{
"name": "Cellular",
"publisher": "Pavel Machek",
"short_description": "Application for placing phone calls",
"long_description": "Simple application for monitoring network state and placing phone calls.",
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.cellular/icons/cz.ucw.pavel.cellular_0.0.1_64x64.png",
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.cellular/mpks/cz.ucw.pavel.cellular_0.0.1.mpk",
"fullname": "cz.ucw.pavel.cellular",
"version": "0.0.1",
"category": "utilities",
"activities": [
{
"entrypoint": "assets/main.py",
"classname": "Main",
"intent_filters": [
{
"action": "main",
"category": "launcher"
}
]
}
]
}
@@ -0,0 +1,151 @@
from mpos import Activity
"""
Simple cellular-network example
"""
import time
import os
import json
try:
import lvgl as lv
except ImportError:
pass
from mpos import Activity, MposKeyboard
TMP = "/tmp/cmd.json"
def run_cmd_json(cmd):
rc = os.system(cmd + " > " + TMP)
if rc != 0:
raise RuntimeError("command failed")
with open(TMP, "r") as f:
data = f.read().strip()
return json.loads(data)
def dbus_json(cmd):
return run_cmd_json("sudo /home/mobian/g/MicroPythonOS/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/phone.py " + cmd)
class CellularManager:
def init(self):
v = dbus_json("loc_on")
def poll(self):
v = dbus_json("signal")
print(v)
self.signal = v
def call(self, num):
v = dbus_json("call '%s'" % num)
def sms(self, num, text):
v = dbus_json("call '%s' '%s'" % (num, text))
cm = CellularManager()
# ------------------------------------------------------------
# User interface
# ------------------------------------------------------------
class Main(Activity):
def __init__(self):
super().__init__()
# --------------------
def onCreate(self):
self.screen = lv.obj()
#self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE)
# Top labels
self.lbl_time = lv.label(self.screen)
self.lbl_time.set_style_text_font(lv.font_montserrat_34, 0)
self.lbl_time.set_text("Startup...")
self.lbl_time.align(lv.ALIGN.TOP_LEFT, 6, 22)
self.lbl_date = lv.label(self.screen)
self.lbl_date.set_style_text_font(lv.font_montserrat_20, 0)
self.lbl_date.align_to(self.lbl_time, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5)
self.lbl_date.set_text("(details here?")
self.lbl_month = lv.label(self.screen)
self.lbl_month.set_style_text_font(lv.font_montserrat_20, 0)
self.lbl_month.align(lv.ALIGN.TOP_RIGHT, -6, 22)
self.number = lv.textarea(self.screen)
#self.number.set_accepted_chars("0123456789")
self.number.set_one_line(True)
self.number.set_style_text_font(lv.font_montserrat_34, 0)
self.number.align_to(self.lbl_date, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 12)
self.call = lv.button(self.screen)
self.call.align_to(self.number, lv.ALIGN.OUT_RIGHT_MID, 2, 0)
self.call.add_event_cb(lambda e: self.on_call(), lv.EVENT.CLICKED, None)
# Two text areas on single screen don't work well.
# Perhaps make it dialog?
#self.sms = lv.textarea(self.screen)
#self.sms.set_style_text_font(lv.font_montserrat_24, 0)
#self.sms.align_to(self.number, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10)
l = lv.label(self.call)
l.set_text("Call")
l.center()
kb = lv.keyboard(self.screen)
kb.set_textarea(self.number)
kb.set_size(lv.pct(100), lv.pct(33))
self.setContentView(self.screen)
cm.init()
def onResume(self, screen):
self.timer = lv.timer_create(self.tick, 60000, None)
self.tick(0)
def onPause(self, screen):
if self.timer:
self.timer.delete()
self.timer = None
# --------------------
def on_call(self):
num = self.number.get_text()
cm.call(num)
def on_sms(self):
num = self.number.get_text()
text = self.sms.get_text()
cm.sms(num, text)
def tick(self, t):
now = time.localtime()
y, m, d = now[0], now[1], now[2]
hh, mm, ss = now[3], now[4], now[5]
self.lbl_month.set_text("busy")
cm.poll()
s = ""
s += cm.signal["OperatorName"] + "\n"
s += "RegistrationState %d\n" % cm.signal["RegistrationState"]
s += "State %d " % cm.signal["State"]
sq, re = cm.signal["SignalQuality"]
s += "Signal %d\n" % sq
self.lbl_month.set_text(s)
self.lbl_time.set_text("%02d:%02d" % (hh, mm))
s = ""
self.lbl_date.set_text("%04d-%02d-%02d %s" % (y, m, d, s))
# --------------------
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
from pydbus import SystemBus, Variant
import pydbus
import time
import sys
import json
"""
Lets make it class Phone, one method would be reading battery information, one would be reading operator name / signal strength, one would be getting wifi enabled/disabled / AP name.
sudo apt install python3-pydbus
sudo mmcli --list-modems
sudo mmcli -m 6 --location-enable-gps-nmea --location-enable-gps-raw
"""
class Phone:
verbose = False
def __init__(self):
self.bus = pydbus.SystemBus()
def init_sess(self):
self.sess = pydbus.SessionBus()
def get_mobile_loc(self):
loc = None
mm = self.bus.get("org.freedesktop.ModemManager1")
for modem_path in mm.GetManagedObjects():
modem = self.bus.get(".ModemManager1", modem_path)
loc = modem.GetLocation()
return loc
def get_cell_signal(self):
loc = None
mm = self.bus.get("org.freedesktop.ModemManager1")
for modem_path in mm.GetManagedObjects():
modem = self.bus.get(".ModemManager1", modem_path)
loc = {}
def attr(v):
loc[v] = getattr(modem, v, None)
attr("OperatorName")
attr("OperatorCode") # 0..11 according to MMState
attr("State") # 0..11 according to MMState
attr("AccessTechnologies")
attr("Model")
attr("Manufacturer")
attr("Revision")
attr("EquipmentIdentifier")
attr("Gsm")
attr("Umts")
attr("Lte")
attr("SignalQuality")
attr("RegistrationState")
return loc
def start_call(self, num):
mm = self.bus.get("org.freedesktop.ModemManager1")
for modem_path in mm.GetManagedObjects():
modem = self.bus.get("org.freedesktop.ModemManager1", modem_path)
voice = modem["org.freedesktop.ModemManager1.Modem.Voice"]
call_properties = {
"number": Variant('s', num)
}
call_path = voice.CreateCall(call_properties)
#call = self.bus.get("org.freedesktop.ModemManager1", call_path)
#call_iface = call["org.freedesktop.ModemManager1.Call"]
#call_iface.Start()
return { "call": call_path }
def send_sms(self, num, text):
mm = self.bus.get("org.freedesktop.ModemManager1")
for modem_path in mm.GetManagedObjects():
modem = self.bus.get("org.freedesktop.ModemManager1", modem_path)
messaging = modem["org.freedesktop.ModemManager1.Modem.Messaging"]
sms_properties = {
"number": Variant('s', num),
"text": Variant('s', text)
}
sms_path = messaging.Create(sms_properties)
sms = self.bus.get("org.freedesktop.ModemManager1", sms_path)
sms_iface = sms["org.freedesktop.ModemManager1.Sms"]
sms_iface.Send()
return { "sms": sms_path }
# 0x01 = 3GPP LAC/CI
# 0x02 = GPS NMEA
# 0x04 = GPS RAW
# 0x08 = CDMA BS
# 0x10 = GPS Unmanaged
CELL_ID = 0x01
GPS_NMEA = 0x02
GPS_RAW = 0x04
def enable_mobile_loc(self, gps_on, cell_on):
"""
Enable GPS RAW + NMEA.
"""
mm = self.bus.get("org.freedesktop.ModemManager1")
for modem_path in mm.GetManagedObjects():
modem = self.bus.get(".ModemManager1", modem_path)
# Setup(uint32 sources, boolean signal_location)
# signal_location=True makes ModemManager emit LocationUpdated signals
if gps_on:
sources = self.GPS_NMEA | self.GPS_RAW
else:
sources = 0
if cell_on:
sources |= self.CELL_ID;
modem.Setup(sources, True)
continue
# Optional: explicitly enable (some modems require it)
try:
modem.SetEnable(True)
except Exception:
print("Cant setenable")
return { 'result' : 'setenable failed' }
return { 'result': 'ok' }
phone = Phone()
def handle_cmd(v, a):
if v == "bat":
print(json.dumps(phone.get_battery_info()))
sys.exit(0)
if v == "loc":
print(json.dumps(phone.get_mobile_loc()))
sys.exit(0)
if v == "loc_on":
print(json.dumps(phone.enable_mobile_loc(True, True)))
sys.exit(0)
if v == "loc_off":
print(json.dumps(phone.enable_mobile_loc(False, False)))
sys.exit(0)
if v == "signal":
print(json.dumps(phone.get_cell_signal()))
sys.exit(0)
if v == "call":
print(json.dumps(phone.start_call(a[2])))
sys.exit(0)
if v == "sms":
print(json.dumps(phone.send_sms(a[2], a[3])))
sys.exit(0)
print("Unknown command "+v)
sys.exit(1)
if len(sys.argv) > 1:
handle_cmd(sys.argv[1], sys.argv)
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB