Tutorials

Brandset

Introduction

At the core of any dispenser is the product that we wish to dispense to our customers. We have to configure our dispenser to know what the products are and what they consists of. This configuration is known as a Brandset.

What is a Brandset?

A brandset is like a recipe book for a drink dispenser’s brands. It tells the machine what ingredients it has and what combinations of them make specific beverages.

In our dispenser, there are four syrups and two waters. To pour a syrup our dispenser needs to know whether to use water or carb. We can define these relationships by creating ingredients to represent the syrups and waters, and by creating beverages to link them together as a pourable combination.

Key Parts of our Brandset:

  • Ingredients: These are the basic components needed to make drinks, such as water, carb, lemon syrup, cherry syrup, etc.

  • Beverages: These are the final drinks you can get from the machine, like Lemon Zip (carb and lemon syrup) or Cherry Cooler (water and cherry syrup)

Lesson 1: Define Brandset

We want our drink dispenser to be able to pour four different drinks. Here’s how to tell the dispenser what it can make:

part1/brandset/brandset.json
{
  "ingredients": [
    {
      "id": "water",
      "name": "water"
    },
    {
      "id": "carb",
      "name": "carb"
    },
    {
      "id": "lemon",
      "name": "lemon"
    },
    {
      "id": "cherry",
      "name": "cherry"
    },
    {
      "id": "lime",
      "name": "lime"
    },
    {
      "id": "grape",
      "name": "grape"
    }
  ],
  "beverages": [
    {
      "id": "bev:lemon",
      "name": "Lemon Zip",
      "ingredientIds": ["carb", "lemon"]
    },
    {
      "id": "bev:cherry",
      "name": "Cherry Cooler",
      "ingredientIds": ["water", "cherry"]
    },
    {
      "id": "bev:lime",
      "name": "Lime Zip",
      "ingredientIds": ["carb", "lime"]
    },
    {
      "id": "bev:grape",
      "name": "Grape Cooler",
      "ingredientIds": ["water", "grape"]
    }
  ]
}

Create brandset.json in a brandset child module of a part1 project, then paste the sample JSON into it. Your file structure should look like this:

part1/
├── brandset/
│   ├── brandset.json

Lesson 2: Package as KAB

For this brandset to be available in our dispenser, it must be packaged in a KAB file.

Let’s start with a little Maven set up. Our parent needs a POM file that looks like this:

/part1/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.samples.dispenser</groupId>
    <artifactId>dispenser-part1</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <modules>
        <module>brandset</module>
    </modules>
</project>

For now, we only need one child module: brandset.

Place pom.xml in the root of part1.

dispenser-part1/
├── brandset/
│   ├── brandset.json
│   ├── pom.xml
├─ pom.xml

The brandset POM is simple because it does not build an application at all. The purpose of this POM is to package the brandset into a KAB for use by the dispenser in the kOS runtime. All it needs to do is assemble the zip with the maven-assembly-plugin and convert it to a KAB file with the kos-kab-maven-plugin.

part1/brandset/pom.xml
 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <parent>
    <groupId>com.samples.dispenser</groupId>
    <artifactId>dispenser-part1</artifactId>
    <version>1.0.0-SNAPSHOT</version>
  </parent>

  <artifactId>dispenser-part1.brandset</artifactId>
  <packaging>pom</packaging>

  <!-- properties -->
  <properties>
    <maven-assembly-plugin.version>3.6.0</maven-assembly-plugin.version>
    <kos-maven-plugin.version>1.0.7</kos-maven-plugin.version>
  </properties>
  <build>
    <plugins>
      <!-- Package the build output into a ZIP file -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>${maven-assembly-plugin.version}</version>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <descriptors>
                <descriptor>assembly.xml</descriptor>
              </descriptors>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <!-- @kdoc-brandset-plugin-pom@ -->
      <!-- Convert the ZIP file to a KAB file -->
      <plugin>
        <groupId>com.kosdev.kos.maven</groupId>
        <artifactId>kos-kab-maven-plugin</artifactId>
        <configuration>
          <type>dispenser.brandset</type>
        </configuration>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>kabtool</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

