1

Building a React F# UI for an Embedded System

 9 months ago
source link: https://medium.com/@viktorschepik/building-a-react-f-ui-for-an-embedded-system-115f9e89522b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Building a React F# UI for an Embedded System

When the UI has to wait for values

TLDR: We build a React F# single page application that is able to handle values that are sent to and received from an embedded system, considering that the communication takes noticeable time.

Quite often, embedded systems need a user interface but do not have their own display. For that, they can offer a Web user interface like a Single Page Application (SPA) and a corresponding REST API that is used by the SPA.

When you build an SPA for an embedded system, you will realise that there are some challenges that you only seldom encounter in a different application domain. One main issue is the reaction time of an embedded system regarding the user interface. That means, for example, when the user wants to switch on or off something controlled by the embedded system, this could take several seconds. And the user interface has to wait for the confirmation that the “switch” activity has been successful. You could think of showing some waiting state or disabling the UI component in order to avoid that the user desperately repeats the action of clicking. Let’s see how we could accomplish this in a decent way with F#, Fable

Let’s assume the following e-mobility scenario as an example:

We have a charging station at home and want to charge our electric vehicle.

1*7pL7brdD0umLIKS7IQBLMA.png

The charging station is connected to a power net and can conduct the electrical power to the vehicle. The vehicle decides on its own if and when to charge the vehicle battery, but it is dependent on the permission of the charging station.

So when we select the “Allow charging” option at the SPA, the corresponding command is transferred to the charging station. Then the charging station can switch the electrical connection and communicate to the vehicle that it is allowed to charge. It could take some time until the electrical connection has been established and the communication with the vehicle has finished. Only then the charging station can inform the SPA that everything has worked out. Plenty of things could go wrong when dealing with embedded systems because they operate in the physical world where mechanical interactions, temperature conditions and so on directly affect the behaviour of the system.

But how do we represent this timing-related behaviour appropriately in an SPA? As you all probably have figured out, we can define a nice solution in F#. For that, let’s concentrate on the user interaction where the user selects one of the two following options:

  • Allow charging
  • Disallow charging

and represent it as an F# discriminated union:

type ChargingPermission = 
| Allow
| Disallow

At the UI we represent it as options of a radio group:

1*7cqIPDrVXLtGqPqF_6REQQ.png

Waiting when receiving values

When the WebBrowser loads the SPA, the charging permission state is unknown until the current value has been requested at the API of the charging station and until a response has been received.

Now it is time to represent the Receive state of the charging permission. A receive value can be in the state of

  • being available and having a domain value
  • being unavailable and the domain value is unknown
  • receive has failed and error information is provided.

Again, an F# discriminated union will solve the problem by representing three states:

type ReceiveValue<'T> =
| Available of 'T
| Unavailable
| ReceiveFailed of ErrorInfo

In order to represent the received value of the charging permission, we get

let (chargingPermission: ReceiveValue<ChargingPermission>) = Unavailable

and this represents the initial state when the SPA is loaded. A UI component built with F#, the Fable compiler, Feliz and TailwindCSS could look like this:

[<ReactComponent>]
static member RadioInputReceiveOnly
(inputValue: ChargingPermission)
(chargingPermission: ReceiveValue<ChargingPermission>)
=
let inputValueStr = $"%A{inputValue}"

