154 lines
4.8 KiB
Lua
154 lines
4.8 KiB
Lua
--[[
|
|
platform.service — Service lifecycle helpers for cc-platform-core
|
|
|
|
Provides a lightweight service context for Opus-based CC services.
|
|
Handles modem discovery, channel management, config loading, and
|
|
message sending using platform conventions.
|
|
|
|
Usage:
|
|
local Service = require('platform.service')
|
|
|
|
local ctx = Service.create({
|
|
name = 'inventory-manager',
|
|
defaults = { scanInterval = 120 },
|
|
})
|
|
|
|
Service.openChannels(ctx)
|
|
|
|
-- Send on a named channel
|
|
Service.send(ctx, 'inventory.broadcast', 'state', { items = {...} })
|
|
|
|
-- Broadcast on both legacy and target channels during migration
|
|
Service.broadcast(ctx, 'inventory.broadcast', 'state', { items = {...} })
|
|
]]
|
|
|
|
local Config = require('opus.config')
|
|
local Protocol = require('platform.protocol')
|
|
local Channels = require('platform.channels')
|
|
|
|
local peripheral = _G.peripheral
|
|
|
|
local Service = {}
|
|
|
|
--- Create a new service context.
|
|
-- Initializes the protocol identity, loads config, discovers modem.
|
|
-- @param opts table Options:
|
|
-- name string (required) Service identifier
|
|
-- defaults table Default config values
|
|
-- modem boolean Set to false to skip modem discovery
|
|
-- channelPrefix string Override for channel name prefix (defaults to name)
|
|
-- @return table Service context
|
|
function Service.create(opts)
|
|
assert(opts and opts.name, 'Service name required')
|
|
|
|
Protocol.setServiceId(opts.name)
|
|
|
|
local prefix = opts.channelPrefix or opts.name
|
|
local ctx = {
|
|
name = opts.name,
|
|
config = Config.load(opts.name, opts.defaults or {}),
|
|
prefix = prefix,
|
|
channels = Channels.forService(prefix),
|
|
modem = nil,
|
|
}
|
|
|
|
-- Discover modem
|
|
if opts.modem ~= false then
|
|
-- Prefer Opus device registry
|
|
if _G.device and _G.device.wireless_modem then
|
|
ctx.modem = _G.device.wireless_modem
|
|
else
|
|
-- Fallback: scan peripherals (wired or wireless)
|
|
for _, name in ipairs(peripheral.getNames()) do
|
|
if peripheral.getType(name) == 'modem' then
|
|
ctx.modem = peripheral.wrap(name)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return ctx
|
|
end
|
|
|
|
--- Open all registered channels for this service on its modem.
|
|
-- Respects dual-mode: opens both current and target channels when appropriate.
|
|
-- @param ctx table Service context from Service.create()
|
|
function Service.openChannels(ctx)
|
|
if not ctx.modem then return end
|
|
|
|
local channelList = Channels.openListForService(ctx.prefix)
|
|
for _, ch in ipairs(channelList) do
|
|
ctx.modem.open(ch)
|
|
end
|
|
end
|
|
|
|
--- Close all registered channels for this service.
|
|
-- @param ctx table Service context
|
|
function Service.closeChannels(ctx)
|
|
if not ctx.modem then return end
|
|
|
|
local channelList = Channels.openListForService(ctx.prefix)
|
|
for _, ch in ipairs(channelList) do
|
|
ctx.modem.close(ch)
|
|
end
|
|
end
|
|
|
|
--- Send a message on a single named channel.
|
|
-- Uses Protocol.message() to construct a well-formed envelope.
|
|
-- @param ctx table Service context
|
|
-- @param channelName string Logical channel name (e.g. 'inventory.broadcast')
|
|
-- @param msgType string Message type discriminator
|
|
-- @param data table|nil Payload fields
|
|
-- @param opts table|nil Protocol.message options (id, src, noMeta)
|
|
-- @return boolean true if sent successfully
|
|
function Service.send(ctx, channelName, msgType, data, opts)
|
|
if not ctx.modem then return false end
|
|
local ch = Channels.get(channelName)
|
|
local msg = Protocol.message(msgType, data, opts)
|
|
ctx.modem.transmit(ch, ch, msg)
|
|
return true
|
|
end
|
|
|
|
--- Broadcast a message, sending on both channels during dual-mode migration.
|
|
-- In current or target mode, behaves identically to send().
|
|
-- @param ctx table Service context
|
|
-- @param channelName string Logical channel name
|
|
-- @param msgType string Message type discriminator
|
|
-- @param data table|nil Payload fields
|
|
-- @param opts table|nil Protocol.message options
|
|
-- @return boolean true if sent on at least one channel
|
|
function Service.broadcast(ctx, channelName, msgType, data, opts)
|
|
if not ctx.modem then return false end
|
|
|
|
local msg = Protocol.message(msgType, data, opts)
|
|
|
|
if Channels.mode == 'dual' then
|
|
local sent = false
|
|
for _, ch in ipairs(Channels.getBoth(channelName)) do
|
|
ctx.modem.transmit(ch, ch, msg)
|
|
sent = true
|
|
end
|
|
return sent
|
|
else
|
|
local ch = Channels.get(channelName)
|
|
ctx.modem.transmit(ch, ch, msg)
|
|
return true
|
|
end
|
|
end
|
|
|
|
--- Save the service config back to persistent storage.
|
|
-- @param ctx table Service context
|
|
function Service.saveConfig(ctx)
|
|
Config.update(ctx.name, ctx.config)
|
|
end
|
|
|
|
--- Reload the service config from persistent storage.
|
|
-- @param ctx table Service context
|
|
-- @param defaults table|nil Default values
|
|
function Service.reloadConfig(ctx, defaults)
|
|
ctx.config = Config.load(ctx.name, defaults or ctx.config)
|
|
end
|
|
|
|
return Service
|