It’s important to note that in our POM we have configured our brandset KAB as type dispenser.brandset. The type of a KAB is often used to determine how the KAB is to be used. Certain KAB types are defined by kOS and have specific semantics such as the dispenser.brandset. This will come in handy when we need to use the brandset in our app.

The assembly plugin will need an assembly.xml in the brandset root directory.

/part1/brandset/assembly.xml
<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.1.1"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.1.1 http://maven.apache.org/xsd/assembly-2.1.1.xsd">
    <id>app</id>
    <baseDirectory>/</baseDirectory>
    <formats>
        <format>zip</format>
    </formats>
    <files>
        <file>
            <source>${project.basedir}/brandset.json</source>
        </file>
    </files>
</assembly>

Your project should look like this.

dispenser-part1/
├── brandset/
│   ├── assembly.xml
│   ├── brandset.json
│   ├── pom.xml
├─ pom.xml

Go ahead and build your KAB by executing mvn clean install on the brandset\pom.xml. With a successful build, you will find brandset-1.0.0-SNAPSHOT.kab in your projects target directory. Congratulations! You have build a brandset that can be accessed by a dispenser.

Lesson 3: Implement Brandset Class

In this lesson, we will walk through the process of creating a Brandset class. This class will represent our brandset in the application, allowing it to be used as a source of ingredients and beverages by the IngredientService.

Step 1: Setup

Before we code, we need our dispenser Maven child module, so let’s do some more setup.

  1. Add the /dispenser project under the parent root then download pom.xml, the assembly.xml, and the descriptor.xml to the root of /dispenser from the repo.

dispenser-part1/
├── brandset/
│   ├── pom.xml
│   ├── assembly.xml
└── dispenser/
└── src/main/java/
├── pom.xml
├── descriptor.json
├── assembly.xml

The dispenser POM has a critical addition: the Dispense Extension. This dependency provides all the specialized tools for building dispensers. Our previous tutorial was simpler, focusing on a smart rack that only required the kOS api-core. But now, because we’re building a dispenser, we need the kOS api-dispenser. Your <dependencies> will look like this:

Dependencies for part1/dispenser/pom.xml
<!-- project dependencies -->
  <dependencies>
...
    <dependency>
      <groupId>com.kosdev.kos.sdk.api</groupId>
      <artifactId>api-dispense</artifactId>
    </dependency>
....
  </dependencies>

Step 2: Define the Class

The Brandset class should implement the IngredientSource interface, allowing the brandset to be used as a source of ingredients by the IngredientService. Additionally, we will extend it to serve as a source for beverages. Remember our ingredients and beverages elements in brandset.json? That data will be loaded into an instance of this class to be referenced within our app.

Brandset.java
package com.kosdev.samples.dispenser.part1.brandset;
import com.tccc.kos.ext.dispense.service.ingredient.BaseIngredient;
import com.tccc.kos.ext.dispense.service.ingredient.IngredientSource;
import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
public class Brandset implements IngredientSource {
    static final String SOURCE_ID = "brandset";
    private List<Ingredient> ingredients;
    private List<Beverage> beverages;

    @Override
    public String getSourceId() {
        return SOURCE_ID;
    }
    @Override
    public BaseIngredient getIngredient(String id) {
        return ingredients.stream()
        .filter(i -> i.getId().equals(id))
        .findFirst()
        .orElse(null);
        }
}

As you can see, brandset depends on two beans: Ingredient and Beverage

Let’s define them. Note that BaseIngredient gives us id and name, so that do not need to be declared.

Ingredient.java
package com.kosdev.samples.dispenser.part1.brandset;

import com.tccc.kos.ext.dispense.service.ingredient.BaseIngredient;

public class Ingredient extends BaseIngredient {
    // Ingredient ids for water so we can assign it manually
    public static final String WATER = "water";
    public static final String CARB = "carb";
}
Beverage.java
package com.kosdev.samples.dispenser.part1.brandset;

