
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?
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.
This section describes how we create a list of all possible ingredients. Of course, we use a subset to shorten the example.
Here’s the set of files we’re going to add to our project:
It includes the following files:
Contains the list of all possible ingredients (syrups and flavors).
Contains the instructions on how to build a brandset KAB file, which is used by our KOS app.
Contains list of files to include in the output KAB.
Icon images used by the user interface to associate syrups.
These files are shown in detail below. Add these to your project as shown.
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:
{
"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"
}
]
}
<?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>
<?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>
Grab these from our GitHub repo.
This section shows the Java code that inputs these ingredients.
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.
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,
}
}
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.
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;
}
}
@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.");
}
}
// . . .
}
Run your KOS app inside the Studio simulator.