Html.div [
prop.classes [ "flex"; "gap-2" ]
prop.children [
Html.input [
prop.type'.radio
prop.name "charging-permission"
prop.value inputValueStr

prop.isChecked (
match chargingPermission with
| Available permission when permission = inputValue -> true
| Available _
| Unavailable
| ReceiveFailed _ -> false
)

prop.disabled (
match chargingPermission with
| Available _ -> false
| Unavailable
| ReceiveFailed _ -> true
)
]
Html.label [ prop.for' inputValueStr; prop.text inputValueStr ]
]
]

[<ReactComponent>]
static member ChargingPermissionComponent() =
let receivedChargingPermission, setReceivedChargingPermission =
React.useState Unavailable

React.useEffectOnce (fun () -> RestApi.getChargingPermission setReceivedChargingPermission)

Html.div [
prop.children [
Html.div [ prop.classes [ "text-xl"; "my-4" ]; prop.text "Charging Permission" ]
ChargingPermissionComponent.RadioInputReceiveOnly Allow receivedChargingPermission
ChargingPermissionComponent.RadioInputReceiveOnly Disallow receivedChargingPermission
]
]

Let me guide you through the main sequences by depicting it in the following state diagrams. On the left side, the receive state of the charging permission is shown in the context of the main UI component. On the right side, you can see the asynchronous communication with the REST API that triggers the state change on the left side.

The UI component RadioInputReceiveOnly handles at this stage of development the receive state only and renders the radio buttons depending on the receive state in the match chargingPermission with sections.

You could enhance those sections with some error handling, too, which is omitted here. At this stage, you probably got the idea of how to handle the different receive states.

What about changing a value? This gets indeed interesting and can be solved decently with F# language constructs.

Waiting when sending values

In the upper code sample, the radio button selection can’t be changed. So let’s enhance the example in order to be able

  1. to change the value,
  2. send the selected value to the REST API,
  3. wait for approval by the REST API and
  4. change the radio buttons’ state accordingly.

Sending values can be represented by several states:

type ErrorInfo =
| Unauthorized
| InternalServerError

type SendValue<'T> =
| Uninitialized
| Initial of 'T
| Sending of 'T
| Sent of 'T
| SendFailed of 'T * ErrorInfo

Mapping the receiving and sending states to the UI component states could look like the state diagrams below.

In the diagram, the corresponding states are horizontally aligned. The grey radio buttons in the middle column shall represent the disabled state. You could choose to represent the waiting states differently, but for simplicity, we use the disabled state of the HTML input element.

You could represent the sending state as a React state:

let sendChargingPermission, setSendChargingPermission = React.useState Uninitialized

When the user selects on of the two options, Allow or Disallow, the SPA sets this state to Sending and sends the value to the REST API. When the SPA obtains the response it sets the state to Sent if everything went well and otherwise to SendFailed when something went wrong.

The UI component has now all information to switch the radio button states. By using match clauses we can express literally what should happen at the UI. For example to define the disabled state of a radio button we match like this:

prop.disabled (
match props.SendChargingPermission with
| Initial _
| Sent _
| SendFailed _ -> false
| Uninitialized
| Sending _ -> true
)

or a little bit more sophisticated the checked state like this:

prop.isChecked (
match props.SendChargingPermission, props.ReceiveChargingPermission with
| (Initial permission | Sent permission), _ when permission = props.RadioInputValue -> true
| (Sending _ | SendFailed _), Available permission when permission = props.RadioInputValue ->
true
| _ -> false
)

This code serves just to give an idea and should better be read in the full code context below.

Summary

The decent aspect of representing remote value states by F# discriminated unions is that you can represent all asynchronous events and states in your source code and handle it in a nice way using the match construct of F#.

The full solution

type ChargingPermission =
| Allow
| Disallow

type ErrorInfo =
| Unauthorized
| InternalServerError

type ReceiveValue<'T> =
| Available of 'T
| Unavailable
| ReceiveFailed of ErrorInfo

type SendValue<'T> =
| Uninitialized
| Initial of 'T
| Sending of 'T
| Sent of 'T
| SendFailed of 'T * ErrorInfo


type ChargingPermissionComponent =

[<ReactComponent>]
static member RadioInput
(
radioInputValue: ChargingPermission,
receiveChargingPermission: ReceiveValue<ChargingPermission>,
sendChargingPermission: SendValue<ChargingPermission>,
onChange: ChargingPermission -> unit
) =
let inputValueStr = $"%A{radioInputValue}"

Html.div [
prop.classes [ "flex"; "gap-2" ]
prop.children [
Html.input [
prop.type'.radio
prop.name "charging-permission"
prop.value inputValueStr

prop.isChecked (
match sendChargingPermission, receiveChargingPermission with
| (Initial permission | Sent permission), _ when permission = radioInputValue -> true
| (Sending _ | SendFailed _), Available permission when permission = radioInputValue -> true
| _ -> false
)

prop.disabled (
match sendChargingPermission with
| Initial _
| Sent _
| SendFailed _ -> false
| Uninitialized
| Sending _ -> true
)

prop.onChange (fun (selectedValue: string) ->
if selectedValue = $"%A{Allow}" then
onChange Allow
elif selectedValue = $"%A{Disallow}" then
onChange Disallow
else
())
]
Html.label [ prop.for' inputValueStr; prop.text inputValueStr ]
]
]

[<ReactComponent>]
static member ChargingPermissionComponent() =
let receiveChargingPermission, setReceiveChargingPermission =
React.useState Unavailable

let sendChargingPermission, setSendChargingPermission = React.useState Uninitialized

React.useEffectOnce (fun () ->
RestApi.getChargingPermission (fun receiveChargingPermission ->
setReceiveChargingPermission receiveChargingPermission

match receiveChargingPermission with
| Available chargingPermission -> setSendChargingPermission (Initial chargingPermission)
| Unavailable
| ReceiveFailed _ -> ()))

Html.div [
prop.children [
Html.div [ prop.classes [ "text-xl"; "my-4" ]; prop.text "Charging Permission" ]
ChargingPermissionComponent.RadioInput(
radioInputValue = Allow,
receiveChargingPermission = receiveChargingPermission,
sendChargingPermission = sendChargingPermission,
onChange =
(fun value ->
setSendChargingPermission (Sending value)
RestApi.putChargingPermission value setSendChargingPermission)
)
ChargingPermissionComponent.RadioInput(
radioInputValue = Disallow,
receiveChargingPermission = receiveChargingPermission,
sendChargingPermission = sendChargingPermission,
onChange =
(fun value ->
setSendChargingPermission (Sending value)
RestApi.putChargingPermission value setSendChargingPermission)
)
match sendChargingPermission with
| SendFailed(_, errorInfo) -> Html.div [ prop.text $"Error: %A{errorInfo}" ]
| _ -> ()
]
]

Clone this sample project to run it locally and play with it.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK