From 2d3f7bf51033c4c933421cdf15d4dd731950d0bf Mon Sep 17 00:00:00 2001
From: Andreas Strasser <a.strasser@student.tugraz.at>
Date: Sun, 30 Jul 2023 10:43:47 +0200
Subject: [PATCH] restructured driver implementation

---
 src/driver/nbiot.be | 447 +++++++++++++++++++++-----------------------
 1 file changed, 209 insertions(+), 238 deletions(-)

diff --git a/src/driver/nbiot.be b/src/driver/nbiot.be
index a510d3e..e78726b 100644
--- a/src/driver/nbiot.be
+++ b/src/driver/nbiot.be
@@ -1,48 +1,56 @@
 import string
 import gpio
 
+# ------------------------------------------------------- #
+#                         Requests                        #
+# ------------------------------------------------------- #
 class NBIoTRequestType
+  static var DEBUG = 0
   static var MQTT = 1
   static var COAP = 2
   static var NTP = 3
   static var PSM = 4
-  static var TEST = 69
 end
 
 class NBIoTRequest
-  var type
+  var request_type
+  var callback
+
+  def init(request_type, callback)
+    self.request_type = request_type
+    self.callback = callback
+  end
 end
 
-class NBIoTTestRequest : NBIoTRequest
-  var msg
+class NBIoTDebugRequest : NBIoTRequest
   var cmd
+  var rsp
+  var retries
 
-  def init(msg, cmd)
-    self.type = NBIoTRequestType.TEST
-    self.msg = msg
+  def init(cmd, rsp, retries)
+    super(self).init(NBIoTRequestType.DEBUG)
     self.cmd = cmd
+    self.rsp = rsp
+    self.retries = retries
   end
 end
 
 class NBIoTMQTTRequest : NBIoTRequest
-  var type
   var host
   var port
   var username
   var password
   var topic
   var payload
-  var callback
 
   def init(host, port, username, password, topic, payload, callback)
-    self.type = NBIoTRequestType.MQTT
+    super(self).init(NBIoTRequestType.MQTT, callback)
     self.host = host
     self.port = port
     self.username = username
     self.password = password
     self.topic = topic
     self.payload = payload
-    self.callback = callback
   end
 end
 
@@ -53,40 +61,39 @@ class NBIoTCOAPRequest : NBIoTRequest
   var query
   var topic
   var payload
-  var callback
 
   def init(host, port, path, method, query, payload, callback)
-    self.type = NBIoTRequestType.COAP
+    super(self).init(NBIoTRequestType.COAP, callback)
     self.host = host
     self.port = port
     self.path = path
     self.method = method
     self.query = query
     self.payload = payload
-    self.callback = callback
   end
 end
 
 class NBIoTNTPRequest : NBIoTRequest
-  var callback
+  
 
   def init(callback)
-    self.type = NBIoTRequestType.NTP
-    self.callback = callback
+    super(self).init(NBIoTRequestType.NTP, callback)
   end
 end
 
 class NBIoTPSMRequest : NBIoTRequest
   var value
-  var callback
 
   def init(value, callback)
-    self.type = NBIoTRequestType.PSM
+    super(self).init(NBIoTRequestType.PSM, callback)
     self.value = value
-    self.callback = callback
   end
 end
 
+# ------------------------------------------------------- #
+#                       Connections                       #
+# ------------------------------------------------------- #
+
 class NBIoTMQTTConnection
   var host
   var port
@@ -111,121 +118,70 @@ class NBIoTMQTTConnection
   end
 end
 
-class NBIoTDriverState
-  static var IDLE = 0
-  static var RESET = 1
-  static var READY = 2
-  static var NTP_SYNC = 3
-  static var MQTT_OPEN = 4
-  static var MQTT_CONNECT = 5
-  static var MQTT_PUBLISH = 6
-  static var MQTT_CHECK_CONN = 7
-  static var MQTT_CLOSE = 8
-  static var COAP_OPEN = 9
-  static var COAP_SET_OPTIONS = 10
-  static var COAP_SEND = 11
-  static var COAP_RECEIVE = 12
-  static var COAP_CLOSE = 13
-  static var PSM_DISABLE = 14
-  static var PSM_INIT = 15
-  static var PSM_CFG = 16
-  static var PSM_ENABLE = 17
-  static var TEST = 69 
-end
+# ------------------------------------------------------- #
+#                        Procedures                       #
+# ------------------------------------------------------- #
 
