Files
cc-platform-core/apis/service.lua
2026-03-26 15:00:49 -04:00

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