Overview
The Siemens SIMATIC IOT2000 series (IOT2020, IOT2040) is an industrial gateway platform that runs a custom Linux image built with the Yocto Project and the OpenEmbedded build system. Siemens ships two reference layers — meta-iot2000-bsp (board support package) and meta-iot2000-example (sample image recipes) — through the official meta-iot2000 GitHub repository. When you need to add packages such as Apache2 with PHP and SQLite, ship a custom application, or change configuration defaults, the recommended path is to create your own Yocto layer rather than editing the upstream layers in place.
This article walks through the complete workflow for building a custom layer for the IOT2000, using kas as the build orchestrator, and explains the role of .bbappend files, the BBFILES variable, and LAYERDEPENDS declarations. The same procedure applies to any Yocto/OE-based image; the IOT2000-specific parts are clearly marked.
Prerequisites
Before you create a custom layer, set up the host environment that matches the IOT2000 BSP requirements.
- Host OS: A 64-bit Linux distribution. The Siemens BSP is validated against Ubuntu 18.04/20.04 LTS; other modern distributions (Debian, Fedora, openSUSE) also work with the standard Yocto dependencies.
-
Required host packages:
git,build-essential,chrpath,diffstat,gawk,gcc-multilib,libsdl1.2-dev,python3,python3-pip,texinfo,unzip,wget,file,libssl-dev,rpcbind. The full list is in the Yocto Project's Build Host Packages reference. -
kas: Install via
pip3 install kas(kas 2.x or 3.x is compatible with meta-iot2000). Verify withkas --version. - Disk space: 80–120 GB free in the build directory. A clean Yocto build with sstate-cache disabled typically consumes 60+ GB.
-
RAM: Minimum 8 GB; 16 GB recommended for parallel builds (
BB_NUMBER_THREADS = "8").
Understanding the IOT2000 Layer Architecture
Yocto layers are isolated directories that each contain a conf/layer.conf file declaring the layer's metadata, the recipes it provides, and its dependencies. For the IOT2000, three layers matter:
| Layer | Source | Role |
|---|---|---|
meta-iot2000-bsp |
Siemens GitHub | Machine configuration (iot2020, iot2040), kernel, bootloader, firmware, GLibc. |
meta-iot2000-example |
Siemens GitHub | Sample image recipes (iot2000-example-image, iot2000-example-image-rt) and a curated conf/distro. |
meta-openembedded |
OpenEmbedded GitHub | Upstream community layers, including meta-webserver, meta-python, meta-networking, etc. |
Siemens already pulls meta-openembedded into the default build via the kas file in meta-iot2000-example. You do not need to clone it manually when your custom kas file declares the same repositories. This avoids the fatal: not a git repository (or any of the parent directories): .git error that occurs when kas tries to update a directory it cannot identify.
Step 1 — Create the Custom Layer Directory Structure
Create a sibling directory to meta-iot2000-bsp and meta-iot2000-example. The directory name becomes the layer name; use only letters, digits, and hyphens.
- Open a terminal in the parent directory that contains the two Siemens layers.
- Create the layer skeleton:
mkdir -p meta-my-iot2000/conf mkdir -p meta-my-iot2000/recipes-core/images mkdir -p meta-my-iot2000/recipes-httpd/apache2 - Initialise an empty
README.mdand optionalLICENSE(e.g. MIT) so the layer is self-describing.
The recipes-core/images path is not strictly required, but mirroring the upstream naming convention makes BBFILES patterns easy to read.
Step 2 — Write the layer.conf File
Every layer must have a conf/layer.conf declaring what recipes it provides and which other layers it depends on. For a layer that only customises the IOT2000 example image, a minimal file looks like:
# We have a conf and classes directory, add to BBPATH
BBPATH .= ":${LAYERDIR}"
# We have recipes-* directories, add to BBFILES
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
${LAYERDIR}/recipes-*/*/*.bbappend"
# Standard layer metadata
LAYERVERSION = "1"
LAYERDEPENDS_meta-my-iot2000 = "core iot2000-example openembedded-layer"
LAYERDEPENDS += "core iot2000-example openembedded-layer"
# Recognise bitbake variables LAYERDIR and LAYERVERSION
LAYERSERIES_COMPAT_meta-my-iot2000 = "dunfell gatesgarth hardknott honister kirkstone"
Key declarations:
-
BBFILES: Globs the recipes and bbappends. If your bbappend is not picked up, the glob is the first place to check — bitbake silently ignores files outside the pattern. -
LAYERDEPENDS: Lists the layers your layer requires. Omittingiot2000-examplecauses bitbake to fail with "variable do_compile not found" or "recipe not found" when processing bbappends that target the IOT2000 image recipe. Forgettingopenembedded-layertriggers errors when extending recipes that originate frommeta-openembedded. -
LAYERSERIES_COMPAT: Yocto release codenames your layer has been tested with. Mismatches raise a warning but do not block the build.
layer.conf, rename every LAYERDEPENDS_<name> variable to match your layer name. The trailing _<name> token is a per-layer namespace and bitbake will not resolve dependencies declared under the wrong name.Step 3 — Create the kas Build File
kas is a YAML-driven build orchestrator that clones repositories, checks out the right revisions, and runs bitbake. Drop a kas.yml at the root of your custom layer:
header:
version: 11
machine: iot2040
distro: iot2000-example
target:
- iot2000-example-image
repos:
meta-iot2000:
url: https://github.com/siemens/meta-iot2000.git
refspec: master
meta-openembedded:
url: https://github.com/openembedded/meta-openembedded.git
refspec: master
meta-my-iot2000:
url: .
refspec:
layers:
- name: meta-iot2000-bsp
- name: meta-iot2000-example
- name: meta-openembedded/meta
- name: meta-openembedded/meta-webserver
- name: meta-openembedded/meta-python
- name: meta-openembedded/meta-networking
- name: meta-my-iot2000
Notes on the kas file:
-
url: .formeta-my-iot2000tells kas to use the current directory instead of cloning from a remote. This is what enables the "no.git" workflow when you develop locally. - YAML colon placement: A colon must be followed by a space. The error "while scanning a simple key ... could not find expected ':'" is a YAML parse error and has nothing to do with bitbake.
-
Order of
layersmatters when two layers provide the same recipe. The later entry wins; placemeta-my-iot2000last so your bbappends override upstream values.
Step 4 — Extend the IOT2000 Example Image with a .bbappend
A .bbappend file is appended to the original recipe when bitbake parses it. Use it to add packages to IMAGE_INSTALL, override variables, or append files to IMAGE_FEATURES.
- Mirror the directory structure of the original recipe. If
iot2000-example-image.bblives atmeta-iot2000-example/recipes-core/images/, place the bbappend at the same path inside your custom layer:meta-my-iot2000/recipes-core/images/iot2000-example-image.bbappend - Write the bbappend:
IMAGE_INSTALL_append = " apache2 php php-cli php-sqlite3 sqlite3" # Optional: enable Apache modules PACKAGECONFIG_append_pn-apache2 = " ssl" # Optional: start Apache by default SYSTEMD_AUTO_ENABLE_pn-apache2 = "enable" - Save the file. The next
kas build kas.ymlwill pick it up becauseBBFILESin yourlayer.confmatches*.bbappend.
iot2000-example-image%.bbappend is supported by bitbake and targets both iot2000-example-image and iot2000-example-image-rt (the real-time variant). Use the bare name when you only need to affect the standard image.Step 5 — Optional: Carry a Local Apache2 Recipe with PHP and SQLite
If you need to modify the Apache2 recipe itself — for example to force-enable mod_php or apply a patch — copy the upstream apache2_2.4.29.bb from meta-openembedded/meta-webserver/recipes-httpd/apache2 into meta-my-iot2000/recipes-httpd/apache2/ and then either edit the recipe in place or add an apache2_2.4.29.bbappend next to it. Carrying the unmodified upstream recipe is the correct approach when you need full control; carrying only a bbappend is preferred when your changes are limited to PACKAGECONFIG entries and patches.
A minimal bbappend for enabling PHP and SQLite support looks like:
PACKAGECONFIG_append = " php"
SRC_URI_append = " file://my-mod_php.conf"
do_install_append() {
install -d ${D}${sysconfdir}/apache2/mods-enabled
install -m 0644 ${WORKDIR}/my-mod_php.conf ${D}${sysconfdir}/apache2/mods-enabled/php.conf
}
The recipe from meta-openembedded is already known to the build because Siemens' kas file pulls meta-openembedded/meta-webserver into the layer set. You do not need to copy it; only the bbappend is required if your changes are additive.
Step 6 — Run the Build
From the root of your custom layer, execute:
kas build kas.yml
kas will:
- Clone
meta-iot2000andmeta-openembeddedinto a workspace. - Symlink or copy your
meta-my-iot2000directory into that workspace. - Run
source oe-init-build-env. - Append each
conf/layer.conftobitbake.conf. - Execute
bitbake iot2000-example-image.
For an incremental rebuild after editing a bbappend, the same command is sufficient. For a clean build, append --force-checkout or delete the build/tmp directory.
Verification
After a successful build, the image is written to build/tmp/deploy/images/iot2040/ as iot2000-example-image-iot2040.wic (or .ext4 / .hddimg depending on the BSP version). To verify the customisation took effect:
- Inspect the package list:
gunzip -c build/tmp/deploy/images/iot2040/iot2000-example-image-iot2040.wic.gz | \ (mknod /tmp/img p; cat > /tmp/img) 2>/dev/null sudo losetup -P /dev/loop0 /tmp/img sudo mount /dev/loop0p2 /mnt ls /mnt/usr/lib/apache2/modules/ | grep php ls /mnt/usr/bin/ | grep sqlite sudo umount /mnt - Boot the IOT2040 with the new image, log in over serial or SSH, and confirm:
systemctl status apache2 php -v sqlite3 --version apache2ctl -M | grep php - Confirm the custom layer's bbappend was parsed:
bitbake-getvar -r iot2000-example-image IMAGE_INSTALL | grep apache2
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
fatal: not a git repository when kas starts |
kas was invoked from a directory outside the custom layer, or the layer has no .git and url: . is missing. |
Invoke kas build from inside meta-my-iot2000. Set url: . for the local layer. |
| bbappend silently ignored |
BBFILES glob in layer.conf does not match the path. |
Compare the path against the glob; run bitbake-layers show-appends to confirm parsing. |
ERROR: Nothing PROVIDES 'apache2' |
meta-openembedded/meta-webserver not declared in kas layers:. |
Add the layer to kas.yml under layers: and rebuild. |
do_compile failed in apache2 with PHP patch errors |
Local recipe copy is older than the meta-openembedded version that provides PHP support. | Refresh from upstream or use a bbappend with PACKAGECONFIG_append = " php" instead of carrying the full recipe. |
| Image builds but Apache2 not installed at runtime |
IMAGE_INSTALL_append missing or typo in package name. |
Run bitbake-getvar -r iot2000-example-image IMAGE_INSTALL to inspect the resolved list. |
Layer 'meta-my-iot2000' depends on missing layer 'openembedded-layer' |
Spelling error or missing LAYERDEPENDS line. |
Add LAYERDEPENDS += "openembedded-layer" and ensure the layer name matches LAYERDEPENDS_<layername> token. |
YAML parse error: could not find expected ':'
|
Missing space after a colon, or tab indentation. | Use two-space indentation throughout; never mix tabs and spaces. |
Best Practices for Maintainable IOT2000 Custom Layers
-
Never edit upstream Siemens layers in place. All customisations belong in your own layer so that upgrading
meta-iot2000remains a one-step operation. - Prefer bbappend over recipe copying. A bbappend survives upstream refactors; a copied recipe diverges silently.
-
Pin upstream revisions. Replace
refspec: masterwith a commit SHA or tag in the kas file for reproducible builds. - Keep IMAGE_INSTALL appends sorted alphabetically. Bitbake is order-insensitive, but humans are not. Sorting prevents duplicate appends during merges.
-
Use a shared sstate-cache. Configure
SSTATE_MIRRORSinbuild/conf/local.confto point at a CI archive and cut rebuild time from hours to minutes. -
Test the bbappend in isolation. Build only
apache2withbitbake apache2 -c populate_sysrootbefore triggering a full image build to catch recipe errors early.
Field-Proven Notes
Two operational details that consistently surface during commissioning:
-
Serial console configuration. The IOT2000 default
iot2000-example-imageenables the serial console at 115200 8N1 on the first UART. Custom layers that override[email protected]orIMAGE_FEATURESshould re-enable it explicitly to keep debugging access after field deployment. -
Read-only root filesystem. The Siemens example image ships a read-only rootfs with an overlay for
/var. Adding packages that write to/etc(e.g.apache2with default config) will fail on first boot. UseIMAGE_FEATURES_append = " read-only-rootfs"together with a custom systemd tmpfiles configuration to redirect writable paths to/var.
References Within This Article
- Siemens meta-iot2000 GitHub repository
- meta-openembedded Apache2 recipe
- Yocto Project: Customising Your Image with a Custom Layer
- BitBake User Manual
- kas documentation
What is the difference between a .bb recipe and a .bbappend file?
A .bb recipe defines a package from scratch (sources, build steps, dependencies). A .bbappend is merged into an existing .bb at parse time and is used to add configuration, patches, or extra packages without duplicating the recipe. For image customisation on the IOT2000, prefer .bbappend to keep your layer upgrade-safe.
Do I need to clone meta-openembedded manually?
No. Declare meta-openembedded in your kas file's repos: and layers: sections, and kas will clone it. Manually cloning first and then pointing kas at the directory causes the fatal: not a git repository error when kas tries to update it.
Where should my custom layer live relative to meta-iot2000?
As a sibling directory — for example, workspace/meta-my-iot2000 next to workspace/meta-iot2000. The exact location is not enforced by bitbake, but a sibling layout keeps the kas file and the layer together for easy versioning.
How do I add Apache2 with PHP and SQLite to the IOT2000 image?
Add IMAGE_INSTALL_append = " apache2 php php-cli php-sqlite3 sqlite3" to iot2000-example-image.bbappend in your custom layer, and ensure meta-openembedded/meta-webserver and meta-openembedded/meta-python are listed in your kas file. A full rebuild will then include the packages and their dependencies.
Why is my bbappend being ignored by bitbake?
Most commonly, the BBFILES variable in your conf/layer.conf does not glob the directory you placed the bbappend in. Run bitbake-layers show-appends from inside the build directory to confirm whether the append was parsed. If the file is missing from the output, adjust the BBFILES pattern.