Back to top

Campaigns and firmware

Last updated September 12th, 2024

To use Knox E-FOTA, your customer must create a campaign and select policies. Using campaigns, your customer can set detailed policies to schedule and throttle firmware downloads, and configure firmware policies for their fleet of devices.

After campaign creation, manage firmware with Firmware policies.

Campaign details

Campaigns have many detailed settings which can be set by your customer. To help you provide campaign creation functionalities to your customer, the Knox E-FOTA API provides a detailed list of all available campaign settings complete with data types, default values, dependencies, and the hierarchy among campaign features.

The GET /campaignSchemas operation returns a list of all available campaign settings as a schema in JSON format, and you can use this schema to dynamically generate a campaign creation UX on your UEM portal for your customers. When new Knox E-FOTA campaign features are released, GET /campaignSchemas returns the newly updated schema, and your dynamic campaign creation page can account for the newly added features.

Once your customer finishes entering their campaign settings to your UEM portal, use POST /campaigns to create a campaign with their specified settings.

Each campaign setting listed in the JSON campaign schema has the property “key”, and its value corresponds to a parameter in POST /campaigns which configures that campaign setting. This means you can map entries in the JSON campaign schema to parameters in your POST /campaigns API request to allow your customer to create a campaign. The exceptions to this are entries in the JSON campaign schema with “type”:“object” or “type”:“hidden”, which don’t refer to a corresponding campaign setting.

Here is a sample JSON response from GET /campaignSchemas. You can parse the returned JSON to dynamically generate a campaign creation page, as shown in the UX sample.

Sample code to build a campaign creation page

In this code sample, we build a campaign creation page dynamically based on the JSON campaign schema returned from GET /campaignSchemas.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;

public class SchemaHandler {
    private static boolean inObjectArray = false;
    public static String getSchema(String token, String schemaUrl) {
        String urlString = Config.API_URL + schemaUrl;
        StringBuilder responseContent = new StringBuilder();
        try {
            URL url = new URL(urlString);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Authorization", "Bearer " + token);
            connection.setRequestProperty("Content-Type", "application/json");
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                responseContent.append(inputLine);
            }
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return responseContent.toString();
    }

    public static String extractNestedObjects(String schema, Map<String, Object> category) {
        List<Map<String, Object>> nestedObjects = (List<Map<String, Object>>) category.get("nestedObjects");
        for (Map<String, Object> field : nestedObjects) {
            String title = field.getOrDefault("title", "[Missing title]").toString();
            if (field.containsKey("nestedObjects")) {
                schema += "<article><header>" + title + "</header>";
            }
            if ("objectArray".equals(field.get("type"))) {
                inObjectArray = true;
                schema += "<div class='objectArray'><div class='objectArrayItem template' style='display: none;'>";
            }
            schema += "<label for='" + field.get("key") + "'>" + title;
            if (Boolean.TRUE.equals(field.get("mandatory"))) {
                schema += "<font color='#ff0000'>*</font>";
            }
            if (field.containsKey("description")) {
                String description = field.get("description").toString();
                if (description.length() > 120) {
                    description = description.substring(0, 120) + "...";
                }
                schema += " <font color='#999' data-tooltip='" + description + "' data-placement='right'>🛈</font>";
            }

            String defaultValue = field.getOrDefault("defaultValue", "").toString();
            String disabled = inObjectArray ? "disabled" : "";
            if (field.get("enum") == null) {
                String fieldType = switch (field.get("type").toString()) {
                    case "bool" -> "type='checkbox' role='switch'";
                    case "number" -> "type='number'";
                    default -> "type='text'";
                };
                if ("yyyy-mm-dd".equals(field.get("pattern"))) {
                    fieldType = "type='date'";
                }

                String fieldPattern = field.containsKey("pattern") ? "placeholder=" + field.get("pattern") : "";
                String required = "";
                if (field.containsKey("required")) {
                    if (field.get("required") instanceof Map) {
                        Map<String, String> requiredMap = (Map<String, String>) field.get("required");
                        StringBuilder reqBuilder = new StringBuilder();
                        requiredMap.forEach((k, v) -> reqBuilder.append(k).append("=").append(v).append(", "));
                        required = "reqmap='" + reqBuilder.substring(0, reqBuilder.length() - 2) + "'";
                    } else {
                        List<String> requiredList = (List<String>) field.get("required");
                        required = "req=\"" + String.join(",", requiredList) + "\"";
                    }
                }

                if (!"objectArray".equals(field.get("type")) && !"object".equals(field.get("type"))) {
                    schema += " <input " + fieldType + " " + fieldPattern + " ";
                    schema += Boolean.TRUE.equals(field.get("mandatory")) ? "required " : "";
                    schema += required + " ";
                    schema += "name='" + field.get("key") + "' value='" + defaultValue + "' " + disabled + ">";
                }
            } else { schema += "<select name='" + field.get("key") + "' " + disabled + ">";
                List<Map<String, String>> options = (List<Map<String, String>>) field.get("enum");
                for (Map<String, String> option : options) {
                    String selected = defaultValue.equals(option.get("value")) ? "selected" : "";
                    schema += "<option value=" + option.get("value") + " " + selected + ">";
                    schema += option.get("text") + "</option>";
                }
                schema += "</select>";
            }
            schema += "</label><input type='hidden' value='" + field.get("type") + "' name='" + field.get("key") + "_type'>";
            schema += "<input type='hidden' name='" + field.get("key") + "_container' value='" + category.get("key") + "'>";

            if (field.containsKey("nestedObjects")) {
                schema = extractNestedObjects(schema, field);
            }
            if ("objectArray".equals(field.get("type"))) {
                inObjectArray = false;
                schema += "<hr></div><div role='group' style='width: 20%;'>";
                schema += "<button type='button' onclick='addFields(this)' style='width: 80%;'>+</button>";
                schema += "<button type='button' onclick='remFields(this)' class='secondary'>-</button>";
                schema += "</div></div>";
            }
            if (field.containsKey("nestedObjects")) {
                schema += "</article>";
            }
        }
        return schema;
    }

    public static String getSchemaForm(Map<String, Object> schemaSource) {
        List<Map<String, Object>> categories = (List<Map<String, Object>>) ((Map<String, Object>) schemaSource.getOrDefault("data", Map.of()))
                .getOrDefault("campaignSchemas", List.of(Map.of()))
                .get(0)
                .getOrDefault("campaign", schemaSource.get("campaign"));
        StringBuilder schema = new StringBuilder();
        for (Map<String, Object> category : categories) {
            if (category.containsKey("nestedObjects")) {
                schema.append("<article id='").append(category.get("key")).append("'><header>")
                        .append(category.getOrDefault("title", "[Missing title]")).append("</header>");
                schema = new StringBuilder(extractNestedObjects(schema.toString(), category));
                schema.append("</article>");
            }
        }
        return schema.toString();
    }
} 

Create a campaign

Use POST /campaigns to implement campaign creation functionality on your UEM portal. As mentioned above, we recommend using GET /campaignSchemas to generate a campaign creation page for your customers, then populate your API request to POST /campaigns with the campaign setting values they’ve specified.

Update a campaign

Use PUT /campaigns/{campaignId} to allow your customers to update a specified campaign on your UEM portal. Similar to creating a campaign, you can use GET /campaignSchemas to generate a campaign update page for your customers, then populate your API request with the new campaign setting values they’ve specified.

List campaigns

Use GET /campaigns to get a list of campaigns for your customer, including all information for each campaign. This operation includes sorting and filtering parameters to allow your customer to sort and filter their list of campaigns.

You can also get a specific campaign with GET /campaigns/{campaignId}.

Delete a campaign

Use DELETE /campaigns/{campaignId} to allow your customers to delete a campaign on your UEM portal. You can use Knox Webhook Notification to receive result notifications when the delete campaign operation is complete. To learn more, see the Knox Webhook Notification API reference.

Cancel a campaign

Use PUT /campaigns/{campaignId}/cancel allow your customers to cancel a campaign on your UEM portal.

Campaign termination behavior

The following table outlines what happens to a campaign and its assigned devices when the campaign is terminated, depending on how it was terminated.

Campaign termination action Result Trigger Assigned devices Status Visibility on Knox E-FOTA Can be re-activated
Campaign expiration Policies of the campaign are no longer applied. (The device will complete any ongoing firmware installations first.) The end date of the campaign period passes. Devices remain assigned to the campaign. Expired Yes Yes (By extending the campaign period)
Campaign cancellation Policies of the campaign are no longer applied. (The device will complete any ongoing firmware installations first.) Your customer cancels the campaign. Devices remain assigned to the campaign. Cancelled Yes No
Campaign deletion Policies of the campaign are no longer applied. (The device will complete any ongoing firmware installations first.) Your customer deletes the campaign. Devices are unassigned from the campaign. - No No

Assign a campaign

After creating a campaign and setting its firmware policies, your customer can proceed to assign the campaign to their devices. This will apply all campaign settings and its firmware policies to the devices it is assigned to. If the campaign has no firmware policy for assigned devices, the “lock current firmware” policy is applied by default, and can be updated at any time.

Use POST /campaigns/{campaignId}/bulkAssign to allow your customers to assign a list of devices to a specified campaign. This operation allows customers to assign up to 1000 devices at once to a campaign. If any devices are already assigned to the specified campaign, the Knox E-FOTA API response will indicate devices that are already assigned.

Every 24 hours from when the campaign was activated, the Knox E-FOTA client app on the assigned devices automatically polls the Knox E-FOTA server to check for any policy changes made to the campaign.

Unassign a campaign

If your customers no longer want their devices to be compliant with campaign policies, they can unassign the campaign. When devices are unassigned from their campaign, the lock current firmware policy is applied to them by default.

Use POST /campaigns/{campaignId}/bulkUnassign to allow your customer to unassign a list of devices from a specified campaign. This operation allows customers to unassign up to 1000 devices at once. If any devices are already in an unassigned state, the Knox E-FOTA API response will indicate that those devices are already unassigned.

Firmware policies

After creating their campaign, your customer can proceed to add firmware policies for that campaign. Firmware policies allow your customer to manage firmware for devices assigned to a campaign.

Use PUT /campaigns/{campaignId}/firmwares to allow your customers to set firmware policies for a specified campaign.

Knox E-FOTA provides 4 firmware policies.

  • Lock current firmware — Maintain the current firmware version. This is the default firmware policy.
  • Latest any — Push latest firmware available to devices.
  • Latest up to — Push latest firmware available within a specified Android OS version.
  • Select from firmware list — Push a specific firmware version to devices.

Each firmware policy targets devices with a specific combination of device model, sales code, and CSC. This is because this combination of device information determines what firmware is available for each device. This means separate firmware policies must be created according to the different types of devices uploaded to Knox E-FOTA.

For example, two separate firmware policies are needed to target these two unique combinations of device model, sales code, and CSC:

  • SM-G986N / KOO / OKR — Latest Any
  • SM-G986N / KTC / OKR — Latest Any

If a device is assigned to a campaign that has no firmware policies with a matching combination of device model, sales code, and CSC, the device will default to the “Lock current firmware” policy. Customers can only set firmware policies for devices enrolled to Knox E-FOTA, because Knox E-FOTA does not store sales code and CSC information for unenrolled devices.

Here is a code sample that sets a firmware policy for a campaign:

private HttpResponse<String> updateFirmwarePolicy(String campaignId) throws URISyntaxException, IOException, InterruptedException {
    String firmwarePolicyUrl = API_URI + "campaigns/" + campaignId + "/firmwares";
    String body = "{\n" +
            "  \"firmwares\" : [\n" +
            "    {\n" +
            "      \"model\" : \"SM-960F\",\n" +
            "      \"salesCode\" : \"ATT\",\n" +
            "      \"deviceCount\" : \"2\"\n" +
            "    }\n" +
            "  ] \n" +
            "}";
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI(firmwarePolicyUrl))
            .PUT(HttpRequest.BodyPublishers.ofString(body))
            .headers("Content-Type", "application/json",
                    "Authorization", "Bearer " + accessToken)
            .build();
    return client.send(request, HttpResponse.BodyHandlers.ofString());
}
Response sample:
{"transactionId": "T882582188b10", "code": "00000", "message": "SUCCESS" }

