Tutorials

Ingredients

Introduction

Now that we have an assembly, a board, six pumps, and a nozzle, we turn our attention to the actual ingredients. For example, how do we tell our system that pump S3 has Coke syrup attached to it?

Overview

It’s typical that dispenser software includes a list of all possible ingredients (syrups and flavor shots) that could ever be used. Part of the device set up is for the technician to hook up syrup boxes to the various pumps, and then inform the software what syrup is connected to which pump.

Define All Possible Ingredients

This section describes how we create a list of all possible ingredients. Of course, we use a subset to shorten the example.

Project file structure

Here’s the set of files we’re going to add to our project:

brandset files
Figure 1. BrandSet file structure

It includes the following files:

descriptor.json

Contains the list of all possible ingredients (syrups and flavors).

pom.xml

Contains the instructions on how to build a brandset KAB file, which is used by our KOS app.

assembly.xml

Contains list of files to include in the output KAB.

icons/*.png

Icon images used by the user interface to associate syrups.

These files are shown in detail below. Add these to your project as shown.

1) Descriptor JSON file

Our list of possible ingredients is stored in a JSON file. Each ingredient has a unique ID, a name, a type, whether it’s carbonated or not, and an icon URL. Here’s what that file looks like:

brandset.json file
{
  "ingredients": [
    {
      "id": "coca-cola",
      "name": "Coca-Cola",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Coca-Cola-320.png"
    },
    {
      "id": "diet-coke",
      "name": "Diet Coke",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Diet-Coke-320.png"
    },
    {
      "id": "diet-coke-cfree",
      "name": "Diet Coke Caffeine Free",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Diet-Coke-CFree-320.png"
    },
    {
      "id": "dr-pepper",
      "name": "Dr Pepper",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Dr-Pepper-320.png"
    },
    {
      "id": "dr-pepper-diet",
      "name": "Dr Pepper Diet",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Dr-Pepper-Diet-320.png"
    },
    {
      "id": "fanta-grape",
      "name": "Fanta Grape",
      "type": "syrup",
      "carbonated": false,
      "icon": "icons/Fanta-Grape-320.png"
    },
    {
      "id": "fanta-orange",
      "name": "Fanta Orange",
      "type": "syrup",
      "carbonated": false,
      "icon": "icons/Fanta-Orange-320.png"
    },
    {
      "id": "sprite",
      "name": "Sprite",
      "type": "syrup",
      "carbonated": true,
      "icon": "icons/Sprite-320.png"
    },
    {
      "id": "water-carbonated",
      "name": "Carbonated Water",
      "type": "carb",
      "carbonated": true,
      "icon": "icons/Water-Carbonated-320.png"
    },
    {
      "id": "water-plain",
      "name": "Water",
      "type": "water",
      "carbonated": false,
      "icon": "icons/Water-Plain-320.png"
    }
  ]
}

2) Brandset POM file

pom.xml file
<?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>

    <!-- ====== Basics =============================================================== -->

    <groupId>com.example</groupId>
    <artifactId>brandset</artifactId>
    <version>1.2.3</version>
    <packaging>pom</packaging>
    <name>brandset</name>

    <!-- ====== Properties =========================================================== -->

    <properties>
        <kos.version>1.0.0</kos.version>
        <maven-assembly-plugin.version>3.6.0</maven-assembly-plugin.version>
        <maven-clean-plugin.version>3.2.0</maven-clean-plugin.version>
    </properties>

    <!-- ====== Plugin Repositories ================================================== -->

    <pluginRepositories>
        <pluginRepository>
            <id>kosdevcode</id>
            <name>KOS Plugin Repository</name>
            <url>https://maven.pkg.github.com/kosdev-code/kos-maven</url>
            <releases>
                <enabled>true</enabled>
                <updatePolicy>daily</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </releases>
            <snapshots>
                <enabled>true</enabled>
                <updatePolicy>always</updatePolicy>
                <checksumPolicy>fail</checksumPolicy>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <!-- ====== Build ================================================================ -->

    <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>
            <!-- Convert the ZIP file to a KAB file -->
            <plugin>
                <groupId>com.kosdev.kos.maven</groupId>
                <artifactId>kos-kab-maven-plugin</artifactId>
                <version>${kos-kab-maven-plugin.version}</version>
                <configuration>
                    <type>our-brandset</type>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>kabtool</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3) Assembly XML file

assembly.xml file
<?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>
    <fileSets>
        <fileSet>
            <directory>${project.basedir}</directory>
            <includes>
                <include>brandset.json</include>
                <include>icons/*</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly>

4) Icon PNG images

Grab these from our GitHub repo.

Build the KAB file

To build the BrandSet KAB file:

cd files/brandset
mvn clean install

You’ll see these output files:

brandset kab file
Figure 2. Resultant branset KAB file

Further down this page you’ll add this KAB file to our Studio image.

Java Code

This section shows the Java code that inputs these ingredients.

OurIngredient class

We first create a custom ingredient class, extending BaseIngredient, which contains id and name fields. We add fields to indicate if the resulting beverage is carbonated or not, the filename of the ingredient’s icon, and what the ingredient type is.

OurIngredient class
package com.example;

import com.tccc.kos.ext.dispense.service.ingredient.BaseIngredient;
import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class OurIngredient extends BaseIngredient {

    // IDs for well-known ingredients:
    public static final String WATER_ID = "water-plain";
    public static final String CARB_ID = "water-carbonated";

    public boolean carbonated;  // true if the ingredient requires carbonated water to make a beverage
    public String icon;         // URL to the ingredient's icon
    public Type type;           // type of ingredient

    public enum Type {
        water,
        carb,
        syrup,
        flavor,
    }
}

OurIngredientSource class

Once we have our ingredient class, then we want to build a class that holds all possible ingredients. We name this OurIngredientSource, which derives from IngredientSource.

OurIngredientSource class
package com.example;

import com.tccc.kos.commons.core.vfs.VFSSource;
import com.tccc.kos.commons.kab.KabFile;
import com.tccc.kos.commons.util.KosUtil;
import com.tccc.kos.ext.dispense.service.ingredient.BaseIngredient;
import com.tccc.kos.ext.dispense.service.ingredient.IngredientSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.Data;

public class OurIngredientSource implements IngredientSource<OurIngredient> {

    // KAB type for brandset files:
    public static final String KAB_TYPE = "our-brandset";

    // Mount point for brandset in VFS:
    public static final String MOUNT_POINT = "/brandset";

    // Name of the JSON file in the KAB file:
    private static final String BRANDSET_FILE_NAME = "brandset.json";

    // Map of loaded ingredient data:
    public Map<String, OurIngredient> ingredientMap;

    /**
     * Creates a new {code OurIngredientSource} from a KAB file containing the JSON
     * descriptor file and related icons. This also takes the VFSSource of where the
     * brandset was mounted into the VFS so that we can build URLs for the icons in
     * the KAB file.
     *
     * @param brandSetKabFile the KAB containing the brandset data (JSON + icons)
     * @param source          where the KAB is mounted in VFS (URL space)
     */
    public OurIngredientSource(KabFile brandSetKabFile, VFSSource source) throws IOException {

        // Deserialize JSON into Java objects:
        InputStream inputStream = brandSetKabFile.getEntry(BRANDSET_FILE_NAME).getInputStream();
        Schema schema = KosUtil.getMapper().readValue(inputStream, Schema.class);

        // Set URLs for all the ingredients based on the base path of assets in the KAB:
        String brandsetUrlPrefix = "http://localhost:8081" + source.getBasePath();
        schema.getIngredients().forEach(ingredient -> ingredient.icon = brandsetUrlPrefix + ingredient.getIcon());

        // Convert ingredients list to sorted Tree map for use as IngredientSource:
        ingredientMap = schema.getIngredients().stream().collect(Collectors.
                toMap(BaseIngredient::getId, Function.identity(), (o1, o2) -> o1, TreeMap::new));
    }

    /**
     * Returns the ingredient associated with the given ID.
     */
    @Override
    public OurIngredient getIngredient(String id) {
        return ingredientMap.get(id);
    }

    /**
     * Returns the full collection of all possible ingredients.
     */
    @Override
    public Collection<OurIngredient> getIngredients() {
        return ingredientMap.values();
    }

    @Data
    public static class Schema {
        private List<OurIngredient> ingredients;
    }
}

