Skip to content
Closed
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
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ build-go:
tool-install:
GOBIN=`pwd`/$(TOOL_BIN) go install \
github.com/edaniels/golinters/cmd/combined \
github.com/golangci/golangci-lint/cmd/golangci-lint \
github.com/AlekSi/gocov-xml \
github.com/axw/gocov/gocov \
gotest.tools/gotestsum \
Expand All @@ -29,10 +28,12 @@ tool-install:
lint: lint-go
PATH=$(TOOL_BIN) actionlint

GOVERSION = $(shell grep '^go ' go.mod | head -n1 | cut -d' ' -f2)

lint-go: tool-install
go mod tidy
export pkgs="`go list -f '{{.Dir}}' ./... | grep -v /proto/`" && echo "$$pkgs" | xargs go vet -vettool=$(TOOL_BIN)/combined
GOGC=50 $(TOOL_BIN)/golangci-lint run -v --fix --config=./etc/golangci.yaml
GOTOOLCHAIN=go$(GOVERSION) GOGC=50 go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8 run -v --fix --config=./etc/golangci.yaml

test: test-go

Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ On the new service panel, copy and paste the following attribute template into y
"api_key": "<location-api-key>",
"api_key_id": "<location-api-key-id>",
"organization_id": "<organization_id>",
"location_id": "<location_id>",
"machine_id": "<machine_id>",
"location_id": "<location_id>"
}
```

> **Note:** `machine_id` and `machine_part_id` are automatically read from the `VIAM_MACHINE_ID` and `VIAM_MACHINE_PART_ID` environment variables that viam-server sets on every machine. You only need to set them explicitly in the config when running outside of viam-server.

In addition, in your Cartographer config the setting `"use_cloud_slam"` must be set to `true`. This only applies when trying to use cloudslam. Uploading a locally built map does not require this setting.

### Attributes
Expand All @@ -35,8 +36,8 @@ The following attributes are available for `viam:cloudslam-wrapper:cloudslam`
| `api_key_id` | string | **Required** | location owner API key id |
| `organization_id` | string | **Required** | id string for your [organization](https://docs.viam.com/cloud/organizations/) |
| `location_id` | string | **Required** | id string for your [location](https://docs.viam.com/cloud/locations/) |
| `machine_id` | string | **Required** | id string for your [machine](https://docs.viam.com/appendix/apis/fleet/#find-machine-id) |
| `machine_part_id` | string | Optional | optional id string for the [machine part](https://docs.viam.com/appendix/apis/fleet/#find-machine-id). Used for local package creation and updating mode |
| `machine_id` | string | Optional | id string for your [machine](https://docs.viam.com/appendix/apis/fleet/#find-machine-id). Defaults to the `VIAM_MACHINE_ID` env var set by viam-server |
| `machine_part_id` | string | Optional | id string for the [machine part](https://docs.viam.com/appendix/apis/fleet/#find-machine-id). Defaults to the `VIAM_MACHINE_PART_ID` env var set by viam-server. Used for data capture validation, local package creation, and updating mode |
| `viam_version` | string | Optional | optional string to identify which version of viam-server to use with cloudslam. Defaults to `stable` |
| `slam_version` | string | Optional | optional string to identify which version of cartographer to use with cloudslam. Defaults to `stable` |
| `camera_freq_hz` | float | Optional | set the expected capture frequency for your camera/lidar components. Defaults to `5` |
Expand All @@ -47,16 +48,14 @@ The following attributes are available for `viam:cloudslam-wrapper:cloudslam`
```json
{
"slam_service": "my-actual-slam-service",
"api_key": "location-api-key",
"api_key_id": "location-api-key-id",
"organization_id": "organization_id",
"location_id": "location_id",
"machine_id": "machine_id",
"machine_part_id": "machine_part_id",
"camera_freq_hz": 5.0,
"movement_sensor_freq_hz": 20.0,
"slam_version": "stable",
"viam_version": "stable",
"api_key": "location-api-key",
"api_key_id": "location-api-key-id",
"organization_id": "organization_id",
"location_id": "location_id",
"camera_freq_hz": 5.0,
"movement_sensor_freq_hz": 20.0,
"slam_version": "stable",
"viam_version": "stable"
}
```

Expand Down Expand Up @@ -110,4 +109,4 @@ To interact with a cloudslam mapping session, go to the `DoCommand` on the [Cont
- {`"stop": ""`} will stop an active cloudslam mapping session if one is running. The completed map can be found on the SLAM library tab of the machines page
- {`"save-local-map": "<MAP_NAME>"`} will grab the current map from the configured SLAM service and upload it to your location, in the SLAM library tab of the machines page

For updating a map using cloudslam, a `machine_part_id` must be configured. When configured, the module will check the machine's config to see if any slam maps are configured on the robot. If a slam map is found, cloudslam will be configured for updating mode and the map name will be inherited from the configured map.
For updating a map using cloudslam, the module checks the machine's config for any slam map packages. If one is found and the SLAM service is not in new-map mode, cloudslam will start in updating mode and inherit the map name from the configured package.
78 changes: 78 additions & 0 deletions cloudslam/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cloudslam

import (
"context"
"fmt"
"io"
"net/http"
"net/url"
Expand All @@ -12,6 +13,7 @@ import (
pbPackage "go.viam.com/api/app/packages/v1"
pbApp "go.viam.com/api/app/v1"
"go.viam.com/rdk/logging"
"go.viam.com/rdk/services/slam"
"go.viam.com/utils/rpc"
)

Expand All @@ -27,6 +29,7 @@ type AppClient struct {
SyncClient pbDataSync.DataSyncServiceClient
RobotClient pbApp.RobotServiceClient
HTTPClient *http.Client // used for downloading pcds of the current cloudslam session
logger logging.Logger
}

// CreateCloudSLAMClient creates a new grpc cloud configured to communicate with the robot service based on the cloud config given.
Expand Down Expand Up @@ -61,6 +64,7 @@ func CreateCloudSLAMClient(ctx context.Context, apiKey, apiKeyID, baseURL string
// This might be redundant with CloseIdleConnections in Close(),
// and unsure if the extra cost of redoing the TLS handshake makes this change worth it
HTTPClient: &http.Client{Transport: &http.Transport{DisableKeepAlives: true}},
logger: logger,
}, nil
}

Expand Down Expand Up @@ -101,6 +105,80 @@ func (app *AppClient) GetDataFromHTTP(ctx context.Context, dataURL string) ([]by
return io.ReadAll(res.Body)
}

// CheckSensorsDataCapture verifies that all of the provided sensors have at least one enabled
// data capture method configured in the machine part's config. Returns an error listing any sensors
// that are missing enabled capture.
func (app *AppClient) CheckSensorsDataCapture(ctx context.Context, partID string, sensors []*cloudslamSensorInfo) error {
req := pbApp.ConfigRequest{Id: partID}
resp, err := app.RobotClient.Config(ctx, &req)
if err != nil {
return err
}

pending := make(map[string]struct{}, len(sensors))
sensorTypes := make(map[string]slam.SensorType, len(sensors))
for _, s := range sensors {
pending[s.name] = struct{}{}
sensorTypes[s.name] = s.sensorType
}

for _, comp := range resp.GetConfig().GetComponents() {
if _, ok := pending[comp.GetName()]; !ok {
continue
}
app.logger.Debugf("checking data capture for sensor %q (type %v)", comp.GetName(), sensorTypes[comp.GetName()])
for _, svcConfig := range comp.GetServiceConfigs() {
app.logger.Debugf(" service config type: %q, attributes: %v", svcConfig.GetType(), svcConfig.GetAttributes())
}
if hasEnabledDataCapture(comp, sensorTypes[comp.GetName()]) {
delete(pending, comp.GetName())
}
}

if len(pending) > 0 {
missing := make([]string, 0, len(pending))
for name := range pending {
missing = append(missing, name)
}
return fmt.Errorf("the following sensors do not have data capture enabled: %v", missing)
}
return nil
}

// hasEnabledDataCapture returns true if the component has an appropriate enabled capture method
// in its data_manager service config. For cameras, NextPointCloud must be configured and enabled.
// For other sensor types, any enabled capture method is sufficient.
func hasEnabledDataCapture(comp *pbApp.ComponentConfig, sensorType slam.SensorType) bool {
for _, svcConfig := range comp.GetServiceConfigs() {
if svcConfig.GetType() != "rdk:service:data_manager" {
continue
}
captureMethods := svcConfig.GetAttributes().GetFields()["capture_methods"]
if captureMethods == nil {
continue
}
for _, method := range captureMethods.GetListValue().GetValues() {
fields := method.GetStructValue().GetFields()
if fields["disabled"].GetBoolValue() {
continue
}
if sensorType == slam.SensorTypeCamera {
if fields["method"].GetStringValue() == "NextPointCloud" {
return true
}
} else {
return true
}
}
}
return false
}

// SLAMMapURL returns the app URL for viewing a SLAM map package.
func (app *AppClient) SLAMMapURL(mapName, version string) string {
return app.baseURL + "/robots?page=slam&name=" + mapName + "&version=" + version
}

// Close closes the app clients.
func (app *AppClient) Close() error {
// close any idle connections to prevent goleaks. Possibly redundant with DisableKeepAlives
Expand Down
Loading
Loading