Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get started #82

Merged
merged 2 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion .github/workflows/docs-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ on:
- 'release-*'

jobs:
docs:
api-docs:
runs-on: ubuntu-latest
steps:
# Checkout the source
Expand Down Expand Up @@ -65,3 +65,59 @@ jobs:
working-directory: ./docs
run: |
git push origin master
get-started-docs:
runs-on: ubuntu-latest
steps:
osmanhadzic marked this conversation as resolved.
Show resolved Hide resolved
# Checkout the source
- uses: actions/checkout@v4
with:
path: astarte-device-sdk-csharp
fetch-depth: 0
# Checkout the sdk-doc repository
- uses: actions/checkout@v4
with:
repository: astarte-platform/sdk-doc
ssh-key: ${{ secrets.SDK_DOC_DEPLOY_KEY }}
path: sdk-doc
- name: Check release branch
id: check-release-branch
working-directory: ./astarte-device-sdk-csharp
shell: bash
run: |
latest_release=$(git branch -r | grep "release-*" | sort -t '-' -k 2,2n | tail -n 1 | cut -d '/' -f 2)
current_branch=$(git branch --show-current)
if [[ "$current_branch" == "$latest_release" ]]; then
echo "Current branch is the latest release branch"
echo "RESULT=OK" >> $GITHUB_OUTPUT
else
echo "Current branch is not the latest release branch"
echo "RESULT=FAILED" >> $GITHUB_OUTPUT
fi
- name: Compare and copy get started
id: cmp-and-copy-get-started
if: steps.check-release-branch.outputs.RESULT == 'OK'
run: |
our_get_started="./astarte-device-sdk-csharp/GET_SARTED.md"
their_get_started="./sdk-doc/source/get_started/csharp.md"
if cmp -s "$our_get_started" "$their_get_started"; then
echo "Get started are identical, no need for substitution"
echo "RESULT=NO_SUBSTITUTION" >> $GITHUB_OUTPUT
else
echo "Our get started is different, substituting theirs"
cp "$our_get_started" "$their_get_started"
echo "RESULT=SUBSTITUTION" >> $GITHUB_OUTPUT
fi
- name: Commit changes
id: commit-changes
if: steps.cmp-and-copy-get-started.outputs.RESULT == 'SUBSTITUTION'
working-directory: ./sdk-doc
run: |
git config --local user.email "astarte-machine@ispirata.com"
git config --local user.name "Astarte Bot"
git add .
git diff-index --quiet HEAD || git commit -m "Update CSharp SDK get started"
- name: Push changes
if: steps.commit-changes.conclusion == 'success'
working-directory: ./sdk-doc
run: |
git push origin master
211 changes: 211 additions & 0 deletions GET_STARTED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<!---
Copyright 2024 SECO Mind Srl

SPDX-License-Identifier: Apache-2.0
-->

# Get started with CSharp

Follow this guide to get started with the Astarte device SDK for the CSharp programming language.

## Generating a device ID

A device ID will be required to uniquely identify a device in an Astarte instance. Some of the Astarte device SDKs provide utilities to generate a deterministic or random device identifier, in some cases based on hardware information.

This step is only useful when registering a device using a JWT token and the provided Astarte device SDKs registration APIs. Registration of a device can also be performed outside the device in the Astarte instance. In such cases, the device ID should be obtained via astartectl, or the Astarte dashboard. The device ID should then be loaded manually on the device.

A device ID can be generated randomly:

```CSharp
string deviceId = AstarteDeviceIdUtils.GenerateId();
```

Or in a deterministic way:

```CSharp
Guid nameSpace = Guid.Parse("f79ad91f-c638-4889-ae74-9d001a3b4cf8");
string macAddress = "98:75:a8:0d:96:db";
deviceId = AstarteDeviceIdUtils.GenerateId(nameSpace, macAddress);
```

osmanhadzic marked this conversation as resolved.
Show resolved Hide resolved
## Registering a device

First, generate JWT using astartectl:

```
astartectl utils gen-jwt pairing -k <private-key-file>.pem
```

Then use said token to register the device:

```CSharp
string credentialsSecret = await new AstartePairingService(pairingUrl, realm).RegisterDeviceWithJwtToken(deviceId, jwt);
```

## Instantiating and connecting a new device

```CSharp
// Device creation
// connectionSource allows to connect to a db for persistency
// The interfaces supported by the device are populated by ExampleInterfaceProvider
AstarteDevice myDevice = new(
deviceId,
realm,
credentialsSecret,
interfaceProvider,
pairingUrl,
cryptoStoreDir,
TimeSpan.FromMilliseconds(500));

// ExampleMessageListener listens for device connection, disconnection and failure.
myDevice.SetAstarteMessageListener(new ExampleMessageListener());

// Connect the device
await myDevice.Connect();
```

## Streaming data

All Astarte Device SDKs include primitives for sending data to a remote Astarte instance. Streaming of data could be performed for device-owned interfaces of individual or object aggregation type.

### Streaming individual data

In Astarte interfaces with individual aggregation, each mapping is treated as an independent value and is managed individually.