-class NBIoTDriver
-  static var MAX_RETRIES = 10
-
-  var ser
-  var state
-  var rsp_awaiting
-  var payload_awaiting
+class NBIoTCommand
+  var cmd
+  var rsp
   var retries
-  var request_queue
-  var request
-  var mqtt_connection
-  var psm_eint
+  var done
 
-  def init(rx, tx, psm_eint)
-    self.state = NBIoTDriverState.PSM_DISABLE
-    self.rsp_awaiting = false
-    self.retries = self.MAX_RETRIES
-    self.request_queue = []
-    self.request = nil
-    self.mqtt_connection = nil
-    self.psm_eint = psm_eint
-
-    self.ser = serial(rx, tx, 115200, serial.SERIAL_8N1)
-  end
-
-  def next_state(state)
-    self.state = state
-    self.retries = self.MAX_RETRIES
-  end
-
-  def queue_request(request)
-    self.request_queue.push(request)
-  end
-
-  def finish_request(payload)
-    if self.request.callback != nil
-      self.request.callback(payload)
-    end
-
-    self.request = nil
-  end
-
-  def set_system_time(rsp)
-    var rsp_args = string.split(rsp, '\"')
-    var timestamp = nil
-
-    for rsp_arg : rsp_args
-      timestamp = tasmota.strptime(rsp_arg, "%y/%m/%d,%H:%M:%S")
-
-      if timestamp != nil
-        break
-      end
-    end
-
-    tasmota.cmd('time ' + str(timestamp['epoch'] + 3600)) # UTC + 1
+  def init(cmd, rsp, retries)
+    self.cmd = cmd
+    self.rsp = rsp
+    self.retries = retries
+    self.done = false
   end
+end
 
-  def build_mqtt_open_cmd()
-    var cmd = 'AT+QMTOPEN=0,\"%s\",%s\r\n'
-
-    return string.format(cmd, self.request.host, str(self.request.port))
+class NBIoTProcedure
+  var ser
+  var request
+  var debug
+  var done
+  var aborted
+  var rsp_awaiting
+  var cmd_in_process
+
+  def init(ser, request)
+    self.ser = ser
+    self.request = request
+    self.debug = false
+    self.done = false
+    self.aborted = false
+    self.rsp_awaiting = false
+    self.cmd_in_process = nil
   end
 
-  def build_mqtt_conn_cmd()
-    var client_id = 'nb-iot' # todo: change
-    var cmd = 'AT+QMTCONN=0,\"%s\",\"%s\",\"%s\"\r\n'
-
-    return string.format(cmd, client_id, self.request.username, self.request.password)
+  def is_done()
+    return self.done
   end
 
-  def build_mqtt_pub_cmd()
-    var cmd = 'AT+QMTPUB=0,0,0,0,\"%s\",\"%s\"\r\n'
-
-    return string.format(cmd, self.request.topic, self.request.payload)
+  def is_aborted()
+    return self.aborted
   end
 
-  def send_cmd(cmd)
-    self.ser.write(bytes().fromstring(cmd))
+  def send_cmd()
+    self.ser.write(bytes().fromstring(self.cmd_in_process.cmd))
     self.rsp_awaiting = true
 
-    tasmota.log(string.format('NBT: Sending command \'%s\'', string.replace(cmd, '\r\n', '')), 2)
+    tasmota.log(string.format('NBT: Sending command \'%s\'', string.replace(self.cmd_in_process.cmd, '\r\n', '')), 2)
   end
 
-  def rsp_contains_msg(msg, return_rsp)
+  def read_rsp_contains_expected_rsp(return_rsp)
     if self.rsp_awaiting
       var rsp = self.ser.read().asstring()
 
-      print(rsp)
+      if self.debug
+        print(rsp)
+      end
 
-      if string.find(rsp, msg) >= 0
+      if string.find(rsp, self.cmd_in_process.rsp) >= 0
         self.rsp_awaiting = false
 
