--[[ platform.channels — Channel registry for cc-platform-core Centralizes modem channel allocation across all platform services. Supports controlled migration between legacy and target channels via three modes: "current", "target", and "dual". Usage: local Channels = require('platform.channels') -- Get the active channel for a service endpoint local ch = Channels.get('inventory.broadcast') -- => 4200 -- During migration, listen on both old and new channels Channels.setMode('dual') local both = Channels.getBoth('remoteturtle.command') -- => {100, 4210} -- Get all channels for a service local chs = Channels.forService('inventory') -- Register a custom channel Channels.register('myservice.data', 5000) ]] local Config = require('opus.config') local Channels = {} --- Default channel allocations. -- Each entry maps a logical channel name to current and target channel numbers. -- During migration, 'current' is the legacy channel and 'target' is the new one. -- When current == target, no migration is needed for that channel. Channels.registry = { -- RemoteTurtle service (legacy: 100-103, target: 4210-4213) ['remoteturtle.command'] = { current = 100, target = 4210 }, ['remoteturtle.response'] = { current = 101, target = 4211 }, ['remoteturtle.status'] = { current = 102, target = 4212 }, ['remoteturtle.pocket'] = { current = 103, target = 4213 }, -- Inventory Manager service (no migration needed — channels stay the same) ['inventory.broadcast'] = { current = 4200, target = 4200 }, ['inventory.order'] = { current = 4201, target = 4201 }, ['inventory.client'] = { current = 4202, target = 4202 }, ['inventory.craft_tx'] = { current = 4203, target = 4203 }, ['inventory.craft_rx'] = { current = 4204, target = 4204 }, ['inventory.system'] = { current = 4205, target = 4205 }, ['inventory.bridge'] = { current = 4206, target = 4206 }, -- Platform-level shared channels (reserved range: 4250-4259) ['platform.discovery'] = { current = 4250, target = 4250 }, ['platform.heartbeat'] = { current = 4251, target = 4251 }, } --- Channel mode controls which channel number is returned by get(). -- "current" — use legacy channel numbers (default, safe) -- "target" — use new channel numbers (post-migration) -- "dual" — listeners open both; senders use target Channels.mode = 'current' --- Load channel mode from persistent config if available. -- Called automatically on require; can be called again to reload. function Channels.loadConfig() local cfg = Config.load('platform', { channelMode = 'current' }) if cfg.channelMode then Channels.mode = cfg.channelMode end -- Allow per-channel overrides from config if cfg.channels then for name, entry in pairs(cfg.channels) do if type(entry) == 'number' then Channels.registry[name] = { current = entry, target = entry } elseif type(entry) == 'table' then Channels.registry[name] = entry end end end end --- Get the active channel number for a logical channel name. -- In "current" mode, returns the legacy channel. -- In "target" mode, returns the new channel. -- In "dual" mode, returns the target channel (for sending). -- @param name string Logical channel name (e.g. 'inventory.broadcast') -- @return number Channel number function Channels.get(name) local entry = Channels.registry[name] if not entry then error('Unknown channel: ' .. tostring(name), 2) end if Channels.mode == 'current' then return entry.current end return entry.target end --- Get both current and target channels for a logical name. -- Returns a deduplicated list. Used for opening channels in dual mode. -- @param name string Logical channel name -- @return table Array of channel numbers (1 or 2 entries) function Channels.getBoth(name) local entry = Channels.registry[name] if not entry then error('Unknown channel: ' .. tostring(name), 2) end if entry.current == entry.target then return { entry.current } end return { entry.current, entry.target } end --- Set the channel migration mode. -- @param mode string One of: "current", "target", "dual" function Channels.setMode(mode) assert(mode == 'current' or mode == 'target' or mode == 'dual', 'Invalid channel mode: ' .. tostring(mode)) Channels.mode = mode end --- Get all channels for a service prefix. -- @param prefix string Service prefix (e.g. 'inventory', 'remoteturtle') -- @return table Map of channel name → active channel number function Channels.forService(prefix) local result = {} local pattern = '^' .. prefix:gsub('%-', '%%-') .. '%.' for name, _ in pairs(Channels.registry) do if name:find(pattern) then result[name] = Channels.get(name) end end return result end --- Get all channels that should be opened for a service (respects dual mode). -- @param prefix string Service prefix -- @return table Array of unique channel numbers function Channels.openListForService(prefix) local seen = {} local result = {} local pattern = '^' .. prefix:gsub('%-', '%%-') .. '%.' for name, _ in pairs(Channels.registry) do if name:find(pattern) then for _, ch in ipairs(Channels.getBoth(name)) do if not seen[ch] then seen[ch] = true result[#result + 1] = ch end end end end table.sort(result) return result end --- Register a custom channel (for extensions or new services). -- @param name string Logical channel name -- @param channel number|table Channel number or {current=N, target=N} function Channels.register(name, channel) if type(channel) == 'number' then channel = { current = channel, target = channel } end assert(type(channel) == 'table' and channel.current and channel.target, 'Channel must be a number or {current=N, target=N}') Channels.registry[name] = channel end --- Check whether a modem channel belongs to a known service. -- @param ch number Channel number -- @return string|nil Channel name, or nil if unknown function Channels.identify(ch) for name, entry in pairs(Channels.registry) do if entry.current == ch or entry.target == ch then return name end end return nil end -- Auto-load config on require pcall(Channels.loadConfig) return Channels