The snippet below shows how to send a value that will be inserted into the "/test0/value" datastream which is defined by the "/%{sensor_id}/value" parametric endpoint, that is part of the "org.astarte-platform.genericsensors.Values" datastream interface.

```CSharp
AstarteDeviceDatastreamInterface valuesInterface =
(AstarteDeviceDatastreamInterface)myDevice.GetInterface(valuesInterfaceName);

while (true)
{
double value = Random.Shared.NextDouble();
Console.WriteLine("Streaming value: " + value);

await valuesInterface.StreamData($"/{sensorUuid}/value", value, DateTime.Now);

Thread.Sleep(1000);
}
```

osmanhadzic marked this conversation as resolved.
Show resolved Hide resolved
### Streaming aggregated data

In Astarte interfaces with object aggregation, Astarte expects the owner to send all of the interface’s mappings at the same time, packed in a single message.

The following snippet shows how to send a value for an object aggregated interface. In this example, lat and long will be sent together and will be inserted into the "/coords" datastream which is defined by the "/coords" endpoint, that is part of the "com.example.GPS" datastream interface.

```CSharp
AstarteDeviceAggregateDatastreamInterface aggregateInterface =
(AstarteDeviceAggregateDatastreamInterface)myDevice.GetInterface(geolocationInterfaceName);

while (true)
{
Dictionary<string, object> gpsValues = new()
{
{ "latitude", Random.Shared.NextDouble() * 50 },
{ "longitude", Random.Shared.NextDouble() * 50 },
{ "altitude", Random.Shared.NextDouble() },
{ "accuracy", Random.Shared.NextDouble() },
{ "altitudeAccuracy", Random.Shared.NextDouble() },
{ "heading", Random.Shared.NextDouble() },
{ "speed", Random.Shared.NextDouble() * 100 }
};
Console.WriteLine("Streaming object:" + JsonConvert.SerializeObject(gpsValues));
await aggregateInterface.StreamData($"/{sensor_id}", gpsValues, DateTime.Now);
Thread.Sleep(1000);
}
```

## Setting and unsetting properties

Interfaces of property type represent a persistent, stateful, synchronized state with no concept of history or timestamping. From a programming point of view, setting and unsetting properties of device-owned interface is rather similar to sending messages on datastream interfaces.

The following snippet shows how to set a value that will be inserted into the "/sensor0/name" property which is defined by the "/%{sensor_id}/name" parametric endpoint, that is part of the "org.astarte-platform.genericsensors.AvailableSensors" device-owned properties interface.

It should be noted how a property should be marked as unsettable in its interface definition to be able to use the unsetting method on it.

Set property:

```CSharp
AstarteDevicePropertyInterface availableSensorsInterface =
(AstarteDevicePropertyInterface)myDevice.GetInterface(availableSensorsInterfaceName);

availableSensorsInterface.SetProperty($"/{sensorUuid}/name", "randomThermometer");
availableSensorsInterface.SetProperty($"/{sensorUuid}/unit", "°C");
```

Unset property:

```CSharp
AstarteDevicePropertyInterface availableSensorsInterface =
(AstarteDevicePropertyInterface)myDevice.GetInterface(availableSensorsInterfaceName);

availableSensorsInterface.UnsetProperty("/myPath/name");
```

## Receive data on server owner interface

In Astarte interfaces with server owner interface, Astarte sends data to connected devices. If a device is offline when data is sent, it receives the messages as soon as it reconnects to Astarte.

This section provides instructions on how to receive data on the server’s owner interface using the Astarte Device SDK for C#.

```CSharp
using AstarteDeviceSDKCSharp.Protocol;
using AstarteDeviceSDKCSharp.Protocol.AstarteEvents;
using Newtonsoft.Json;

namespace AstarteDeviceSDKExample
{
public class ExampleGlobalEventListener : AstarteGlobalEventListener
{

}
}
```

### Receiving on server owned datastream interface with individual aggregation

```CSharp
public override void ValueReceived(AstarteDatastreamEvent e)
{
Console.WriteLine(
$"Received datastream value on interface {e.GetInterfaceName()}, " +
$"path: {e.GetPath()}, " +
$"value: {e.GetValue()}");
}
```

### Receiving on server owned datastream interface with object aggregation

```CSharp
public override void ValueReceived(AstarteAggregateDatastreamEvent e)
{
Console.WriteLine(
$"Received aggregate datastream object on interface {e.GetInterfaceName()}, " +
$"value: {JsonConvert.SerializeObject(e.GetValues())}");
}
```

### Receiving set on a server owned properties interface

```CSharp
public override void PropertyReceived(AstartePropertyEvent e)
{
Console.WriteLine(
$"Received property on interface {e.GetInterfaceName()}, " +
$"path: {e.GetPath()}, " +
$"value: {e.GetValue()}");
}
```

### Receiving unset on a server owned properties interface

```CSharp
public override void PropertyUnset(AstartePropertyEvent e)
{
Console.WriteLine(
$"Received unset on interface {e.GetInterfaceName()}, " +
$"path: {e.GetPath()}");
}
```
Loading