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