Softata running on an Arduino Pico W can be remotely controlled through host client app calls to a local class library, through a Web API and by Azure IoT Cloud to Device messages.

Softata was primarily developed to provide a remote .NET interface to Arduino devices running on a RPi Pico W. It took its lead from Firmata an earlier “protocol for communicating with microcontrollers from software on a host computer”. Whilst Firmata was a low-level protocol with device universality which used serial connectivity, Softata is a high-level protocol with connectivity over TCP/IP. With the Softata sketch running on a Pico W, peripheral device functionality can be orchestrated from a client in a number of ways. A Console or Blazor app can remotely initiate functionality through direct calls to the SofataLib class library. A Console and a Blazor app, with menu driven options, have been implemented to exercise Softata on the device. An ASP.NET Web API app exposes the same over a web interface. The Swagger interface for this is enabled facilitating the remote direct call of individual Softata commands on the Pico w. A sensor can periodically send telemetry to an Azure IoT Hub. Cloud to device messages have been harnessed to also implement some remote control of the device such as pausing and continuing the telemetry stream.

When adding a new device, connected to a Pico using Softata, it requires the implementation of methods that are general for a device type in the Arduino context with a .NET mirror. For example, all devices regardless of type, require a default setup method. The main device types are displays, sensors and actuators. All displays will implement a Clear() method. All sensors will implement a Read() method. All actuators will implement a Set(value) method. Displays also implement a Misc() method allowing for display specific methods. SoftataLib is a C# class library that seamlessly wrappers the Arduino functionality acting as a TCP/IP client to the service running on the Pico. It takes high level calls and massages them into a standardized lower level stream, sends them to the Pico and gets the response, passing this back up the protocol stack. The main call to the Pico is

public static string SendMessage(Commands MsgType, byte pin = 0xff, byte state = 0xff, string expect = "OK", byte other=0xff, byte[]? Data=null, bool debug=true )

Except for some simple commands such as Connect(), Version() etc, all SoftataLib methods to the Pico call through this method. These calls must marshal the required parameters into these arguments. Not all arguments are required for all methods, with some using default values and other required but not used parameters passed as zero, ignored on the Pico.

  • MsgType The command or message type. For example DigitalWrite. For device specific commands it specifies the target type as display, sensor or actuator.
  • pin specifies the GPIO pin for, for example, a DigitalWrite call. Also can specify I2C0or1 or Serial1 or 2
  • State * Digital state or for the actual device type specific command such as Clear, Read, Set etc.
  • Expect is used with the result to determine pass or fail of the method call to the device. It is passed back by the TCP/IP service as part of the returned string.
  • Other is a further parameter if required. When a display, sensor or actuator is setup, an index is returned that is used in subsequent calls to its device type API.
  • Data is the optional several additional bytes if required. Used with Display Misc commands, to, for example, pass a string an/or cursor data to an LCD display.

  • With pin specific digital IO, State was initially used with setting or clearing (1 or 0) a specific pin. When the package was expanded to handle device type APIs, this became the device type command. Note that all messages are constructed from bytes to minimise the message size and to simplify interpretation in the Arduino code. The marshalled command is sent the service on the Pico as a string where it is deconstructed back into its parts and is actioned.

An example of a Softata transaction

  1. Setup a DHT11 sensor
cmd:F0 pin:0 param:2 other:0
  • cmd F0:= Sensor
  • pin is not sent but set to zero in the interpretation
  • param (State) 2 = Default setup
  • other 0 specifies the DHT11 sensor (1=BME280 etc) Sent as [0xf0,2,0] as an array of bytes The Curl request URL is:
  1. Read the second property
cmd:F0 pin:1 param:5 other:0
  • cmd F0:= Sensor
  • pin 1: is used to indicate which property is to be read.
  • param (State) 5 = Read one value
  • other 0 = index to instantiated sensor

Sent as [0xf0,1,5,0] as an array of bytes

The Curl request URL is:


Cloud 2 Device Messages

The Console app and the Blazor apps directly access the SoftataLib class library. The WebAPI app has a controller class for each device type. The controller methods make calls to SoftataLib thus exposing it as a web interface. The C2D messages are sent directly to the device. These are interpreted there into an array of bytes, as per those sent from SoftataLib, and inserted to the same queue that the service feeds and so handled in the same way.

An example of a C2D message is, as a string:

telemetry 0 pause  0

This is interpreted in the C2D Message Callback code as C2DMsg:length=0x4 cmd=0xF0 pin=0x0 param=0x9 other=0x0

And submitted as:


The 9 is pause, 10 to continue and 11 to stop telemetry

For a telemetry command, the pin is ignored but other is an index to the sensor instantiation, typically zero but can be more if more than one sensor is instantiated.

The C2D message can also be sent in raw mode as a CSV or space separated list of bytes. For example

240 0 9 0 

… both would also invoke a telemetry pause.

Footnote: Softata make use of both cores of the Pico. The second core is setup and controlled by setup1() and loop1(). Initially the C2D Message callback just passed back data as a data structure that loop() running in the first core was meant to check and process if updated. This did not work. What was required was the pushing of that data into the inter-process queue that is then checked by loop() and popped there and processed if available. This worked. The second core process could though have directly accessed the telemetry code as that is where it runs. The inter-process approach means that, for example, the Servo could be remotely controlled using a D2C message by passing a suitable array of bytes such as:

cmd:F2 pin:0 param:5 other:0  otherData:[1,38]

This would write 0x38 to the servo.

  • F2 means Actuator
  • 2 means to write a value
  • 1 means 1 byte of Data
  • 38 is 0x38 = 56 is the angle written


The C2D msg Callback has been updated for correct handling of raw messages such that the otherData can be two bytes of data; length=1 plus the byte of data. For example:

242 0 5 0 1 56

… sent from Azure IoT Explorer, (you can paste it in), will set the Servo to 56 degrees.

  • The 1 means 1 byte of otherData and 56 is that byte of data. The servo needs to have been setup first though.


There are many ways to exercise peripherals connected to an Pico W running SoftataLib.

  Next: > Aged Care Independent Living
 This Category Links 
Category:Softata Index:Softata
  Next: > Softata
<  Prev:   Softata