What is a CSC?

A Country Specific Code (CSC) is an essential component of Samsung firmware binaries, and it contains customized settings, system configurations, localizations, and geo-specific configurations such as the system language, APN settings, and carrier-branding. You can allow your customer to see the CSCs for a given device model and sales code with GET /devices/csc.

Get device information for firmware policies

To create a firmware policy, your customer needs to find the device model name, sales code, and CSC of the devices they want to target their firmware policy for. You can provide this information to your customer with the following operations in the Knox E-FOTA API.

  • Use GET /devices/models to retrieve a list of all device models in your customer’s pool of devices on Knox E-FOTA.
  • Use GET /devices/salesCodes to retrieve a list of sales codes for a given device model.
  • Use GET /devices/csc to retrieve a list of CSCs, given a device model and sales code combination.

Here are some code samples that call these operations to get device information for firmware policies.

private HttpResponse<String> getDeviceModels() throws URISyntaxException, IOException, InterruptedException {
    String deviceModelsUrl = API_URI + "devices/models";
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI(deviceModelsUrl))
            .GET()
            .headers("Authorization", "Bearer " + accessToken)
            .build();
    return client.send(request, HttpResponse.BodyHandlers.ofString());
}

Response sample:
{ "transactionId": "T882582188b10", "code": "KFMBE0N00000", "message": "SUCCESS", "data": { "models": ["SM-960F", "SM-111F"]}}

private HttpResponse<String> getSalesCodes(String model) throws URISyntaxException, IOException, InterruptedException {
    String salesCodesUrl = API_URI + "devices/salesCodes?model=" + model;
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI(salesCodesUrl))
            .GET()
            .headers("Authorization", "Bearer " + accessToken)
            .build();
    return client.send(request, HttpResponse.BodyHandlers.ofString());
}

Response sample:
{"transactionId": "T882582188b10", "code": "KFMBE0N00000", "message": "SUCCESS", "data": {"salesCodes": ["BMC"]}}

private HttpResponse<String> getDevicesCscValues(String model, String salesCode) throws URISyntaxException, IOException, InterruptedException {
    String devicesCscUrl = API_URI + "devices/csc?model=" + model + "&salesCode=" + salesCode;
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI(devicesCscUrl))
            .GET()
            .headers("Authorization", "Bearer " + accessToken)
            .build();
    return client.send(request, HttpResponse.BodyHandlers.ofString());
}

Response sample:
{"transactionId": "T882582188b10", "code": "KFMBE0N00000", "message": "SUCCESS", "data": {"cscList": ["OXM","OXW"]}}

Get available firmware list

Your customers may want to see all available firmware for a given combination of device model, sales code, and CSC. You can provide them with this information using GET /fota, which returns a list of all firmware for the given combination, and the firmware list can be filtered based on different filter parameters.

This feature is particularly useful for when your customer wants to use the “Select from firmware list” policy, because it lets them see what firmware is available their devices. When calling PUT /campaigns/{campaignId}/firmwares to set the “Select from firmware list” policy, the firmwareVersion should be set to the toFirmwareVersion returned from GET /fota.

Get firmware policy

You can allow your customers to see the firmware policies of a given campaign with GET /campaigns/{campaignId}/firmwares.

Here is a Java code sample for getting the firmware policy of a campaign:

private HttpResponse<String> getFirmwarePolicy(String campaignId) throws URISyntaxException, IOException, InterruptedException {
    String firmwarePolicyUrl = API_URI + "campaigns/" + campaignId + "/firmwares";
    HttpClient client = HttpClient.newHttpClient();
    HttpRequest request = HttpRequest.newBuilder()
            .uri(new URI(firmwarePolicyUrl))
            .GET()
            .headers("Authorization", "Bearer " + accessToken)
            .build();
    return client.send(request, HttpResponse.BodyHandlers.ofString());
}
Response sample:
{ "transactionId": "T882582188b10", "code": "00000", "message": "SUCCESS", "data": {…} }

Next step

Next, we’ll cover how you can help your customers monitor and troubleshoot devices on Knox E-FOTA.

Monitoring and troubleshooting

Is this page helpful?