Level 5 - Create a Brand/Model Specific Worker
Level 5 workers are concrete implementations that handle actual device communication for specific hardware brands or models.
B.1. Repository Naming Convention
Level 4 repositories follow strict naming patterns (see Naming Conventions).
miningos-wrk-{devicetype}-{brand}Examples:
miningos-wrk-powermeter-abb— ABB power metersminingos-wrk-powermeter-schneider— Schneider Electric power metersminingos-wrk-miner-antminer— Bitmain Antminer hardwareminingos-wrk-miner-whatsminer— MicroBT Whatsminer hardwareminingos-wrk-container-antspace— Bitmain Antspace containersminingos-wrk-sensor-seneca— Seneca temperature sensors
For complete specifications of all supported devices, see Supported Devices.
No tpl in the name — Level 5 workers are concrete, not templates.
B.2. Worker File Naming Convention
Worker files follow a reverse naming pattern based on wtype:
workers/{brand}.rack.{devicetype}.wrk.jsResolution Example:
// CLI: --wtype=wrk-miner-antminer
// Resolution: workers/antminer.rack.miner.wrk.jsFor brands with multiple models requiring separate workers:
workers/{model}.rack.{devicetype}.wrk.jsExample (Schneider with multiple models):
miningos-wrk-powermeter-schneider/
├── workers/
│ ├── pm5340.rack.powermeter.wrk.js # --wtype=wrk-powermeter-pm5340
│ └── p3u30.rack.powermeter.wrk.js # --wtype=wrk-powermeter-p3u30B.3. Directory Structure
A Level 5 implementation repository follows this structure:
miningos-wrk-{devicetype}-{brand}/
├── config/
│ ├── base.thing.json.example # Brand-specific configuration
│ ├── common.json.example # Common worker settings
│ └── facs/
│ ├── modbus.config.json.example # Protocol-specific config (if needed)
│ ├── net.config.json.example # Network/RPC configuration
│ └── store.config.json.example # Storage configuration
├── mock/
│ └── mock-{brand}-device.js # Mock device for testing
├── tests/
│ ├── cases/
│ │ └── {feature}.js # Brand-specific test cases
│ ├── schema/
│ │ └── {feature}.js # Brand-specific schema validators
│ ├── {brand}.spec.js # Main test file
│ ├── executors.js # Test executors (may extend parent)
│ └── utils.js # Test utilities (extends parent)
├── workers/
│ ├── lib/
│ │ ├── {brand}.js # Device library class
│ │ ├── constants.js # Brand-specific constants
│ │ ├── registers.js # Register maps (Modbus devices)
│ │ └── utils.js # Brand-specific utilities
│ └── {brand}.rack.{devicetype}.wrk.js # Main worker implementation
├── LICENSE # Apache-2.0
├── README.md # Documentation
├── package.json # Dependencies
├── setup-config.sh # Configuration setup script
└── worker.js # Entry pointB.4. Package.json Structure
To create a package.json for a Level 5 worker:
- Open a text editor and create the following structure:
{
"name": "miningos-wrk-{devicetype}-{brand}",
"version": "0.0.1",
"description": "MiningOS Worker {DeviceType} {Brand}",
"author": {
"name": "Your Name",
"email": "your.email@example.com"
},
"maintainers": [
{
"name": "Your Name",
"email": "your.email@example.com"
}
],
"keywords": ["miningos", "bitcoin", "mining", "{brand}"],
"scripts": {
"test": "brittle ./tests/**/*.spec.js",
"lint": "standard",
"lint:fix": "standard --fix"
},
"dependencies": {
"async": "3.2.6",
"bfx-svc-boot-js": "git+https://github.com/bitfinexcom/bfx-svc-boot-js.git",
"miningos-tpl-wrk-{devicetype}": "git+https://github.com/tetherto/miningos-tpl-wrk-{devicetype}.git",
"svc-facs-modbus": "git+https://github.com/tetherto/svc-facs-modbus.git"
},
"devDependencies": {
"brittle": "3.16.3",
"miningos-mock-control-service": "git+https://github.com/tetherto/miningos-mock-control-service.git"
},
"engine": {
"node": ">=16.0"
},
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "git+https://github.com/tetherto/miningos-wrk-{devicetype}-{brand}.git"
}
}-
Replace
\{devicetype\}and\{brand\}with your specific values. -
Update the
authorandmaintainersfields. -
Save the file as
package.json.
The dependency is the Level 4 template (miningos-tpl-wrk-{devicetype}), not the Level 3 thing worker.
B.5. Main Worker Implementation
To create the main worker file:
-
Create
workers/\{brand\}.rack.\{devicetype\}.wrk.js. -
Add the following implementation, replacing placeholders:
'use strict'
const WrkRack = require('miningos-tpl-wrk-{devicetype}/workers/rack.{devicetype}.wrk')
const {Brand}Device = require('./lib/{brand}')
class Wrk{DeviceType}Rack extends WrkRack {
/**
* Initialize the brand-specific worker.
* Add protocol facilities required for device communication.
*/
init () {
super.init()
// Add protocol-specific facility (e.g., Modbus for industrial devices)
this.setInitFacs([
['fac', 'svc-facs-modbus', '0', '0', {}, 0]
])
// Brand-specific initialization (optional)
this.brandDefaults = {
timeout: 30000,
retries: 3
}
}
/**
* Returns the full type identifier including brand.
* Concatenates parent type with brand suffix.
* @returns {string} Full device type (e.g., 'powermeter-dkcyon')
*/
getThingType () {
return super.getThingType() + '-{brand}'
}
/**
* Returns brand-specific tags for device categorization.
* @returns {string[]} Array of brand tag strings
*/
getThingTags () {
return ['{brand}']
}
/**
* Establishes connection to the physical device.
* Creates device controller instance and assigns to thg.ctrl.
* @param {Object} thg - Thing object containing opts and metadata
* @returns {number} 1 on success, 0 on failure
*/
async connectThing (thg) {
// Validate required connection options
if (!thg.opts.address || !thg.opts.port) {
return 0 // Signal connection failure
}
// Create device instance with protocol client
const device = new {Brand}Device({
...thg.opts,
getClient: this.modbus_0.getClient.bind(this.modbus_0),
conf: this.conf.thing?.{devicetype} || {}
})
// Set up error handling
device.on('error', (e) => {
this.debugThingError(thg, e)
})
// Assign controller to thing
thg.ctrl = device
return 1 // Signal success
}
/**
* Collects current device state snapshot.
* Called periodically by the scheduling system.
* @param {Object} thg - Thing object with active controller
* @returns {Object} Snapshot containing stats and config
*/
async collectThingSnap (thg) {
return thg.ctrl.getSnap()
}
/**
* Selects device information for API responses.
* Returns connection details and device-specific metadata.
* @param {Object} thg - Thing object (optional, may use this context)
* @returns {Object} Device information object
*/
selectThingInfo (thg) {
const thing = thg || this
return {
address: thing.opts?.address,
port: thing.opts?.port,
unitId: thing.opts?.unitId,
model: thing.opts?.model,
firmware: thing.info?.firmware
}
}
}
module.exports = Wrk{DeviceType}RackB.6. Device Library Class
Create a device library class that handles protocol communication (workers/lib/\{brand\}.js):
'use strict'
const EventEmitter = require('events')
class {Brand}Device extends EventEmitter {
/**
* @param {Object} opts - Device options
* @param {string} opts.address - Device IP address
* @param {number} opts.port - Device port
* @param {number} opts.unitId - Modbus unit ID (for Modbus devices)
* @param {Function} opts.getClient - Protocol client factory
* @param {Object} opts.conf - Device configuration
*/
constructor (opts) {
super()
this.address = opts.address
this.port = opts.port
this.unitId = opts.unitId || 1
this.getClient = opts.getClient
this.conf = opts.conf || {}
this.client = null
this.connected = false
}
/**
* Establishes connection to device.
* @returns {Promise<boolean>} Connection success
*/
async connect () {
try {
this.client = await this.getClient(this.address, this.port)
this.connected = true
return true
} catch (err) {
this.emit('error', err)
return false
}
}
/**
* Collects complete device snapshot.
* @returns {Promise<Object>} Snapshot with stats and config
*/
async getSnap () {
const stats = await this.getStats()
const config = await this.getConfig()
return {
success: true,
stats,
config
}
}
/**
* Collects device statistics/metrics.
* @returns {Promise<Object>} Device metrics
*/
async getStats () {
// Implement protocol-specific data collection
// Example for power meter:
return {
power: 0,
voltage: { L1: 0, L2: 0, L3: 0 },
current: { L1: 0, L2: 0, L3: 0 },
energy: 0,
frequency: 0,
powerFactor: 0
}
}
/**
* Collects device configuration.
* @returns {Promise<Object>} Device configuration
*/
async getConfig () {
return {
model: 'Unknown',
serial: 'Unknown',
firmware: 'Unknown'
}
}
/**
* Gracefully closes device connection.
*/
async close () {
if (this.client && typeof this.client.close === 'function') {
await this.client.close()
}
this.connected = false
}
}
module.exports = {Brand}DeviceB.7. Protocol Facilities
Level 5 workers typically require protocol-specific facilities. Add them in init():
Modbus TCP (Industrial Devices)
-
In your worker's
init()method:init () { super.init() this.setInitFacs([ ['fac', 'svc-facs-modbus', '0', '0', {}, 0] ]) } // Access via: this.modbus_0.getClient(address, port)
HTTP/REST API
-
In your worker's
init()method:init () { super.init() this.setInitFacs([ ['fac', 'bfx-facs-http', '0', '0', {}, 0] ]) } // Access via: this.http_0
WebSocket
-
In your worker's
init()method:init () { super.init() this.setInitFacs([ ['fac', 'svc-facs-ws', '0', '0', {}, 0] ]) } // Access via: this.ws_0
B.8. Configuration Templates
base.thing.json.example
- Create or edit
config/base.thing.json.example:
{
"thing": {
"{devicetype}DefaultPort": 502,
"{devicetype}": {
"delay": 50,
"timeout": 30000,
"{brand}": {
"model": "default",
"retries": 3
}
}
}
}facs/modbus.config.json.example (if using Modbus)
- Create
config/facs/modbus.config.json.example:
{
"0": {
"connectionTimeout": 10000,
"requestTimeout": 5000,
"maxConnections": 10
}
}B.9. Testing Infrastructure
Level 5 tests inherit from Level 4 and add brand-specific cases:
Level 5 tests inherit from Level 4 and add brand-specific cases. For complete testing guidelines, see Testing & Linting Guidelines.
tests/utils.js
To extend the parent test utilities:
-
Create
tests/utils.js:'use strict' const utils = require('miningos-tpl-wrk-{devicetype}/tests/utils') const path = require('path') // Extend parent test infrastructure utils.SCHEMA_PATHS.push(path.join(__dirname, 'schema')) utils.TEST_PATHS.push(path.join(__dirname, 'cases')) module.exports = utils
tests/{brand}.spec.js
To create the main test file:
-
Create
tests/\{brand\}.spec.js:'use strict' const utils = require('./utils') const executors = require('./executors') // Run inherited tests plus brand-specific tests utils.runTests(executors)
tests/executors.js
To extend parent executors:
-
Create
tests/executors.js:'use strict' const parentExecutors = require('miningos-tpl-wrk-{devicetype}/tests/executors') // Extend or override parent executors module.exports = { ...parentExecutors, // Brand-specific executor example get{Brand}StatusExecutor: async (ctx) => { const result = await ctx.rpc.call('getThing', { id: ctx.thingId }) return result } }
tests/cases/{feature}.js
To add a test case:
-
Create
tests/cases/\{feature\}.js:'use strict' const path = require('path') const { getSchema } = require(path.join(process.cwd(), 'tests/utils')) const { getSnapExecutor, getStatsExecutor } = require('../executors') const defaults = getSchema() module.exports = () => ({ getSnap: { stages: [ { name: 'getSnap', executor: getSnapExecutor, validate: { type: 'schema', schema: { success: { type: 'boolean' }, stats: defaults.stats_validate.schema.stats, config: defaults.config_validate.schema.config } } } ] } })
B.10. Worker Entry Point
worker.js
-
Create the
worker.jsentry point:'use strict' require('bfx-svc-boot-js')({ wtype: 'wrk-{devicetype}-{brand}', conf: null })
For multimodel repositories, create separate entry points:
worker-pm5340.js
-
Create a model-specific entry point:
'use strict' require('bfx-svc-boot-js')({ wtype: 'wrk-powermeter-pm5340', conf: null })
B.11. Implementation Requirements Summary
Each Level 5 implementation must:
| Requirement | Method or Location | Purpose |
|---|---|---|
| Add protocol facility | init() with setInitFacs() | Enable device communication |
| Extend type definition | getThingType() | Return super.getThingType() + '-\{brand\}' |
| Implement connection | connectThing(thg) | Create device controller, assign to thg.ctrl |
| Implement data collection | collectThingSnap(thg) | Return \{ success, stats, config \} |
| Define device info | selectThingInfo(thg) | Return connection or device metadata |
| Add brand tags | getThingTags() | Return brand-specific tags |
B.12. Level 5 Checklist
- Repository created with correct naming (
miningos-wrk-\{devicetype\}-\{brand\}) - Worker file follows naming convention (
\{brand\}.rack.\{devicetype\}.wrk.js) -
package.jsondepends on Level 4 template -
package.jsonincludes required protocol facility -
package.jsonincludesmaintainersarray -
init()adds protocol facility viasetInitFacs() -
getThingType()returnssuper.getThingType() + '-\{brand\}' -
getThingTags()returns brand-specific tags -
connectThing()implemented with proper error handling -
collectThingSnap()returns\{ success, stats, config \} -
selectThingInfo()returns device metadata - Device library class created in
workers/lib/ - Test infrastructure extends Level 4 tests
- Mock device created for testing
- Configuration examples provided
- README.md documents brand-specific features
- Apache-2.0 license file included