App changes

@Slf4j
public class MyKosApp extends SystemApplication<BaseAppConfig> {

    private IngredientService<OurIngredient> ingredientService;

    @Override
    public void load() {
        log.info("MyKosApp.load()");

        // Create global IngredientService:
        ingredientService = new IngredientService<>();
        getCtx().add(ingredientService);

        // Create the MyController object:
        getCtx().add(new MyController());
    }

    @Override
    public void start() {
        log.info("MyKosApp.start()");
        assembly = new OurAssembly();
        installAssembly(assembly);
        log.info("Assembly installed.");
        loadTheBrandSet();
    }

    @SneakyThrows
    private void loadTheBrandSet() {
        // Process the brandset
        KabFile kabFile = getSection().getKabByType(OurIngredientSource.KAB_TYPE);
        if (kabFile != null) {
            // Mount the brandset data into web server and grab the VFS source to generate URLs to content:
            VFSSource source = getVfs().mount(OurIngredientSource.MOUNT_POINT, kabFile);
            // Load the brandset using the VFS source to set icon URLs, and use brandset as ingredient source:
            ingredientService.setSource(new OurIngredientSource(kabFile, source));
        } else {
            log.error("No brandset found in the system section of manifest.");
        }
    }

    // . . .
}

Studio Changes

Make the following changes to the Studio image.

Add brandset file as local artifact

We need to add the new BrandSet KAB file to our Studio image:

studio select brandset kab file
Figure 3. Add the brandset KAB file to local artifacts

Drag new local artifact into section

Then we must include this new local artifact into the output sections:

studio drag brandset kab to sections
Figure 4. Drag the brandset artifact to sections

Test It Out

Run your KOS app inside the Studio simulator.

View all ingredients

Make this HTTP GET call:

http://localhost:8081/api/system/ingredients

You should now see the list of all possible ingredients:

postman all ingredients
Figure 5. Our list of all six ingredients

Notice that all ten of our ingredients are displayed, each having the correct ID, name, and type.

View an icon

If you hit:

http://localhost:8081/system/brandset/icons/Coca-Cola-320.png

You should see:

coca cola icon
Figure 6. Coca-Cola ingredient icon

Summary

In this tutorial, we created our ingredients, created our brandset, and added those into the KOS IngredientService component. We used Postman to verify that our code works as intended.

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.