diff --git a/internal_filesystem/apps/cz.ucw.pavel.cellular/META-INF/MANIFEST.JSON b/internal_filesystem/apps/cz.ucw.pavel.cellular/META-INF/MANIFEST.JSON new file mode 100644 index 00000000..5456fb6d --- /dev/null +++ b/internal_filesystem/apps/cz.ucw.pavel.cellular/META-INF/MANIFEST.JSON @@ -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" + } + ] + } + ] +} + diff --git a/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/main.py b/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/main.py new file mode 100644 index 00000000..a6877e50 --- /dev/null +++ b/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/main.py @@ -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)) + + + # -------------------- + + diff --git a/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/phone.py b/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/phone.py new file mode 100755 index 00000000..948c19ff --- /dev/null +++ b/internal_filesystem/apps/cz.ucw.pavel.cellular/assets/phone.py @@ -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) + diff --git a/internal_filesystem/apps/cz.ucw.pavel.cellular/res/mipmap-mdpi/icon_64x64.png b/internal_filesystem/apps/cz.ucw.pavel.cellular/res/mipmap-mdpi/icon_64x64.png new file mode 100644 index 00000000..662b3c89 Binary files /dev/null and b/internal_filesystem/apps/cz.ucw.pavel.cellular/res/mipmap-mdpi/icon_64x64.png differ