diff --git a/src/api/location.toit b/src/api/location.toit new file mode 100644 index 0000000..708594d --- /dev/null +++ b/src/api/location.toit @@ -0,0 +1,35 @@ +import system.services +import ..location show Location GnssLocation + +interface LocationService: + static SELECTOR ::= services.ServiceSelector + --uuid="b833ce53-3c2c-400c-be82-6538d2409f2d" + --major=0 + --minor=1 + + start config/Map?=null + static START-INDEX ::= 2000 + + read-location -> GnssLocation? + static READ-LOCATION-INDEX ::= 2001 + + stop + static STOP-INDEX ::= 2002 + + +class LocationServiceClient extends services.ServiceClient implements LocationService: + static SELECTOR ::= LocationService.SELECTOR + constructor selector/services.ServiceSelector=SELECTOR: + assert: selector.matches SELECTOR + super selector + + start config/Map?=null: + invoke_ LocationService.START-INDEX config + + read-location -> GnssLocation?: + result := invoke_ LocationService.READ-LOCATION-INDEX null + if not result: return null + return GnssLocation.deserialize result + + stop: + invoke_ LocationService.STOP-INDEX null diff --git a/src/base/cellular.toit b/src/base/cellular.toit index b9feb56..c8ea97d 100644 --- a/src/base/cellular.toit +++ b/src/base/cellular.toit @@ -6,7 +6,7 @@ import log import net import .at as at -import .location show GnssLocation +import ..location show GnssLocation import ..state show SignalQuality RAT_LTE_M ::= 1 diff --git a/src/base/service.toit b/src/base/service.toit index 0c40562..d73d4df 100644 --- a/src/base/service.toit +++ b/src/base/service.toit @@ -5,6 +5,7 @@ import gpio import uart import log +import monitor import net import net.cellular @@ -21,6 +22,8 @@ import system.storage import .cellular import ..api.state +import ..api.location +import ..config CELLULAR_FAILS_BETWEEN_RESETS /int ::= 8 @@ -29,20 +32,6 @@ CELLULAR_RESET_SOFT /int ::= 1 CELLULAR_RESET_POWER_OFF /int ::= 2 CELLULAR_RESET_LABELS ::= ["none", "soft", "power-off"] -pin config/Map key/string -> gpio.Pin?: - value := config.get key - if not value: return null - if value is int: return gpio.Pin value - if value is not List or value.size != 2: - throw "illegal pin configuration: $key == $value" - - pin := gpio.Pin value[0] - mode := value[1] - if mode != cellular.CONFIG_ACTIVE_HIGH: pin = gpio.InvertedPin pin - pin.configure --output --open_drain=(mode == cellular.CONFIG_OPEN_DRAIN) - pin.set 0 // Drive to in-active. - return pin - abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: // We cellular service has been developed against known // versions of the network and cellular APIs. We keep a @@ -60,24 +49,17 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: // TODO(kasper): Let this be configurable. static SUSTAIN_FOR_DURATION_ ::= Duration --ms=100 - // TODO(kasper): Handle the configuration better. - config_/Map? := null - - rx_/gpio.Pin? := null - tx_/gpio.Pin? := null - cts_/gpio.Pin? := null - rts_/gpio.Pin? := null - - power_pin_/gpio.Pin? := null - reset_pin_/gpio.Pin? := null + config/CellularConfiguration driver_/Cellular? := null + driver_clients_/int := 0 + driver_mutex_/monitor.Mutex := monitor.Mutex static ATTEMPTS_KEY ::= "attempts" bucket_/storage.Bucket ::= storage.Bucket.open --flash "toitware.com/cellular" attempts_/int := ? - constructor name/string --major/int --minor/int --patch/int=0: + constructor name/string --major/int --minor/int --patch/int=0 --.config: attempts/int? := null catch: attempts = bucket_.get ATTEMPTS_KEY attempts_ = attempts or 0 @@ -106,86 +88,91 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: abstract create_driver -> Cellular --logger/log.Logger --port/uart.Port - --rx/gpio.Pin? - --tx/gpio.Pin? - --rts/gpio.Pin? - --cts/gpio.Pin? - --power/gpio.Pin? - --reset/gpio.Pin? - --baud_rates/List? + --config/CellularConfiguration + + open_driver --logger: + driver_mutex_.do: + if driver_: + driver_clients_++ + driver_.logger.info "Increasing driver count to $driver_clients_" + return + + // Before the driver is initialized, we change the state of the + // cellular container to indicate that it is now running in + // the foreground and needs to have its driver release + // in order for the shutdown to be clean. + containers.notify-background-state-changed false + is-created := false + try: + driver/Cellular? := null + catch: driver = open_driver_ logger + // If we failed to create the driver, it may very well be + // because we need to reset the modem. We give it one more + // chance, so unless we're already past any deadline set up + // by the caller of open, we'll get another shot at making + // the modem communicate with us. + if not driver: driver = open_driver_ logger + driver_ = driver + is-created = true + finally: + if not is-created: + // We failed to create the driver, so mark the container free + // to be stopped + containers.notify-background-state-changed true + else: + driver_clients_ = 1 + driver_.logger.info "Increasing driver count to $driver_clients_" + + close_driver --error/any=null: + driver_mutex_.do: + driver_clients_-- + driver_.logger.info "Decreasing driver count to to $driver_clients_" + if driver_clients_ == 0: + driver := driver_ + driver_ = null + critical_do: + try: + close_driver_ driver --error=error + finally: + // After closing the driver, we change the state of the cellular + // container to indicate that it is now running in the background. + containers.notify-background-state-changed true connect client/int config/Map? -> List: - if not config: - config = {:} - // TODO(kasper): It feels like the configurations present as assets - // should form the basis (pins, etc.) and then additional options - // provided by the client can give the rest as an overlay. - assets.decode.get "cellular" --if_present=: | encoded | - catch --trace: config = tison.decode encoded - // TODO(kasper): Should we mix in configuration properties from - // firmware.config? - // TODO(kasper): This isn't a super elegant way of dealing with - // the current configuration. Should we pass it through to $open_network - // somehow instead? - config_ = config + this.config.update-from-map_ config return connect client proxy_mask -> int: return NetworkService.PROXY_RESOLVE | NetworkService.PROXY_UDP | NetworkService.PROXY_TCP open_network -> net.Interface: - level := config_.get cellular.CONFIG_LOG_LEVEL --if_absent=: log.INFO_LEVEL - logger := log.Logger level log.DefaultTarget --name="cellular" - - driver/Cellular? := null - catch: driver = open_driver logger - // If we failed to create the driver, it may very well be - // because we need to reset the modem. We give it one more - // chance, so unless we're already past any deadline set up - // by the caller of open, we'll get another shot at making - // the modem communicate with us. - if not driver: driver = open_driver logger - - apn := config_.get cellular.CONFIG_APN --if_absent=: "" - bands := config_.get cellular.CONFIG_BANDS - rats := config_.get cellular.CONFIG_RATS + logger := log.Logger config.log-level log.DefaultTarget --name="cellular" + logger.info "opening network" + open-driver --logger=logger try: with_timeout --ms=30_000: - logger.info "configuring modem" --tags={"apn": apn} - driver.configure apn --bands=bands --rats=rats + logger.info "configuring modem" --tags={"apn": config.apn} + driver_.configure config.apn --bands=config.bands --rats=config.rats with_timeout --ms=120_000: logger.info "enabling radio" - driver.enable_radio + driver_.enable_radio logger.info "connecting" - driver.connect + driver_.connect update-attempts_ 0 // Success. Reset the attempts. logger.info "connected" - // Once the network is established, we change the state of the - // cellular container to indicate that it is now running in - // the foreground and needs to have its proxied networks closed - // correctly in order for the shutdown to be clean. - containers.notify-background-state-changed false - return driver.network_interface + + return driver_.network_interface finally: | is_exception exception | if is_exception: - critical_do: close_driver driver --error=exception.value - else: - driver_ = driver + close_driver close_network network/net.Interface -> none: - driver := driver_ - driver_ = null - logger := driver.logger - critical_do: - try: - close_driver driver - finally: - // After closing the network, we change the state of the cellular - // container to indicate that it is now running in the background. - containers.notify-background-state-changed true + logger := log.Logger config.log-level log.DefaultTarget --name="cellular" + logger.info "closing network" + close_driver - open_driver logger/log.Logger -> Cellular: + open_driver_ logger/log.Logger -> Cellular: attempts := update_attempts_ attempts_ + 1 attempts_since_reset ::= attempts % CELLULAR_FAILS_BETWEEN_RESETS attempts_until_reset ::= attempts_since_reset > 0 @@ -203,38 +190,18 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: "reset": CELLULAR_RESET_LABELS[reset], } - uart_baud_rates/List? := config_.get cellular.CONFIG_UART_BAUD_RATE - --if_present=: it is List ? it : [it] - uart_high_priority/bool := config_.get cellular.CONFIG_UART_PRIORITY - --if_present=: it == cellular.CONFIG_PRIORITY_HIGH - --if_absent=: false - - tx_ = pin config_ cellular.CONFIG_UART_TX - rx_ = pin config_ cellular.CONFIG_UART_RX - cts_ = pin config_ cellular.CONFIG_UART_CTS - rts_ = pin config_ cellular.CONFIG_UART_RTS - - power_pin_ = pin config_ cellular.CONFIG_POWER - reset_pin_ = pin config_ cellular.CONFIG_RESET - port := uart.Port --baud_rate=Cellular.DEFAULT_BAUD_RATE - --high_priority=uart_high_priority - --tx=tx_ - --rx=rx_ - --cts=cts_ - --rts=rts_ + --high_priority=config.uart-high-priority + --tx=config.uart-tx + --rx=config.uart-rx + --cts=config.uart-cts + --rts=config.uart-rts driver := create_driver --logger=logger --port=port - --rx=rx_ - --tx=tx_ - --rts=rts_ - --cts=cts_ - --power=power_pin_ - --reset=reset_pin_ - --baud_rates=uart_baud_rates + --config=config try: if reset == CELLULAR_RESET_SOFT: @@ -257,20 +224,20 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: port.close close_pins_ - close_driver driver/Cellular --error/any=null -> none: + close_driver_ driver/Cellular --error/any=null -> none: logger := driver.logger log_level := error ? log.WARN_LEVEL : log.INFO_LEVEL log_tags := error ? { "error": error } : null try: log.log log_level "closing" --tags=log_tags catch: with_timeout --ms=20_000: driver.close - if rts_: - rts_.configure --output - rts_.set 0 + if config.uart-rts: + config.uart-rts.configure --output + config.uart-rts.set 0 // It appears as if we have to wait for RX to settle down, before // we start to look at the power state. - catch: with_timeout --ms=10_000: wait_for_quiescent_ rx_ + catch: with_timeout --ms=10_000: wait_for_quiescent_ config.uart-rx // The call to driver.close sends AT+CPWROFF. If the session wasn't // active, this can fail and therefore we probe its power state and @@ -291,13 +258,7 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: log.log log_level "closed" --tags=log_tags close_pins_ -> none: - if tx_: tx_.close - if rx_: rx_.close - if cts_: cts_.close - if rts_: rts_.close - if power_pin_: power_pin_.close - if reset_pin_: reset_pin_.close - tx_ = rx_ = cts_ = rts_ = power_pin_ = reset_pin_ = null + config.close-owned-pins_ // Block until a value has been sustained for at least $SUSTAIN_FOR_DURATION_. static wait_for_quiescent_ pin/gpio.Pin: @@ -317,6 +278,7 @@ abstract class CellularServiceProvider extends ProxyingNetworkServiceProvider: // deadlines into consideration. sleep --ms=10 + class CellularStateServiceHandler_ implements ServiceHandler CellularStateService: provider/CellularServiceProvider constructor .provider: @@ -348,3 +310,60 @@ class CellularStateServiceHandler_ implements ServiceHandler CellularStateServic driver := provider.driver_ if not driver: return null return driver.version + + +abstract class LocationServiceProvider extends CellularServiceProvider: + gnss-started/bool := false + + constructor name/string --major/int --minor/int --patch/int=0 --config/CellularConfiguration: + super "location/$name" --major=major --minor=minor --patch=patch --config=config + provides LocationService.SELECTOR --handler=this + + handle index/int arguments/any --gid/int --client/int -> any: + if index == LocationService.START-INDEX: + return start-location arguments + else if index == LocationService.READ-LOCATION-INDEX: + return read-location + else if index == LocationService.STOP-INDEX: + return stop-location + + return super index arguments --gid=gid --client=client + + start-location config-map/Map?: + if gnss-started: + throw "INVALID_STATE" + config.update-from-map_ config-map + + logger := log.Logger config.log-level log.DefaultTarget --name="cellular" + + open-driver --logger=logger + + gnss := driver_ as Gnss + + try: + logger.info "connecting to location service on modem" + gnss.gnss_start + finally: | is_exception e | + if is_exception: + close_driver + else: + gnss-started = true + + read-location: + if not gnss-started: + throw "INVALID_STATE" + location := (driver_ as Gnss).gnss_location + if not location: return null + return location.to_byte_array + + stop-location: + if not gnss-started: + throw "INVALID_STATE" + driver_.logger.info "disconnecting from the location service on the modem" + gnss := driver_ as Gnss + try: + gnss.gnss_stop + finally: + gnss-started = false + close_driver + diff --git a/src/config.toit b/src/config.toit new file mode 100644 index 0000000..cfe1317 --- /dev/null +++ b/src/config.toit @@ -0,0 +1,115 @@ +import gpio show Pin InvertedPin +import net.cellular +import log.level + +class CellularConfiguration: + uart-rx/Pin? := ? + uart-tx/Pin? := ? + uart-cts/Pin? := ? + uart-rts/Pin? := ? + uart-baud-rates/List? := ? + uart-high-priority/bool := ? + power/Pin? := ? + reset/Pin? := ? + apn/string := ? + rats/List? := ? + bands/List? := ? + log-level/int? := ? + + owned_/List := [] + + /** + Notice that $power and $reset should be active high. If your cellular module defines these as active low, then + use $InvertedPin. + */ + constructor + --.uart-rx=null + --.uart-tx=null + --.uart-cts=null + --.uart-rts=null + --.power=null + --.reset=null + --.uart-baud-rates=null + --.uart-high-priority=false + --.apn="" + --.rats=null + --.bands=null + --.log-level=level.INFO-LEVEL: + + static RX-OWNED_ ::= 0 + static TX-OWNED_ ::= 1 + static RTS-OWNED_ ::= 2 + static CTS-OWNED_ ::= 3 + static POWER-OWNED_ ::= 4 + static RESET-OWNED_ ::= 5 + + update-from-map_ config/Map?: + if not config: return + + uart-rx = update-pin-from-map_ config cellular.CONFIG-UART-RX RX-OWNED_ uart-rx + uart-tx = update-pin-from-map_ config cellular.CONFIG-UART-TX TX-OWNED_ uart-tx + uart-cts = update-pin-from-map_ config cellular.CONFIG-UART-CTS CTS-OWNED_ uart-cts + uart-rts = update-pin-from-map_ config cellular.CONFIG-UART-RTS RTS-OWNED_ uart-rts + + power = update-pin-from-map_ config cellular.CONFIG-POWER POWER-OWNED_ power + reset = update-pin-from-map_ config cellular.CONFIG-RESET RESET-OWNED_ reset + + baud-rates/List? := config.get cellular.CONFIG-UART-BAUD-RATE + --if-present=: it is List ? it : [it] + if baud-rates: uart-baud-rates = baud-rates + + high-priority/bool := config.get cellular.CONFIG-UART-PRIORITY + --if-present=: it == cellular.CONFIG-PRIORITY-HIGH + --if-absent=: false + if high-priority: + uart-high-priority = high-priority + + if config-apn := config.get cellular.CONFIG_APN: apn = config-apn + if config-bands := config.get cellular.CONFIG_BANDS: bands = config-bands + if config-rats := config.get cellular.CONFIG_RATS: rats = config-rats + + update-pin_from-map_ config/Map key/string owned-indicator existing/Pin?: + config-pin := pin_ config key + if not config-pin: return existing + owned_.add owned-indicator + return config-pin + + static pin_ config/Map key/string -> Pin?: + value := config.get key + if not value: return null + if value is int: return Pin value + if value is not List or value.size != 2: + throw "illegal pin configuration: $key == $value" + + pin := Pin value[0] + mode := value[1] + if mode != cellular.CONFIG_ACTIVE_HIGH: pin = InvertedPin pin + pin.configure --output --open_drain=(mode == cellular.CONFIG_OPEN_DRAIN) + pin.set 0 // Drive to in-active. + return pin + + close-owned-pins_: + owned_.do: + if it == RX-OWNED_: + uart-rx.close + uart-rx = null + + if it == TX-OWNED_: + uart-tx.close + uart-tx = null + + if it == CTS-OWNED_: + uart-cts.close + uart-cts = null + + if it == RTS-OWNED_: + uart-rts.close + uart-rts = null + + if it == POWER-OWNED_: + power.close + power = null + + if it == RESET-OWNED_: + reset.close + reset = null diff --git a/src/base/location.toit b/src/location.toit similarity index 100% rename from src/base/location.toit rename to src/location.toit diff --git a/src/modules/quectel/bg96.toit b/src/modules/quectel/bg96.toit index b9aa604..3a8f5f1 100644 --- a/src/modules/quectel/bg96.toit +++ b/src/modules/quectel/bg96.toit @@ -11,32 +11,27 @@ import .quectel import ...base.at as at import ...base.base as cellular import ...base.cellular as cellular -import ...base.service show CellularServiceProvider +import ...base.service show LocationServiceProvider +import ...config -main: - service := BG96Service +main --config/CellularConfiguration=CellularConfiguration: + service := BG96Service --config=config service.install // -------------------------------------------------------------------------- -class BG96Service extends CellularServiceProvider: - constructor: - super "quectel/bg96" --major=0 --minor=1 --patch=0 +class BG96Service extends LocationServiceProvider: + constructor --config/CellularConfiguration: + super "quectel/bg96" --major=0 --minor=1 --patch=0 --config=config create_driver -> cellular.Cellular --logger/log.Logger --port/uart.Port - --rx/gpio.Pin? - --tx/gpio.Pin? - --rts/gpio.Pin? - --cts/gpio.Pin? - --power/gpio.Pin? - --reset/gpio.Pin? - --baud_rates/List?: + --config/CellularConfiguration: return BG96 port logger - --pwrkey=power - --rstkey=reset - --baud_rates=baud_rates + --pwrkey=config.power + --rstkey=config.reset + --baud_rates=config.uart-baud-rates --is_always_online=true /** @@ -57,6 +52,7 @@ class BG96 extends QuectelCellular: on_connected_ session/at.Session: // Attach to network. + session.send (QNWINFO) session.set "+QICSGP" [cid_] session.send (QIACT cid_) diff --git a/src/modules/quectel/quectel.toit b/src/modules/quectel/quectel.toit index 1711563..848a801 100644 --- a/src/modules/quectel/quectel.toit +++ b/src/modules/quectel/quectel.toit @@ -15,15 +15,16 @@ import ...base.at as at import ...base.base import ...base.cellular import ...base.exceptions -import ...base.location show Location GnssLocation +import ...location show Location GnssLocation CONNECTED_STATE_ ::= 1 << 0 READ_STATE_ ::= 1 << 1 CLOSE_STATE_ ::= 1 << 2 -TIMEOUT_QIOPEN ::= Duration --s=150 -TIMEOUT_QIRD ::= Duration --s=5 -TIMEOUT_QISEND ::= Duration --s=5 +TIMEOUT_QIOPEN ::= Duration --s=150 +TIMEOUT_QIRD ::= Duration --s=15 +TIMEOUT_QISEND ::= Duration --s=15 +TIMEOUT_CLOSE_WAIT ::= Duration --s=30 monitor SocketState_: state_/int := 0 @@ -45,12 +46,16 @@ monitor SocketState_: if not dirty_: state_ &= ~state -class Socket_: + + +abstract class Socket_: static ERROR_OK_ ::= 0 static ERROR_MEMORY_ALLOCATION_FAILED_ ::= 553 static ERROR_OPERATION_BUSY_ ::= 568 static ERROR_OPERATION_NOT_ALLOWED_ ::= 572 + static SOCKET_CLOSED_ ::= "SOCKET_CLOSED" + state_ ::= SocketState_ should_pdp_deact_ := false cellular_/QuectelCellular ::= ? @@ -63,11 +68,26 @@ class Socket_: pdp_deact_: should_pdp_deact_ = true + /** + Closed from remote + */ + close-wait: + closed_ + id := id_ + id_ = null // Drop the instance reference here to allow for socket closed exceptions. + task --background :: + sleep TIMEOUT_CLOSE_WAIT + socket_call: + it.set "+QICLOSE" [id, 0] + cellular_.sockets_.remove id + closed_: state_.set_state CLOSE_STATE_ + abstract close + get_id_: - if not id_: throw "socket is closed" + if not id_: throw SOCKET-CLOSED_ return id_ /** @@ -86,8 +106,10 @@ class Socket_: Returns the latest socket error (even if OK). */ last_error_ cellular/at.Session original_error/string="" -> Exception: + if original_error == SOCKET-CLOSED_: + catch --trace: throw original-error + throw (UnavailableException original_error) res := cellular.action "+QIGETERROR" - print_ "Error $original_error -> $res.last" error := res.last[0] error_message := res.last[1] if error == ERROR_OK_: @@ -118,6 +140,7 @@ class TcpSocket extends Socket_ implements tcp.Socket: constructor cellular id .peer_address: super cellular id + initiate_connection_: socket_call: it.set "+QIOPEN" --timeout=TIMEOUT_QIOPEN [ cellular_.cid_, @@ -143,9 +166,12 @@ class TcpSocket extends Socket_ implements tcp.Socket: if state & CLOSE_STATE_ != 0: return null else if state & READ_STATE_ != 0: - r := socket_call: it.set "+QIRD" --timeout=TIMEOUT_QIRD [get_id_, 1500] + r/at.Result := socket_call: | session/at.Session | + session.set "+QIRD" --timeout=TIMEOUT_QIRD [get_id_, 1500] out := r.single - if out[0] > 0: return out[1] + if out[0] > 0: + //cellular_.logger.debug "<- <$(out[1].size) bytes>" + return out[1] state_.clear READ_STATE_ else: throw "SOCKET ERROR" @@ -157,12 +183,12 @@ class TcpSocket extends Socket_ implements tcp.Socket: data = data[from..to] e := catch --unwind=(: it is not UnavailableException): + // Give processing time to other tasks, to avoid busy write-loop that starves readings. + yield socket_call: it.set "+QISEND" [get_id_, data.size] --timeout=TIMEOUT_QISEND --data=data - // Give processing time to other tasks, to avoid busy write-loop that starves readings. - yield return data.size // Buffer full, wait for buffer to be drained. @@ -191,14 +217,16 @@ class TcpSocket extends Socket_ implements tcp.Socket: cellular_.sockets_.remove id mtu -> int: - return 1500 + // From spec, +QISEND only allows sending 1460 bytes at a time. + return 1460 class UdpSocket extends Socket_ implements udp.Socket: remote_address_ := null - constructor cellular/QuectelCellular id/int port/int: + constructor cellular/QuectelCellular id/int: super cellular id + initiate-connection_ port/int: socket_call: it.set "+QIOPEN" --timeout=TIMEOUT_QIOPEN [ cellular_.cid_, @@ -302,17 +330,21 @@ abstract class QuectelCellular extends CellularBase implements Gnss: at_session.register_urc "+QIOPEN":: | args | sockets_.get args[0] - --if_present=: | socket | + --if_present=: | socket/Socket_ | if args[1] == 0: // Success. if socket.error_ == 0: socket.state_.set_state CONNECTED_STATE_ else: // The connection was aborted. - socket.close + logger.warn "Socket has errors ($socket.error_), closing" + socket.close-wait else: + // Failure + logger.warn "Open failed with error $args[1]" socket.error_ = args[1] - socket.closed_ + socket.close-wait + --if-absent=: logger.warn "Socket $args[0] not found" at_session.register_urc "+QIURC":: if it[0] == "dnsgip": @@ -325,10 +357,11 @@ abstract class QuectelCellular extends CellularBase implements Gnss: --if_present=: it.state_.set_state READ_STATE_ else if it[0] == "closed": sockets_.get it[1] - --if_present=: it.closed_ + --if_present=: | socket/Socket_ | + socket.close-wait else if it[0] == "pdpdeact": sockets_.get it[1] - --if_present=: + --if_present=: | socket/Socket_ | it.pdp_deact_ it.closed_ @@ -350,6 +383,7 @@ abstract class QuectelCellular extends CellularBase implements Gnss: [0] else: reader.skip 1 // Skip '\n'. + session.logger_.debug "<- +QIRD $parts" parts.add (reader.read_bytes parts[0]) parts @@ -371,7 +405,8 @@ abstract class QuectelCellular extends CellularBase implements Gnss: close: try: - sockets_.values.do: it.closed_ + sockets_.values.do: | socket/Socket_ | + socket.close 2.repeat: | attempt/int | catch: with_timeout --ms=1_500: at_.do: | session/at.Session | if not session.is_closed: @@ -443,6 +478,8 @@ abstract class QuectelCellular extends CellularBase implements Gnss: session.set "+QURCCFG" ["urcport", "uart1"] session.set "+CTZU" [1] + session.set "+IFC" [0, 0] + if bands: mask := 0 bands.do: mask |= 1 << (it - 1) @@ -517,6 +554,7 @@ abstract class QuectelCellular extends CellularBase implements Gnss: sleep --ms=100 gnss_start: + at_.do: gnss_eval_ it gnss_users_++ at_.do: gnss_eval_ it @@ -548,7 +586,7 @@ abstract class QuectelCellular extends CellularBase implements Gnss: if not state: return if gnss_users_ > 0: if state != 1: - session.set "+QGPS" [1] + session.set "+QGPS" [1, 255] else if state != 0: session.action "+QGPSEND" @@ -603,8 +641,12 @@ class Interface_ extends CloseableNetwork implements net.Interface: if not port or port == 0: // Best effort for rolling a free port. port = FREE_PORT_RANGE + free_port_++ % FREE_PORT_RANGE - socket := UdpSocket cellular_ id port + socket := UdpSocket cellular_ id cellular_.sockets_.update id --if_absent=(: socket): throw "socket already exists" + + socket.initiate_connection_ port // Moved the initiation of the socket to after the id is added to the sockets. + // Previously, the modem could respond before the id was added to the sockets, + return socket tcp_connect host/string port/int -> tcp.Socket: @@ -617,6 +659,9 @@ class Interface_ extends CloseableNetwork implements net.Interface: socket := TcpSocket cellular_ id address cellular_.sockets_.update id --if_absent=(: socket): throw "socket already exists" + socket.initiate_connection_ // Moved the initiation of the socket to after the id is added to the sockets. + // Previously, the modem could respond before the id was added to the sockets, + catch --unwind=(: socket.error_ = 1; true): socket.connect_ return socket @@ -675,3 +720,7 @@ class QICFG extends at.Command: constructor.keepalive --enable/bool --idle_time/int=1 --interval_time/int=30 --probe_count=3: ps := enable ? ["tcp/keepalive", 1, idle_time, interval_time, probe_count] : ["tcp/keepalive", 0] super.set "+QICFG" --parameters=ps + +class QNWINFO extends at.Command: + constructor: + super.action "+QNWINFO" \ No newline at end of file diff --git a/src/modules/sequans/monarch.toit b/src/modules/sequans/monarch.toit index ac53e71..afeac35 100644 --- a/src/modules/sequans/monarch.toit +++ b/src/modules/sequans/monarch.toit @@ -12,6 +12,7 @@ import ...base.at as at import ...base.base as cellular import ...base.cellular as cellular import ...base.service show CellularServiceProvider +import ...config /** This is the driver and service for the Sequans Monarch module. The easiest @@ -28,30 +29,24 @@ $ jag run examples/monarch.toit Happy networking! */ -main: - service := MonarchService +main --config/CellularConfiguration=CellularConfiguration: + service := MonarchService --config=config service.install // -------------------------------------------------------------------------- class MonarchService extends CellularServiceProvider: - constructor: - super "sequans/monarch" --major=0 --minor=1 --patch=0 + constructor --config/CellularConfiguration: + super "sequans/monarch" --major=0 --minor=1 --patch=0 --config=config create_driver -> cellular.Cellular --logger/log.Logger --port/uart.Port - --rx/gpio.Pin? - --tx/gpio.Pin? - --rts/gpio.Pin? - --cts/gpio.Pin? - --power/gpio.Pin? - --reset/gpio.Pin? - --baud_rates/List?: + --config/CellularConfiguration: // TODO(kasper): If power or reset are given, we should probably // throw an exception. return Monarch port logger - --uart_baud_rates=baud_rates or [921_600] + --uart_baud_rates=config.uart-baud-rates or [921_600] /** Driver for Sequans Monarch, GSM communicating over NB-IoT & M1. diff --git a/src/modules/ublox/sara_r4.toit b/src/modules/ublox/sara_r4.toit index 5310015..b22b03a 100644 --- a/src/modules/ublox/sara_r4.toit +++ b/src/modules/ublox/sara_r4.toit @@ -12,31 +12,26 @@ import ...base.at as at import ...base.base as cellular import ...base.cellular as cellular import ...base.service show CellularServiceProvider +import ...config -main: +main --config/CellularConfiguration=CellularConfiguration: service := SaraR4Service service.install // -------------------------------------------------------------------------- class SaraR4Service extends CellularServiceProvider: - constructor: - super "ublox/sara_r4" --major=0 --minor=1 --patch=0 + constructor --config/CellularConfiguration: + super "ublox/sara_r4" --major=0 --minor=1 --patch=0 --config=config create_driver -> cellular.Cellular --logger/log.Logger --port/uart.Port - --rx/gpio.Pin? - --tx/gpio.Pin? - --rts/gpio.Pin? - --cts/gpio.Pin? - --power/gpio.Pin? - --reset/gpio.Pin? - --baud_rates/List?: + --config/CellularConfiguration: return SaraR4 port logger - --pwr_on=power - --reset_n=reset - --uart_baud_rates=baud_rates or [460_800, cellular.Cellular.DEFAULT_BAUD_RATE] + --pwr_on=config.power + --reset_n=config.reset + --uart_baud_rates=config.uart-baud-rates or [460_800, cellular.Cellular.DEFAULT_BAUD_RATE] --is_always_online=true /** diff --git a/src/modules/ublox/sara_r5.toit b/src/modules/ublox/sara_r5.toit index 8bdcdc1..19df0f8 100644 --- a/src/modules/ublox/sara_r5.toit +++ b/src/modules/ublox/sara_r5.toit @@ -12,35 +12,30 @@ import ...base.at as at import ...base.base as cellular import ...base.cellular as cellular import ...base.service show CellularServiceProvider +import ...config -main: - service := SaraR5Service +main --config/CellularConfiguration=CellularConfiguration: + service := SaraR5Service --config=config service.install // -------------------------------------------------------------------------- class SaraR5Service extends CellularServiceProvider: - constructor: - super "ublox/sara_r5" --major=0 --minor=1 --patch=0 + constructor --config/CellularConfiguration: + super "ublox/sara_r5" --major=0 --minor=1 --patch=0 --config=config create_driver -> cellular.Cellular --logger/log.Logger --port/uart.Port - --rx/gpio.Pin? - --tx/gpio.Pin? - --rts/gpio.Pin? - --cts/gpio.Pin? - --power/gpio.Pin? - --reset/gpio.Pin? - --baud_rates/List?: + --config/CellularConfiguration: return SaraR5 port logger - --rx=rx - --tx=tx - --rts=rts - --cts=cts - --pwr_on=power - --reset_n=reset - --uart_baud_rates=baud_rates or [921_600, cellular.Cellular.DEFAULT_BAUD_RATE] + --rx=config.uart-rx + --tx=config.uart-tx + --rts=config.uart-rts + --cts=config.uart-cts + --pwr_on=config.power + --reset_n=config.reset + --uart_baud_rates=config.uart-baud-rates or [921_600, cellular.Cellular.DEFAULT_BAUD_RATE] --is_always_online=true /**