import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
public class Beverage {
    private String id;
    private String name;
    private List<String> ingredientIds;
}
Importance of IngredientService

We’ve mentioned IngredientService a couple of times so let’s talk a little bit about it.

IngredientService provides access to available ingredients. This provides a central service that coordinates the installation and dissemination of ingredients along with relevant lifecycle events. Ingredients provided by this service are used during the insertion process to assign ingredients to holders. This service just acts as a proxy to sources of ingredients provided by IngredientSource objects.

This service allows multiple ingredient sources to be registered and holders can be tagged with corresponding ingredient source id’s to ensure that only ingredients from the tagged source can be inserted. The service has a concept of a default source which can be used in applications that don’t require multiple distinct ingredients collections.

By implementing the IngredientSource interface, the Brandset class can now provide ingredients to the IngredientService.

Lesson 4: Loading Brandset into App

We have a brandset and we have a Brandset class represent it logically. Now, let’s load it into our app.

In My First kOS App, we learned that each kOS device must have one and only one System Application to boot the system. For part one of our dispenser, we will need: load() and start(). Declare the Brandset variable as an instance variable with a getter in DispenserApp so that it can be used throughout the application. Then inject the IngredientService in the DispenserApp to set the brandset as the app’s default ingredient source.

DispenserApp.java
package com.kosdev.samples.dispenser.part1;

import com.tccc.kos.core.service.app.BaseAppConfig;
import com.tccc.kos.core.service.app.SystemApplication;
import com.kosdev.samples.dispenser.part1.brandset.Brandset;

public class DispenserApp extends SystemApplication<BaseAppConfig> {
    @Getter
    private Brandset brandset;

    @Autowired
    private IngredientService ingredientService;

    @Override
    public void load() throws Exception {
        // Called when the application is loading; used for initialization
    }
    @Override
    public void start() throws Exception {
        // Called when the app is starting
    }
}
Autowiring in kOS

Similar to popular dependency-injection frameworks like Spring, kOS provides autowiring support via annotations.

Load the brandset and set it as the default ingredient source in the start() lifecycle method. Earlier in this chapter, we created a KAB of type dispenser.brandset that contains definitions of our ingredients and beverages. Later in this tutorial, this KAB artifact, along with the kos.system KAB, is placed into a container called a Section . Our DispenserApp inherits a method that retrieves a reference to the section, which can be used to find the KAB artifacts it contains based on the KAB type. We use a standard object mapper to bind the json data to the Brandset type which we set as the default source in our ingredientService bean.

start() of DispenserApp.java
@Override
public void start() throws Exception {
    // Look for the brandset KAB in the same section as this system app
    KabFile kab = getSection().getKabByType("dispenser.brandset");
    if (kab != null) {
        // Load the brandset JSON into a brandset object
        brandset = KosUtil.getMapper().readValue(kab.getInputStream("brandset.json"), Brandset.class);
        // Tell kOS about all the ingredients using the IngredientSource interface
    ingredientService.setDefaultSource(brandset);
    }
}

You should be able to build the entire multi-module project by executing mvn clean install in the parent directory. In the target of the dispenser project now you should see dispenser-part1.dispenser-1.0.0-SNAPSHOT.kab.

Summary

In this section, we covered the creation and configuration of a brandset for your kOS dispenser. We started by setting up the JSON configuration file (brandset.json), then built it into a KAB file. Next, we implemented a Brandset class to manage these ingredients and beverages, integrating it with the IngredientService. Finally, we loaded the brandset into the application, ensuring it is ready for use in the dispenser.

Previous
Next
On this page
Java Development
Seamlessly transition from Legacy+ systems to Freestyle microdosing and advanced distributed dispense systems.
UI Development
Using kOS SDKs, integrating Consumer and Non-consumer facing UIs becomes seamless, giving you less hassle and more time to create.
Video Library
Meet some of our development team, as they lead you through the tools, features, and tips and tricks of various kOS tools.
Resources
Familiarize yourself with kOS terminology, our reference materials, and explore additional resources that complement your kOS journey.
Copyright © 2024 TCCC. All rights reserved.