diff --git a/internal/api/docs/openapi.yaml b/internal/api/docs/openapi.yaml index f50969e6..f32c9a76 100644 --- a/internal/api/docs/openapi.yaml +++ b/internal/api/docs/openapi.yaml @@ -1204,6 +1204,8 @@ components: type: string name: type: string + require_model: + type: boolean required: - id - name @@ -1332,6 +1334,8 @@ components: type: string readme: type: string + require_model: + type: boolean status: type: string used_by_apps: @@ -1365,6 +1369,8 @@ components: type: string name: type: string + require_model: + type: boolean status: type: string variables: @@ -1386,6 +1392,8 @@ components: type: string name: type: string + require_model: + type: boolean status: type: string type: object diff --git a/internal/e2e/client/client.gen.go b/internal/e2e/client/client.gen.go index 1f3e6bbd..496680c9 100644 --- a/internal/e2e/client/client.gen.go +++ b/internal/e2e/client/client.gen.go @@ -71,9 +71,10 @@ type AppBrickInstancesResult struct { // AppDetailedBrick defines model for AppDetailedBrick. type AppDetailedBrick struct { - Category *string `json:"category,omitempty"` - Id string `json:"id"` - Name string `json:"name"` + Category *string `json:"category,omitempty"` + Id string `json:"id"` + Name string `json:"name"` + RequireModel *bool `json:"require_model,omitempty"` } // AppDetailedInfo defines model for AppDetailedInfo. @@ -151,6 +152,7 @@ type BrickDetailsResult struct { Id *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` Readme *string `json:"readme,omitempty"` + RequireModel *bool `json:"require_model,omitempty"` Status *string `json:"status,omitempty"` UsedByApps *[]AppReference `json:"used_by_apps"` Variables *map[string]BrickVariable `json:"variables,omitempty"` @@ -165,6 +167,7 @@ type BrickInstance struct { Id *string `json:"id,omitempty"` Model *string `json:"model,omitempty"` Name *string `json:"name,omitempty"` + RequireModel *bool `json:"require_model,omitempty"` Status *string `json:"status,omitempty"` // Variables Deprecated: use config_variables instead. This field is kept for backward compatibility. @@ -173,12 +176,13 @@ type BrickInstance struct { // BrickListItem defines model for BrickListItem. type BrickListItem struct { - Author *string `json:"author,omitempty"` - Category *string `json:"category,omitempty"` - Description *string `json:"description,omitempty"` - Id *string `json:"id,omitempty"` - Name *string `json:"name,omitempty"` - Status *string `json:"status,omitempty"` + Author *string `json:"author,omitempty"` + Category *string `json:"category,omitempty"` + Description *string `json:"description,omitempty"` + Id *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + RequireModel *bool `json:"require_model,omitempty"` + Status *string `json:"status,omitempty"` } // BrickListResult defines model for BrickListResult. diff --git a/internal/e2e/daemon/app_test.go b/internal/e2e/daemon/app_test.go index 1de8ff64..b62b882b 100644 --- a/internal/e2e/daemon/app_test.go +++ b/internal/e2e/daemon/app_test.go @@ -783,9 +783,10 @@ func TestAppDetails(t *testing.T) { require.Len(t, *detailsResp.JSON200.Bricks, 1) require.Equal(t, client.AppDetailedBrick{ - Id: ImageClassifactionBrickID, - Name: "Image Classification", - Category: f.Ptr("video"), + Id: ImageClassifactionBrickID, + Name: "Image Classification", + Category: f.Ptr("video"), + RequireModel: f.Ptr(true), }, (*detailsResp.JSON200.Bricks)[0], ) diff --git a/internal/e2e/daemon/brick_test.go b/internal/e2e/daemon/brick_test.go index 02736884..5d709bb3 100644 --- a/internal/e2e/daemon/brick_test.go +++ b/internal/e2e/daemon/brick_test.go @@ -83,6 +83,7 @@ func TestBricksList(t *testing.T) { require.Equal(t, bIdx.Description, *brick.Description) require.Equal(t, "Arduino", *brick.Author) require.Equal(t, "installed", *brick.Status) + require.Equal(t, bIdx.RequireModel, *brick.RequireModel) } } diff --git a/internal/orchestrator/bricks/bricks.go b/internal/orchestrator/bricks/bricks.go index cc9128c7..df49c1ec 100644 --- a/internal/orchestrator/bricks/bricks.go +++ b/internal/orchestrator/bricks/bricks.go @@ -58,12 +58,13 @@ func (s *Service) List() (BrickListResult, error) { res := BrickListResult{Bricks: make([]BrickListItem, len(s.bricksIndex.Bricks))} for i, brick := range s.bricksIndex.Bricks { res.Bricks[i] = BrickListItem{ - ID: brick.ID, - Name: brick.Name, - Author: "Arduino", // TODO: for now we only support our bricks - Description: brick.Description, - Category: brick.Category, - Status: "installed", + ID: brick.ID, + Name: brick.Name, + Author: "Arduino", // TODO: for now we only support our bricks + Description: brick.Description, + Category: brick.Category, + Status: "installed", + RequireModel: brick.RequireModel, } } return res, nil @@ -85,6 +86,7 @@ func (s *Service) AppBrickInstancesList(a *app.ArduinoApp) (AppBrickInstancesRes Author: "Arduino", // TODO: for now we only support our bricks Category: brick.Category, Status: "installed", + RequireModel: brick.RequireModel, ModelID: brickInstance.Model, // TODO: in case is not set by the user, should we return the default model? Variables: variablesMap, // TODO: do we want to show also the default value of not explicitly set variables? ConfigVariables: configVariables, @@ -118,6 +120,7 @@ func (s *Service) AppBrickInstanceDetails(a *app.ArduinoApp, brickID string) (Br Author: "Arduino", // TODO: for now we only support our bricks Category: brick.Category, Status: "installed", // For now every Arduino brick are installed + RequireModel: brick.RequireModel, Variables: variables, ConfigVariables: configVariables, ModelID: modelID, @@ -203,6 +206,7 @@ func (s *Service) BricksDetails(id string, idProvider *app.IDProvider, Author: "Arduino", // TODO: for now we only support our bricks Description: brick.Description, Category: brick.Category, + RequireModel: brick.RequireModel, Status: "installed", // For now every Arduino brick are installed Variables: variables, Readme: readme, diff --git a/internal/orchestrator/bricks/bricks_test.go b/internal/orchestrator/bricks/bricks_test.go index 4fee6fde..0077a5b2 100644 --- a/internal/orchestrator/bricks/bricks_test.go +++ b/internal/orchestrator/bricks/bricks_test.go @@ -503,12 +503,14 @@ func TestAppBrickInstanceModelsDetails(t *testing.T) { {Name: "EI_OBJ_DETECTION_MODEL", DefaultValue: "default_path", Description: "path to the model file"}, {Name: "CUSTOM_MODEL_PATH", DefaultValue: "/home/arduino/.arduino-bricks/ei-models", Description: "path to the custom model directory"}, }, + RequireModel: true, }, { - ID: "arduino:weather_forecast", - Name: "Weather Forecast", - Category: "miscellaneous", - ModelName: "", + ID: "arduino:weather_forecast", + Name: "Weather Forecast", + Category: "miscellaneous", + ModelName: "", + RequireModel: false, }, }, } @@ -577,6 +579,7 @@ func TestAppBrickInstanceModelsDetails(t *testing.T) { require.Equal(t, "installed", res.Status) require.Empty(t, res.ModelID) require.Empty(t, res.CompatibleModels) + require.False(t, res.RequireModel) }, }, { @@ -597,6 +600,7 @@ func TestAppBrickInstanceModelsDetails(t *testing.T) { require.Len(t, res.CompatibleModels, 2) require.Equal(t, "yolox-object-detection", res.CompatibleModels[0].ID) require.Equal(t, "face-detection", res.CompatibleModels[1].ID) + require.True(t, res.RequireModel) }, }, { @@ -618,6 +622,7 @@ func TestAppBrickInstanceModelsDetails(t *testing.T) { require.Len(t, res.CompatibleModels, 2) require.Equal(t, "yolox-object-detection", res.CompatibleModels[0].ID) require.Equal(t, "face-detection", res.CompatibleModels[1].ID) + require.True(t, res.RequireModel) }, }, } diff --git a/internal/orchestrator/bricks/testdata/bricks-list.yaml b/internal/orchestrator/bricks/testdata/bricks-list.yaml index 8e3114d6..9a8c68c4 100644 --- a/internal/orchestrator/bricks/testdata/bricks-list.yaml +++ b/internal/orchestrator/bricks/testdata/bricks-list.yaml @@ -23,3 +23,8 @@ bricks: mount_devices_into_container: false ports: [] category: storage +- id: arduino:brick-with-require-model + name: A brick with required model + description: "Brick with required model" + require_model: true + model_name: mobilenet-image-classification \ No newline at end of file diff --git a/internal/orchestrator/bricks/types.go b/internal/orchestrator/bricks/types.go index 782ec2f2..1fca898f 100644 --- a/internal/orchestrator/bricks/types.go +++ b/internal/orchestrator/bricks/types.go @@ -20,12 +20,13 @@ type BrickListResult struct { } type BrickListItem struct { - ID string `json:"id"` - Name string `json:"name"` - Author string `json:"author"` - Description string `json:"description"` - Category string `json:"category"` - Status string `json:"status"` + ID string `json:"id"` + Name string `json:"name"` + Author string `json:"author"` + Description string `json:"description"` + Category string `json:"category"` + Status string `json:"status"` + RequireModel bool `json:"require_model"` } type AppBrickInstancesResult struct { @@ -40,6 +41,7 @@ type BrickInstance struct { Status string `json:"status"` Variables map[string]string `json:"variables,omitempty" description:"Deprecated: use config_variables instead. This field is kept for backward compatibility."` ConfigVariables []BrickConfigVariable `json:"config_variables,omitempty"` + RequireModel bool `json:"require_model"` ModelID string `json:"model,omitempty"` CompatibleModels []AIModel `json:"compatible_models"` } @@ -78,6 +80,7 @@ type BrickDetailsResult struct { Description string `json:"description"` Category string `json:"category"` Status string `json:"status"` + RequireModel bool `json:"require_model"` Variables map[string]BrickVariable `json:"variables,omitempty"` Readme string `json:"readme"` ApiDocsPath string `json:"api_docs_path"` diff --git a/internal/orchestrator/bricksindex/bricks_index_test.go b/internal/orchestrator/bricksindex/bricks_index_test.go index 8b02c8e0..b8bf7c0a 100644 --- a/internal/orchestrator/bricksindex/bricks_index_test.go +++ b/internal/orchestrator/bricksindex/bricks_index_test.go @@ -28,25 +28,36 @@ func TestGenerateBricksIndexFromFile(t *testing.T) { require.NoError(t, err) // Check if ports are correctly set - b, found := index.FindBrickByID("arduino:web_ui") + bWebUI, found := index.FindBrickByID("arduino:web_ui") require.True(t, found) - require.Equal(t, []string{"7000"}, b.Ports) + require.Equal(t, []string{"7000"}, bWebUI.Ports) // Check if variables are correctly set - b, found = index.FindBrickByID("arduino:image_classification") + bIC, found := index.FindBrickByID("arduino:image_classification") require.True(t, found) - require.Equal(t, "Image Classification", b.Name) - require.Equal(t, "mobilenet-image-classification", b.ModelName) - require.True(t, b.RequireModel) - require.Len(t, b.Variables, 2) - require.Equal(t, "CUSTOM_MODEL_PATH", b.Variables[0].Name) - require.Equal(t, "/opt/models/ei/", b.Variables[0].DefaultValue) - require.Equal(t, "path to the custom model directory", b.Variables[0].Description) - require.Equal(t, "EI_CLASSIFICATION_MODEL", b.Variables[1].Name) - require.Equal(t, "/models/ootb/ei/mobilenet-v2-224px.eim", b.Variables[1].DefaultValue) - require.Equal(t, "path to the model file", b.Variables[1].Description) - require.False(t, b.Variables[0].IsRequired()) - require.False(t, b.Variables[1].IsRequired()) + require.Equal(t, "Image Classification", bIC.Name) + require.Equal(t, "mobilenet-image-classification", bIC.ModelName) + require.Len(t, bIC.Variables, 2) + require.Equal(t, "CUSTOM_MODEL_PATH", bIC.Variables[0].Name) + require.Equal(t, "/opt/models/ei/", bIC.Variables[0].DefaultValue) + require.Equal(t, "path to the custom model directory", bIC.Variables[0].Description) + require.Equal(t, "EI_CLASSIFICATION_MODEL", bIC.Variables[1].Name) + require.Equal(t, "/models/ootb/ei/mobilenet-v2-224px.eim", bIC.Variables[1].DefaultValue) + require.Equal(t, "path to the model file", bIC.Variables[1].Description) + require.False(t, bIC.Variables[0].IsRequired()) + require.False(t, bIC.Variables[1].IsRequired()) + + bRequireModel, found := index.FindBrickByID("arduino:model_required") + require.True(t, found) + require.True(t, bRequireModel.RequireModel) + + bDb, found := index.FindBrickByID("arduino:dbstorage_tsstore") + require.True(t, found) + require.False(t, bDb.RequireModel) + + bNoRequireModel, found := index.FindBrickByID("arduino:missing-model-require") + require.True(t, found) + require.False(t, bNoRequireModel.RequireModel) } func TestBricksIndexYAMLFormats(t *testing.T) { diff --git a/internal/orchestrator/bricksindex/testdata/bricks-list.yaml b/internal/orchestrator/bricksindex/testdata/bricks-list.yaml index 0bd036f8..c494df17 100644 --- a/internal/orchestrator/bricksindex/testdata/bricks-list.yaml +++ b/internal/orchestrator/bricksindex/testdata/bricks-list.yaml @@ -130,4 +130,13 @@ bricks: description: path to the custom model directory - name: EI_V_ANOMALY_DETECTION_MODEL default_value: /models/ootb/ei/concrete-crack-anomaly-detection.eim - description: path to the model file \ No newline at end of file + description: path to the model file +- id: arduino:missing-model-require + name: Camera Scanner + description: Scans a camera for barcodes and QR codes + require_container: false + ports: [] +- id: arduino:model_required + name: Model Required Brick + description: A brick that requires a model + require_model: true diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 1a61145c..74851180 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -673,9 +673,10 @@ type AppDetailedInfo struct { } type AppDetailedBrick struct { - ID string `json:"id" required:"true"` - Name string `json:"name" required:"true"` - Category string `json:"category,omitempty"` + ID string `json:"id" required:"true"` + Name string `json:"name" required:"true"` + Category string `json:"category,omitempty"` + RequireModel bool `json:"require_model"` } func AppDetails( @@ -738,6 +739,7 @@ func AppDetails( } res.Name = bi.Name res.Category = bi.Category + res.RequireModel = bi.RequireModel return res }), }, nil