-        tasmota.log(string.format('NBT: Received \'%s\'', msg), 2)
+        tasmota.log(string.format('NBT: Received \'%s\'', self.cmd_in_process.rsp), 2)
         
         if return_rsp
           return rsp
@@ -233,8 +189,8 @@ class NBIoTDriver
 
         return true
       else
-        if self.retries > 0
-          self.retries -= 1
+        if self.cmd_in_process.retries > 0
+          self.cmd_in_process.retries -= 1
         end
       end
     end
@@ -246,158 +202,169 @@ class NBIoTDriver
     end
   end
 
-  def fetch_rsp_if_contains_msg_or_send(msg, cmd)
-    var rsp = self.rsp_contains_msg(msg, true)
+  def fetch_read_rsp_if_contains_expected_rsp_or_send()
+    var rsp = self.read_rsp_contains_expected_rsp(true)
 
     if rsp != nil
       return rsp
     else
-      self.send_cmd(cmd)
+      self.send_cmd()
 
       return nil
     end
   end
 
-  def rsp_contains_msg_or_send(msg, cmd)
-    if self.rsp_contains_msg(msg, false)
+  def read_rsp_contains_expected_rsp_or_send()
+    if self.read_rsp_contains_expected_rsp(false)
       return true
     else
-      self.send_cmd(cmd)
+      self.send_cmd()
 
       return false
     end
   end
 
+  def retries_exceeded()
+    return self.cmd_in_process.retries < 1
+  end
+
+  def execute() end
+end
+
+class NBIoTDebugProcedure : NBIoTProcedure
+  var cmd_debug
+
+  def init(ser, request)
+    super(self).init(ser)
+
+    self.debug = true
+    self.cmd_debug = NBIoTCommand(request.cmd + '\r\n', request.rsp, request.retries)
+  end
+
+  def execute()
+    if self.cmd_in_process == nil
+      self.cmd_in_process = self.cmd_debug
+    end
+
+    self.done = self.read_rsp_contains_expected_rsp_or_send()
+    self.aborted = self.retries_exceeded()
+  end
+end
+
+class NBIoTResetProcedure : NBIoTProcedure
+  var cmd_reset
+
+  def init(ser)
+    super(self).init(ser)
+
+    self.cmd_reset = NBIoTCommand('AT+QRST=1\r\n', 'RDY', 0)
+  end
+
+  def execute()
+    if self.cmd_in_process == nil
+      self.cmd_in_process = self.cmd_reset
+    end
+
+    self.done = self.read_rsp_contains_expected_rsp_or_send()
+  end
+end
+
+# ------------------------------------------------------- #
+#                       Driver State                      #
+# ------------------------------------------------------- #
+
+class NBIoTDriverState
+  static var IDLE = 0
+  static var RESET = 1
+  static var READY = 2
+  static var BUSY = 3
+end
+
+# ------------------------------------------------------- #
+#                          Driver                         #
+# ------------------------------------------------------- #
+
+class NBIoTDriver
+  var ser
+  var psm_eint
+  var state
+  var request_queue
+  var procedure
+
+  def init(rx, tx, psm_eint)
+    self.ser = serial(rx, tx, 115200, serial.SERIAL_8N1)
+    self.psm_eint = psm_eint
+
+    self.state = NBIoTDriverState.RESET
+    self.request_queue = []
+    self.procedure = nil
+  end
+
+  def queue_request(request)
+    self.request_queue.push(request)
+  end
+
+  def init_procedure(procedure)
+    self.procedure = procedure
+  end
+
+  def finish_procedure(done)
+    if done && self.procedure.request != nil && self.procedure.request.callback != nil
+      self.procedure.request.callback()
+    end
+
+    self.procedure = nil
+  end
+
+  def next_state(state)
+    tasmota.log(string.format('NBT: Transitioning from state %i to %i', self.state, state), 2)
+
+    self.state = state
+  end
+
   def every_second()
     if self.state == NBIoTDriverState.IDLE
       return
-    # ---- retries exceeded ---- #
-    elif self.retries < 1 && self.request != nil
-      self.request = nil
-      self.mqtt_connection = nil
-      self.next_state(NBIoTDriverState.RESET)
-
-      tasmota.log('NBT: Maximum number of retries exceeded, skipping request', 2)
-    # ---- disable power saving mode ---- #
-    elif self.state == NBIoTDriverState.PSM_DISABLE
-      gpio.digital_write(self.psm_eint, 1)
-
-      if self.rsp_contains_msg_or_send('OK', 'AT+QSCLK=0\r\n')
-        self.next_state(NBIoTDriverState.RESET)
-      end
-    # ---- reset module ---- #
     elif self.state == NBIoTDriverState.RESET
-      if self.rsp_contains_msg_or_send('RDY', 'AT+QRST=1\r\n')
+      if self.procedure == nil
+        self.init_procedure(NBIoTResetProcedure(self.ser))
+      end
+
+      self.procedure.execute()
+
+      if self.procedure.is_done()
         self.next_state(NBIoTDriverState.READY)
       end
-    # ---- ready for request ---- #
     elif self.state == NBIoTDriverState.READY
       if self.request_queue.size() > 0
-        self.request = self.request_queue[0]
+        var request = self.request_queue[0]
         self.request_queue.remove(0)
 
-      if self.request.type == NBIoTRequestType.NTP
-        self.next_state(NBIoTDriverState.NTP_SYNC)
-      elif self.request.type == NBIoTRequestType.MQTT
-          var mqtt_connection = NBIoTMQTTConnection(self.request.host, 
-                                                    self.request.port, 
-                                                    self.request.username, 
-                                                    self.request.password)
-
-          if self.mqtt_connection != nil && self.mqtt_connection.equals(mqtt_connection)
-            self.next_state(NBIoTDriverState.MQTT_CHECK_CONN)
-          elif self.mqtt_connection != nil
-            self.mqtt_connection = mqtt_connection
-            self.next_state(NBIoTDriverState.MQTT_CLOSE)
-          else
-            self.mqtt_connection = mqtt_connection
-            self.next_state(NBIoTDriverState.MQTT_OPEN)
-          end
-        elif self.request.type == NBIoTRequestType.TEST
-          self.next_state(NBIoTDriverState.TEST)
-        elif self.request.type == NBIoTRequestType.PSM
-          if self.request.value
-            self.next_state(NBIoTDriverState.PSM_INIT)
-          else
-            self.next_state(NBIoTDriverState.PSM_DISABLE)
-          end
-        else
-          self.finish_request()
+        var procedure = nil
+
+        if request.request_type == NBIoTRequestType.DEBUG
+          procedure = NBIoTDebugProcedure(self.ser, request)
+        elif
+          tasmota.log(string.format('NBT: Request with type %i not supported, discarding request', request.request_type), 2)
         end
-      end
-    elif self.state == NBIoTDriverState.TEST
-      if self.rsp_contains_msg_or_send(self.request.msg, self.request.cmd + '\r\n')
-        self.next_state(NBIoTDriverState.READY)
-      end
-    # ---- fetch time from ntp server ---- #
-    elif self.state == NBIoTDriverState.NTP_SYNC
-      var rsp = self.fetch_rsp_if_contains_msg_or_send('+QNTP: 0', 'AT+QNTP=1,\"0.at.pool.ntp.org\"\r\n')
 
-      if rsp != nil
-        self.set_system_time(rsp)
-        self.next_state(NBIoTDriverState.READY)
-      end
-    elif self.state == NBIoTDriverState.TEST
-      if self.rsp_contains_msg_or_send(self.request.msg, self.request.cmd + '\r\n')
-        self.next_state(NBIoTDriverState.READY)
-      end
-    # ---- punlish mqtt message ---- #
-    elif self.state == NBIoTDriverState.MQTT_CHECK_CONN
-      if self.retries == 1
-        self.mqtt_connection = nil
-
-        self.next_state(NBIoTDriverState.MQTT_CLOSE)
-      elif self.rsp_contains_msg_or_send('+QMTCONN: 0,3', 'AT+QMTCONN?\r\n')
-        self.next_state(NBIoTDriverState.MQTT_PUBLISH)
-      end
-    elif self.state == NBIoTDriverState.MQTT_OPEN
-      if self.rsp_contains_msg_or_send('+QMTOPEN: 0,0', self.build_mqtt_open_cmd())
-        self.next_state(NBIoTDriverState.MQTT_CONNECT)
-      end
-    elif self.state == NBIoTDriverState.MQTT_CONNECT
-      if self.rsp_contains_msg_or_send('+QMTCONN: 0,0,0', self.build_mqtt_conn_cmd())
-        self.next_state(NBIoTDriverState.MQTT_PUBLISH)
+        if procedure != nil
+          self.init_procedure(procedure)
+          self.next_state(NBIoTDriverState.BUSY)
+        end
       end
