Kotlinscript
This toolkit works great on my Raspberry PI 3 using Kotlin script.
What you need:
- Java 17 (or newer)
- For my PI3 I got Zulu 17 from the Azul download site.
- Kotlin commandline compiler
From there you’ll need a Schema for your device
minimal.yaml
# $schema: https://modbus.basjes.nl/v1/ModbusSchema.json
description: 'A very simple demo schema'
schemaFeatureLevel: 1
blocks:
- id: 'Block 1'
description: 'The first block'
fields:
- id: 'Name'
description: 'The name Field'
# If a field NEVER changes value then set this to true
# immutable: true
# If a field is not a user level usable value set this to true (for example a scaling factor)
# system: true
expression: 'utf8(hr:0 # 12)'
tests:
- id: 'Just to demo the test capability'
input:
- firstRegisterAddress: 'hr:0'
registers: |2-
# --------------------------------------
# The name is here
4e69 656c 7320 4261 736a 6573 0000 0000 0000 0000
0000 0000
blocks:
- id: 'Block 1'
expected:
'Name': [ 'Niels Basjes' ]
GetData.main.kts
#!/usr/bin/env kotlin
// Include the needed libraries
@file:DependsOn("nl.basjes.modbus:modbus-api-plc4j:0.6.0")
@file:DependsOn("nl.basjes.modbus:modbus-schema-device:0.6.0")
// Regular Kotlin import statements
import nl.basjes.modbus.device.api.MODBUS_STANDARD_TCP_PORT
import nl.basjes.modbus.device.plc4j.ModbusDevicePlc4j
import nl.basjes.modbus.schema.get
import nl.basjes.modbus.schema.toSchemaDevice
import nl.basjes.modbus.schema.toTable
import nl.basjes.modbus.schema.toYaml
import java.io.File
// The hostname to connect to
val modbusIp = "modbus.iot.basjes.nl"
val modbusPort = MODBUS_STANDARD_TCP_PORT
val modbusUnit = 1
print("Modbus: Connecting...")
// Connect to the real Modbus device over TCP using the Apache PLC4J library
ModbusDevicePlc4j("modbus-tcp:tcp://${modbusIp}:${modbusPort}?unit-identifier=${modbusUnit}")
.use { modbusDevice ->
println(" done")
// Read the schema from a file
val schema = File("minimal.yaml").readText(Charsets.UTF_8)
// Convert that into a SchemaDevice with all the defined mappings (Blocks and Fields)
val device = schema.toSchemaDevice()
// Connect this Schema Device to the physical device (via Plc4J in this case))
device.connect(modbusDevice)
// Get a reference to some of the available fields
val name = device["Block 1"]["Name"]
?: throw IllegalArgumentException("Unable to get the \"Block 1\" -> \"Name\" Field")
// Real Modbus devices are so very slow that we need to indicate which need to be kept up to date
name .need()
// Now we tell the system all fields must be made up-to-date and we consider values
// less than 5 seconds old to be "new enough".
device.update(5000) // << Here the modbus calls are done.
// Output the results as a table ( "----" is a not retrieved register, "xxxx" is a read error )
println(device.toTable(onlyUseFullFields = false, includeRawDataAndMappings = true))
// Now create a NEW test scenario from the currently available values and add that to the
// schema definition.
// Because we have not retrieved ALL registers this test contains severall null values
// which were present in the original
device.createTestsUsingCurrentRealData()
// And print the entire thing as a reusable schema yaml (which now has 2 test scenarios!!)
println("#-------------- BEGIN: MODBUS SCHEMA --------------")
println(device.toYaml())
println("#-------------- END: MODBUS SCHEMA ----------------")
}