0

我是 Fabulous 和 MUV 模型的新手,我正在尝试实现适用于 BLE 的应用程序。我对 F# 也有点陌生,过去主要使用 erlang 和 C#,所以对外部事件处理有点迷茫。CrossBluetoothLE.Current.Adapter 具有 DeviceDiscovered 事件处理程序 (IEvent)。将此事件处理程序链接到 Fabulous 更新功能的最正确方法是什么?

例如,在我调用 CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync() 之后,我希望这个事件处理程序将新发现的设备提供给更新函数。

如果我会做这样的事情(这不起作用):

type MyApp () as app = 
    inherit Application ()

    let deviceDiscovered dispatch = 
        CrossBluetoothLE.Current.Adapter.DeviceDiscovered.Subscribe (fun x -> dispatch (App.Msg.Discovered x.Device) )

    let runner =
        App.program
        |> Program.withConsoleTrace
        |> Program.withSubscription (fun _ -> Cmd.ofSub deviceDiscovered)
        |> XamarinFormsProgram.run app

如果它有效,则可以进行设备发现,因为 CrossBluetoothLE.Current.Adapter 是静态的。然而,在设备被发现后,我将需要使用(例如接收通知或来自它的回复),并且不可能将动态设备处理程序包含到 Program.withSubscription 中。

不确定 Fabulous 是否适用于此。

4

1 回答 1

0

好的,我找到了一些解决方案,它现在可以工作了,但是整体架构看起来有点奇怪。所以通用的方法是创建一个外部邮箱,它将消息发送到 MUV 循环。

  1. 在外部模块中描述 MUV 的所有消息,例如:
    type Msg = 
        | Scan
        | Discovered of IDevice
        | Connect of IDevice
        | ClockMsg of System.DateTime
        | TextMsg of string
  1. 创建封装邮箱的类型:
    type DispatchFunc = Msgs.Msg -> unit

    type State = 
        | Initialized of DispatchFunc
        | NotInitialized

    type Mail = 
        | Dispatch of DispatchFunc
        | Msg of Msgs.Msg
        | None

    let rand = System.Random()
    let id = rand.NextDouble()

    let postbox = MailboxProcessor.Start(fun inbox -> 
        let rec messageLoop (state:State) = async{
            let! mail = inbox.Receive()

            let new_state = 
                match mail with 
                | None ->
                    state
                | Msg msg -> 
                    match state with 
                    | NotInitialized -> NotInitialized
                    | Initialized df ->
                        df msg
                        state
                | Dispatch df -> 
                    Initialized df

            return! messageLoop (new_state)
            }

        messageLoop (NotInitialized))

    let post(o) =
        postbox.Post o

在这里,邮箱以 NotInitialized 状态启动并等待应用程序启动。当一切都完成后,邮箱接收调度功能,将用于将外部消息进一步调度到 MUV 主循环。

  1. 将调度处理程序传递给邮箱:
type MyApp () as app = 
    inherit Application ()

    // generate initial events + start threads + pass dispatch reference to the mailbox
    let initThreads dispatch =
        // init & start external (e.g. bluetooth receiver) threads here
        // or start them asynchronously from MUV loop
        Postbox.post (Postbox.Dispatch dispatch)
        ()

    let runner = 
        App.program
        |> Program.withConsoleTrace
        |> Program.withSubscription (fun _ -> Cmd.ofSub initThreads)  
        |> XamarinFormsProgram.run app

所以现在,如果你想从外部线程向 MUV 发送事件,只需在内部启动它initThreads(或者,例如从 MUV 循环内部)并使用类似的东西:Postbox.post (Postbox.Msg (Msgs.TextMsg "It works!")).

例如,出于我的目的(BLE 发现),它将如下所示:

    let update msg model =
        match msg with
        | Msgs.Scan -> 
            CrossBluetoothLE.Current.Adapter.StopScanningForDevicesAsync() |> Async.AwaitTask |> ignore
            CrossBluetoothLE.Current.Adapter.DeviceDiscovered.Subscribe (
                    fun (a) ->
                        Postbox.post (Postbox.Msg (Msgs.Discovered a.Device))
                        ()
                ) |> ignore
            CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync() |> Async.AwaitTask |> ignore
            model, Cmd.none
        | Msgs.ClockMsg msg ->
            { model with label = msg.ToString() }, Cmd.none
        | Msgs.TextMsg msg ->
            { model with label = msg }, Cmd.none
        | Msgs.Discovered d ->
            { model with gattDevices = d::model.gattDevices; label = "Discovered " + d.ToString() }, Cmd.none
        | Msgs.Connect d -> { model with connectedDevice = d }, Cmd.none

这肯定是一个非常丑陋的解决方案,但我无法想象更美丽的东西:(。

于 2019-12-15T12:24:54.130 回答