-    elif self.state == NBIoTDriverState.MQTT_PUBLISH
-      if self.rsp_contains_msg_or_send('+QMTPUB: 0,0,0', self.build_mqtt_pub_cmd())
-        self.finish_request()
+    elif self.state == NBIoTDriverState.BUSY
+      if self.procedure.is_aborted() || self.procedure.is_done()
+        if self.procedure.is_aborted()
+          var cmd = string.replace(self.procedure.cmd_in_process.cmd, '\r\n', '')
 
-        self.next_state(NBIoTDriverState.READY)
-      end
-    elif self.state == NBIoTDriverState.MQTT_CLOSE
-      if self.rsp_contains_msg_or_send('+QMTCLOSE: 0,0', 'AT+QMTCLOSE=0\r\n')
-        if self.request != nil
-          self.next_state(NBIoTDriverState.MQTT_OPEN)
-        else
-          self.next_state(NBIoTDriverState.READY)
+          tasmota.log(string.format('NBT: Exceeded retries at command %s, discarding request', cmd, 2)
         end
-      end
-    # ---- send coap request ---- #
-    elif self.state == NBIoTDriverState.COAP_SET_OPTIONS
-      if self.rsp_contains_msg_or_send('OK', 'AT+QCOAPOPTION=1,11,\"test\"\r\n')
-        self.next_state(NBIoTDriverState.COAP_SEND)
-      end
-    elif self.state == NBIoTDriverState.COAP_SEND
-      if self.rsp_contains_msg_or_send('OK', 'AT+QCOAPSEND=1,0,\"37.120.174.40\",5683,0\r\n')
-        self.next_state(NBIoTDriverState.COAP_RECEIVE)
-      end
-    elif self.state == NBIoTDriverState.COAP_RECEIVE
-      var msg = self.ser.read().asstring()
-      print(msg)
-    # ---- enable power saving mode ---- #
-    elif self.state == NBIoTDriverState.PSM_INIT
-      if self.rsp_contains_msg_or_send('OK', 'AT+CEREG=5\r\n')
-        self.next_state(NBIoTDriverState.PSM_CFG)
-      end
-    elif self.state == NBIoTDriverState.PSM_CFG
-      if self.rsp_contains_msg_or_send('OK', 'AT+CPSMS=1,,,\"11100010\",\"00000001\"\r\n')
-        self.next_state(NBIoTDriverState.PSM_ENABLE)
-      end
-    elif self.state == NBIoTDriverState.PSM_ENABLE
-      gpio.digital_write(self.psm_eint, 0)
 
-      if self.rsp_contains_msg_or_send('OK', 'AT+QSCLK=1\r\n')
-        self.next_state(NBIoTDriverState.IDLE)
+        self.finish_procedure(self.procedure.is_done())
+        self.next_state(NBIoTDriverState.READY)
+      else
+        self.procedure.execute()
       end
     else
       tasmota.log('NBT: Invalid driver state, stopping driver', 2)
@@ -406,6 +373,10 @@ class NBIoTDriver
   end
 end
 
+# ------------------------------------------------------- #
+#                       nbiot module                      #
+# ------------------------------------------------------- #
+
 var nbiot = module('nbiot')
 
 nbiot.init = def (m)
@@ -466,8 +437,8 @@ nbiot.init = def (m)
       end
     end
 
-    def test(msg, cmd)
-      var request = NBIoTTestRequest(msg, cmd)
+    def debug_send(cmd, rsp, retries)
+      var request = NBIoTDebugRequest(cmd, rsp, retries)
 
       self._driver.queue_request(request)
     end
-- 
GitLab