Added random missing stuff so I won't go crazy

This commit is contained in:
Casey 2024-10-03 19:03:22 +03:00
parent e2e5d57e1f
commit 3cadd81a6b
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
12 changed files with 1002 additions and 3 deletions

4
.gitignore vendored
View File

@ -2,3 +2,7 @@ img2cpi
img2cpi.o img2cpi.o
wsvpn wsvpn
wsvpn.o wsvpn.o
cpi2png
cc-common.o
vim.state
__pycache__

View File

@ -64,9 +64,29 @@ decoders[1] = function(image, fp)
return true return true
end end
decoders[128] = function(image, fp)
image.w = read_varint(fp)
image.h = read_varint(fp)
image.extras.n_frames = read_varint(fp)
read_palette_full(image.palette, fp)
local success, err = read_pixeldata_v0(image, fp)
if not success then return false, err end
image.extras.frames = {}
for i = 1, image.extras.n_frames - 1 do
local frame = { palette = {}, lines = {} }
frame.w = image.w
frame.h = image.h
frame.scale = image.scale
read_palette_full(frame.palette)
local success, err = read_pixeldata_v0(frame, fp)
if not success then return false, err end
end
return true
end
local function parse(fp) local function parse(fp)
local res local res
local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {} } local image = { w = 0, h = 0, scale = 1.0, palette = {}, lines = {}, extras = {} }
local magic = fp.read(4) local magic = fp.read(4)
if magic == "CCPI" then if magic == "CCPI" then

82
mess/cc-concat.lua Normal file
View File

@ -0,0 +1,82 @@
local args = { ... }
local ccpi = require("ccpi")
local bit = {
band = function(a, b) return a & b end,
bor = function(a, b) return a | b end,
blshift = function(a, b) return a << b end,
brshift = function(a, b) return a >> b end,
}
local function write_varint(fp, value)
value = bit.band(value, 0xFFFFFFFF)
mask = 0xFFFFFF80
while true do
if bit.band(value, mask) == 0 then
fp:write(string.char(bit.band(value, 0xff)))
return
end
fp:write(string.char(bit.bor(0x80, bit.band(value, 0x7f))))
value = bit.brshift(value, 7)
end
end
local function write_palette(fp, pal)
for i = 1, 16 do
fp:write(string.char(
bit.brshift(pal[i], 16),
bit.band(bit.brshift(pal[i], 8), 0xff),
bit.band(pal[i], 0xff)
))
end
end
local function write_pixeldata_v0(img, fp)
for y = 1, img.h do
for x = 1, img.w do
fp:write(img.lines[y].s:sub(x, x))
local bg = tonumber(img.lines[y].bg:sub(x, x), 16)
local fg = tonumber(img.lines[y].fg:sub(x, x), 16)
fp:write(string.char(bit.bor(bg, bit.blshift(fg, 4))))
end
end
end
local fp_out = io.open(table.remove(args, 1), "wb")
fp_out:write("CPI" .. string.char(0x80))
local fp = io.open(table.remove(args, 1), "rb")
print(fp, fp.close)
local img, err = ccpi.parse({
read = function(sz) return fp:read(sz) end,
})
fp:close()
if err ~= nil then
printError(err)
return
end
write_varint(fp_out, img.w)
write_varint(fp_out, img.h)
write_varint(fp_out, #args)
write_palette(fp_out, img.palette)
write_pixeldata_v0(img, fp_out)
for i, arg in ipairs(args) do
print(arg)
local fp = io.open(arg, "rb")
img, err = ccpi.parse({
read = function(sz) return fp:read(sz) end,
})
fp:close()
if err ~= nil then
printError(err)
return
end
write_palette(fp_out, img.palette)
write_pixeldata_v0(img, fp_out)
end
fp_out:close()

65
mess/libcloudcatcher.py Normal file
View File

@ -0,0 +1,65 @@
from typing import Any, Literal
from websockets import connect
HexDigit = Literal[
"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "a", "b", "c", "d", "e", "f"
]
class Packet:
packet_id: int
side: Literal["client", "server"]
def __init_subclass__(cls, packet_id: int, side: Literal["client", "server"]) -> None:
cls.packet_id = packet_id
cls.side = side
class PacketServerPing(Packet, packet_id=2, side="server"):
pass
class PacketClientPong(Packet, packet_id=2, side="client"):
pass
class PacketServerCapabilities(Packet, packet_id=0, side="server"):
clients: int
capabilities: list[str]
class PacketServerComputer(Packet, packet_id=18, side="server"):
id: int
label: str
class PacketServerScreen(Packet, packet_id=16, side="server"):
cursorBlink: bool
width: int
height: int
cursorX: int
cursorY: int
curFore: HexDigit
curBack: HexDigit
text: list[str]
back: list[str]
fore: list[str]
palette: list[int]
class CCEvent:
name: str
args: list[Any]
class PacketClientEvent(Packet, packet_id=17, side="client"):
events: list[CCEvent]
class CCAction:
action: int
def __init_subclass__(cls, action: int) -> None:
cls.action = action
class CCActionFile(CCAction, action=0):
file: str
contents: str
checksum: int
class PacketServerFile(Packet, packet_id=34, side="server"):
actions: list[CCAction]

150
mess/package_video.py Normal file
View File

@ -0,0 +1,150 @@
# x-run: python3 % ~/downloads/moZtoMP7HAA.mp4 /tmp/video.cani
from typing import Literal
from dataclasses import dataclass
from struct import pack
from sys import argv
from subprocess import Popen, PIPE, run
from tqdm import tqdm
from tempfile import TemporaryDirectory
from glob import glob
from PIL import Image
from functools import lru_cache
PIX_BITS = [[1, 2], [4, 8], [16, 0]]
@dataclass
class VideoMetadata:
framerate: Literal[20, 10, 5] = 10
audio_channels: Literal[1, 2] = 2
sample_rate: Literal[12000, 24000, 48000] = 48000
screen_width: int = 164
screen_height: int = 81
@property
def audio_samples_per_frame(self) -> int:
return self.sample_rate // self.framerate
def serialize(self) -> bytes:
return bytes([ self.framerate, self.audio_channels ]) \
+ pack("<HHH", self.sample_rate, self.screen_width, self.screen_height)
@dataclass
class VideoFrame:
audio: list[bytes]
video: list[bytes]
palette: list[int]
def serialize(self) -> bytes:
return bytes.join(b"", self.audio) \
+ bytes.join(b"", self.video) \
+ bytes.join(b"", [ bytes.fromhex("%06x" % color) for color in self.palette ])
@lru_cache
def _brightness(palette: tuple, i: int) -> float:
r, g, b = palette[i * 3 : (i + 1) * 3]
return (r + g + b) / 768
@lru_cache
def _distance(palette: tuple, a: int, b: int) -> float:
r1, g1, b1 = palette[a * 3 : (a + 1) * 3]
r2, g2, b2 = palette[b * 3 : (b + 1) * 3]
rd, gd, bd = r1 - r2, g1 - g2, b1 - b2
return (rd * rd + gd * gd + bd * bd) / 1966608
@lru_cache
def _get_colors(imgdata, palette: tuple, x: int, y: int) -> tuple[int, int]:
brightest_i, brightest_l = 0, 0
darkest_i, darkest_l = 0, 768
for oy, line in enumerate(PIX_BITS):
for ox in range(len(line)):
pix = imgdata[x + ox, y + oy]
assert pix < 16, f"{pix} is too big at {x+ox}:{y+oy}"
brightness = _brightness(palette, pix)
if brightness > brightest_l:
brightest_l, brightest_i = brightness, pix
if brightness < darkest_l:
darkest_l, darkest_i = brightness, pix
return darkest_i, brightest_i
@lru_cache()
def _is_darker(palette: tuple, bg: int, fg: int, c: int) -> bool:
return _distance(palette, bg, c) < _distance(palette, fg, c)
def _get_block(imgdata, palette: tuple, x: int, y: int) -> tuple[int, int, int]:
dark_i, bri_i = _get_colors(imgdata, palette, x, y)
assert dark_i < 16, f"{dark_i} is too big"
assert bri_i < 16, f"{bri_i} is too big"
out: int = 0
for oy, line in enumerate(PIX_BITS):
for ox, bit in enumerate(line):
if not _is_darker(
palette, dark_i, bri_i, imgdata[x + ox, y + oy]
):
out |= bit
# bottom right pixel fix?
if not _is_darker(palette, dark_i, bri_i, imgdata[x + 1, y + 2]):
out ^= 31
dark_i, bri_i = bri_i, dark_i
return out, dark_i, bri_i
metadata = VideoMetadata(
framerate=20,
audio_channels=2,
sample_rate=24000,
screen_width=164,
screen_height=81
)
input_video = argv[1]
with TemporaryDirectory() as tmpdir:
run([
"ffmpeg",
"-i", input_video,
"-f", "s8",
"-ac", str(metadata.audio_channels),
"-ar", str(metadata.sample_rate),
f"{tmpdir}/audio.s8"
])
run([
"ffmpeg",
"-i", input_video,
"-an",
"-r", str(metadata.framerate),
"-vf", f"scale={metadata.screen_width * 2}:{metadata.screen_height * 3}",
f"{tmpdir}/video%06d.jpg"
])
with open(argv[2], "w") as fp_out, open(f"{tmpdir}/audio.s8", "rb") as fp_audio:
print(metadata.serialize().hex(), file=fp_out)
for i, frame_path in tqdm(enumerate(glob(f"{tmpdir}/video*.jpg"))):
with Image.open(frame_path) as img_in:
img_in = img_in.convert("P", palette=Image.Palette.ADAPTIVE, colors=16)
img_data = img_in.load()
img_palette = tuple(img_in.getpalette()) # type: ignore
audio_samples = fp_audio.read(metadata.audio_samples_per_frame * metadata.audio_channels)
frame = VideoFrame(
[
audio_samples[i::metadata.audio_channels]
for i in range(metadata.audio_channels)
],
[],
[
(r << 16) | (g << 8) | b
for r, g, b
in zip(img_palette[0::3], img_palette[1::3], img_palette[2::3]) # type: ignore
])
for y in range(0, img_in.height - 2, 3):
line = bytearray()
for x in range(0, img_in.width - 1, 2):
ch, bg, fg = _get_block(img_data, img_palette, x, y)
line.extend([(ch + 0x80) & 0xFF, fg << 4 | bg])
frame.video.append(line)
print(frame.serialize().hex(), file=fp_out)

36
mess/pipez.lua Normal file
View File

@ -0,0 +1,36 @@
local config = {
{ filter = ".*charged.*", from = "ae2:charger", to = "create:basin" },
{ filter = "!.*charged.*", from = "create:basin", to = "ae2:charger", limit = 1 }
}
local function is_matching(pattern, value)
if pattern:sub(1, 1) == "!" then
return not is_matching(pattern:sub(2))
end
return value:match(pattern) ~= nil
end
local items = {}
parallel.waitForAll(function() -- Inventory listener
while true do
local new_items = {}
peripheral.find("inventory", function(inv)
local inv_items = peripheral.call(inv, "list")
for slot, item in pairs(inv_items) do
new_items[string.format("%s#%d", inv, slot)] = item
end
end)
items = new_items
os.sleep(0)
end
end, function() -- Executor
while true do
for i, rule in ipairs(config) do
end
os.sleep(0)
end
end)

21
mess/ramfsd.lua Normal file
View File

@ -0,0 +1,21 @@
peripheral.find("modem", function(name)
rednet.open(name)
print("Opened modem " .. name .. " for rednet connections")
end)
print("Hostname: " .. os.getComputerLabel())
print("ID: " .. os.getComputerID())
rednet.host("ramfs", os.getComputerLabel())
parallel.waitForAll(function()
while true do
local ev = { os.pullEvent() }
if ev[1] == "ramfs:shutdown" then
break
end
print(table.unpack(ev))
end
end, function() -- Shutdown routine
os.pullEvent("ramfs:shutdown")
end)

84
mess/restock.json Normal file
View File

@ -0,0 +1,84 @@
{
"junk": [
"metalbarrels:gold_tile_4"
],
"storage": [
"create:item_vault_2",
"create:item_vault_1"
],
"pull": [
"minecraft:barrel_1",
"NOT IMPLEMENTED"
],
"push": [
{ "name": "*_nugget", "to": "metalbarrels:gold_tile_8" },
{ "name": "*_ingot", "to": "metalbarrels:gold_tile_8" },
{ "name": "minecraft:*coal", "to": "metalbarrels:gold_tile_9" },
{ "name": "NOT IMPLEMENTED", "to": "NOT IMPLEMENTED" }
],
"stock": {
"metalbarrels:gold_tile_13#1": { "name": "minecraft:spruce_log", "count": 64 },
"metalbarrels:gold_tile_13#2": { "name": "minecraft:spruce_log", "count": 64 },
"metalbarrels:gold_tile_13#3": { "name": "minecraft:spruce_planks", "count": 64 },
"metalbarrels:gold_tile_13#10": { "name": "minecraft:oak_log", "count": 64 },
"metalbarrels:gold_tile_13#11": { "name": "minecraft:oak_log", "count": 64 },
"metalbarrels:gold_tile_13#12": { "name": "minecraft:oak_planks", "count": 64 },
"metalbarrels:gold_tile_13#19": { "name": "minecraft:dark_oak_log", "count": 64 },
"metalbarrels:gold_tile_13#20": { "name": "minecraft:dark_oak_log", "count": 64 },
"metalbarrels:gold_tile_13#21": { "name": "minecraft:dark_oak_planks", "count": 64 },
"metalbarrels:gold_tile_13#4": { "name": "minecraft:cobblestone", "count": 64 },
"metalbarrels:gold_tile_13#5": { "name": "minecraft:cobblestone", "count": 64 },
"metalbarrels:gold_tile_13#6": { "name": "minecraft:stone", "count": 64 },
"metalbarrels:gold_tile_13#13": { "name": "minecraft:cobbled_deepslate", "count": 64 },
"metalbarrels:gold_tile_13#14": { "name": "minecraft:cobbled_deepslate", "count": 64 },
"metalbarrels:gold_tile_13#15": { "name": "minecraft:deepslate", "count": 64 },
"metalbarrels:gold_tile_13#7": { "name": "minecraft:dirt", "count": 64 },
"metalbarrels:gold_tile_13#16": { "name": "minecraft:sand", "count": 64 },
"metalbarrels:gold_tile_13#25": { "name": "minecraft:gravel", "count": 64 },
"metalbarrels:gold_tile_13#8": { "name": "minecraft:netherrack", "count": 64 },
"metalbarrels:gold_tile_13#17": { "name": "minecraft:soul_sand", "count": 64 },
"metalbarrels:gold_tile_13#22": { "name": "minecraft:andesite", "count": 64 },
"metalbarrels:gold_tile_13#23": { "name": "minecraft:diorite", "count": 64 },
"metalbarrels:gold_tile_13#24": { "name": "minecraft:granite", "count": 64 },
"metalbarrels:gold_tile_13#31": { "name": "minecraft:tuff", "count": 64 },
"metalbarrels:gold_tile_13#32": { "name": "expcaves:sediment_stone", "count": 64 },
"metalbarrels:gold_tile_13#33": { "name": "expcaves:lavastone", "count": 64 },
"metalbarrels:gold_tile_13#34": { "name": "expcaves:dirtstone", "count": 64 },
"metalbarrels:gold_tile_13#42": { "name": "minecraft:calcite", "count": 64 },
"metalbarrels:gold_tile_13#43": { "name": "minecraft:clay_ball", "count": 64 },
"metalbarrels:gold_tile_13#40": { "name": "create:limestone", "count": 64 },
"metalbarrels:gold_tile_13#49": { "name": "minecraft:stone_bricks", "count": 64 },
"metalbarrels:gold_tile_13#50": { "name": "minecraft:cracked_stone_bricks", "count": 64 },
"metalbarrels:gold_tile_13#51": { "name": "minecraft:moss_block", "count": 64 },
"metalbarrels:gold_tile_13#41": { "name": "forbidden_arcanus:darkstone", "count": 64 },
"metalbarrels:gold_tile_13#73": { "name": "minecraft:apple", "count": 64 },
"metalbarrels:gold_tile_13#74": { "name": "minecraft:kelp", "count": 64 },
"metalbarrels:gold_tile_13#75": { "name": "minecraft:oak_sapling", "count": 64 },
"metalbarrels:gold_tile_13#76": { "name": "minecraft:spruce_sapling", "count": 64 },
"metalbarrels:gold_tile_13#77": { "name": "minecraft:dark_oak_sapling", "count": 64 },
"metalbarrels:gold_tile_13#81": { "name": "minecraft:stick", "count": 64 },
"metalbarrels:gold_tile_13#26": { "name": "minecraft:nether_bricks", "count": 64 },
"metalbarrels:gold_tile_13#35": { "name": "minecraft:magma_block", "count": 64 },
"metalbarrels:gold_tile_6#1": { "name": "kubejs:kinetic_mechanism", "count": 64 },
"metalbarrels:gold_tile_6#2": { "name": "kubejs:kinetic_mechanism", "count": 64 },
"metalbarrels:gold_tile_6#10": { "name": "create:andesite_alloy", "count": 64 },
"metalbarrels:gold_tile_6#11": { "name": "create:andesite_alloy", "count": 64 },
"metalbarrels:gold_tile_6#12": { "name": "thermal:cured_rubber", "count": 64 },
"metalbarrels:gold_tile_6#19": { "name": "thermal:silver_coin", "count": 64 },
"metalbarrels:gold_tile_6#20": { "name": "thermal:silver_coin", "count": 64 },
"metalbarrels:gold_tile_6#21": { "name": "thermal:silver_coin", "count": 64 },
"metalbarrels:gold_tile_7#1": { "name": "minecraft:lapis_lazuli", "count": 64 },
"metalbarrels:gold_tile_7#2": { "name": "minecraft:redstone", "count": 64 },
"metalbarrels:gold_tile_7#3": { "name": "minecraft:coal", "count": 15 },
"metalbarrels:gold_tile_7#4": { "name": "thermal:apatite", "count": 64 },
"metalbarrels:gold_tile_7#5": { "name": "thermal:sulfur", "count": 64 },
"metalbarrels:gold_tile_7#6": { "name": "thermal:niter", "count": 64 },
"metalbarrels:gold_tile_7#7": { "name": "thermal:cinnabar", "count": 64 },
"metalbarrels:gold_tile_7#10": { "name": "ae2:certus_quartz_dust", "count": 64 },
"metalbarrels:gold_tile_7#11": { "name": "ae2:certus_crystal_seed", "count": 64 },
"metalbarrels:gold_tile_7#12": { "name": "forbidden_arcanus:xpetrified_orb", "count": 16 }
}
}

434
mess/restock.lua Normal file
View File

@ -0,0 +1,434 @@
local function draw_bar(y, c1, c2, p, fmt, ...)
local str = string.format(fmt, ...)
local tw = term.getSize()
local w1 = math.ceil(p * tw)
local w2 = tw - w1
local old_bg = term.getBackgroundColor()
term.setCursorPos(1, y)
term.setBackgroundColor(c1)
term.write(str:sub(1, w1))
local rem = w1 - #str
if rem > 0 then
term.write(string.rep(" ", rem))
end
term.setBackgroundColor(c2)
term.write(str:sub(w1 + 1, w1 + w2))
rem = math.min(tw - #str, w2)
if rem > 0 then
term.write(string.rep(" ", rem))
end
term.setBackgroundColor(old_bg)
end
function table.deepcopy(obj)
if type(obj) ~= 'table' then return obj end
local res = {}
for k, v in pairs(obj) do res[table.deepcopy(k)] = table.deepcopy(v) end
return res
end
function spairs(t, order)
-- collect the tbl_keys
local tbl_keys = {}
for k in pairs(t) do tbl_keys[#tbl_keys+1] = k end
-- if order function given, sort by it by passing the table and tbl_keys a, b,
-- otherwise just sort the tbl_keys
if order then
table.sort(tbl_keys, function(a,b) return order(t, a, b) end)
else
table.sort(tbl_keys)
end
-- return the iterator function
local i = 0
return function()
i = i + 1
if tbl_keys[i] then
return tbl_keys[i], t[tbl_keys[i]]
end
end
end
function string.split(inputstr, sep)
sep = sep or "%s"
local t = {}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return table.unpack(t)
end
function map(tbl, fn)
local out = {}
for k, v in pairs(tbl) do
out[k] = fn(k, v, tbl)
end
return out
end
function filter(tbl, fil)
local out = {}
for k, v in pairs(tbl) do
if fil(k, v, tbl) then
out[k] = v
end
end
return out
end
function pop(tbl)
local out = { k = nil, v = nil }
for k, v in pairs(tbl) do
out.k = k
out.v = v
table.remove(tbl, k)
break
end
return out.k, out.v
end
function groupby(tbl, fn)
local out = {}
for k, v in pairs(tbl) do
local group = fn(k, v, tbl)
out[group] = out[group] or {}
table.insert(out[group], { k = k, v = v })
end
return out
end
function reduce(tbl, operator, initial)
local accumulator = initial
for k, v in pairs(tbl) do
accumulator = operator(k, v, accumulator, tbl)
end
return accumulator
end
function pipe(input)
local function chain(fn)
return function(self, ...)
fn(self, ...)
return self
end
end
return {
_value = table.deepcopy(input),
groupby = chain(function(self, fn) self._value = groupby(self._value, fn) end),
filter = chain(function(self, fn) self._value = filter(self._value, fn) end),
map = chain(function(self, fn) self._value = map(self._value, fn) end),
reduce = chain(function(self, operator, initial) self._value = reduce(self._value, operator, initial) end),
sort = chain(function(self, key)
local out = {}
for k, v in spairs(self._value, key) do
table.insert(out, { k = k, v = v })
end
self._value = out
end),
get = function(self) return self._value end
}
end
local keyboard = {
"qwertyuiop",
"asdfghjkl\x1b",
"zxcvbnm \xd7",
}
local config = { stock = {}, trash = {}, take = {}, storage = {} }
local item_state = { stock = {}, storage = {}, junk = {} }
local storage_sizes = { stock = {}, storage = {}, junk = {} }
local sort_mode, sort_inverse = "count", false
local search_open, search_query = false, ""
local mon = peripheral.find("monitor")
mon.setTextScale(0.5)
mon.clear()
mon.setPaletteColor(colors.blue, 0x131326) -- odd lines
mon.setPaletteColor(colors.purple, 0x261326) -- even lines
mon.setPaletteColor(colors.magenta, 0xaa55ff) -- search query
mon.setPaletteColor(colors.cyan, 0x55aaff) -- keyboard
mon.setPaletteColor(colors.brown, 0x262626) -- keyboard bg
parallel.waitForAll(function()
while true do
local fp = assert(io.open("/restock.json", "r"))
config = textutils.unserializeJSON(fp:read("a"))
fp:close()
os.sleep(1)
end
end, function()
while true do
local new_state = {}
local new_sizes = {}
map(config.storage, function(i, inv)
new_sizes[inv] = peripheral.call(inv, "size")
for slot, item in pairs(peripheral.call(inv, "list")) do
new_state[string.format("%s#%d", inv, slot)] = item
end
end)
item_state.storage = new_state
storage_sizes.storage = new_sizes
os.sleep(0)
end
end, function()
while true do
local new_state = {}
local new_sizes = {}
map(
groupby(config.stock, function(location)
local storage = string.split(location, "#")
return storage
end
), function(inv)
new_sizes[inv] = peripheral.call(inv, "size")
for slot, item in pairs(peripheral.call(inv, "list")) do
new_state[string.format("%s#%d", inv, slot)] = item
end
end)
item_state.stock = new_state
storage_sizes.stock = new_sizes
os.sleep(0)
end
end, function() -- IMPORT
while true do
map(
groupby(config.stock, function(location) return ({ string.split(location, "#") })[1] end),
function(stock_inv)
local size = storage_sizes.stock[stock_inv] or 0
for i = 1, size do
local slot = string.format("%s#%d", stock_inv, i)
local slot_stocked, slot_expected = item_state.stock[slot], config.stock[slot]
if (slot_stocked and slot_expected and slot_stocked.name ~= slot_expected.name) or (slot_stocked ~= nil and slot_expected == nil) then
for _, output in ipairs(config.storage) do
if peripheral.call(stock_inv, "pushItems", output, i) ~= 0 then
print("MOVE", slot, slot_stocked.name)
break
end
end
end
end
end)
os.sleep(0)
end
end, function() -- STOCK
while true do
local new_junk = {}
local item_locations = groupby(item_state.storage, function(location, item)
new_junk[item.name] = (new_junk[item.name] or 0) + item.count
return item.name
end)
local tw, th = term.getSize()
map(config.stock, function(destination, item)
local dst_container, dst_slot = string.split(destination, "#")
new_junk[item.name] = nil
if item_locations[item.name] == nil then return end
local dst_item = item_state.stock[destination]
local dst_count = dst_item and dst_item.count or 0
if dst_count >= item.count then return end
local missing = item.count - dst_count
local _, take_from = pop(filter(item_locations[item.name], function(_, src_item)
local src_container, src_slot = string.split(src_item.k, "#")
return src_container ~= dst_container
end))
if not take_from then return end
local src_container, src_slot = string.split(take_from.k, "#")
local t = os.clock()
local out = peripheral.call(dst_container, "pullItems", src_container, tonumber(src_slot), missing, tonumber(dst_slot))
print(string.format("PUT %s*%d/%d to %s", item.name, out, missing, destination))
-- print(string.format("%s -> %s (%s*%d) took %.2f", take_from.k, destination, item.name, item.count, os.clock() - t))
end)
item_state.junk = new_junk
os.sleep(0)
end
end, function() -- JUNK
while true do
local y = 2
for name, count in pairs(item_state.junk) do
map(filter(item_state.storage, function(location, item)
return item.name == name
end), function(location, item)
local junk_container, junk_slot = string.split(location, "#")
for _, junk_output in ipairs(config.junk) do
if peripheral.call(junk_container, "pushItems", junk_output, tonumber(junk_slot)) ~= 0 then
print("JUNK OUT", item.name, "->", junk_container)
break
end
end
end)
y = y + 1
end
os.sleep(0)
end
end, function() -- USER INPUT
while true do
local ev = { os.pullEvent() }
if ev[1] == "char" and search_open then
search_query = search_query .. ev[2]
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
elseif ev[1] == "key" then
if ev[2] == keys.enter then
search_open = not search_open
elseif ev[2] == keys.backspace and search_open then
search_query = search_query:sub(1, #search_query - 1)
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
end
elseif ev[1] == "monitor_touch" then
local tw, th = mon.getSize()
local _, _, x, y = table.unpack(ev)
if y == 3 or y == th then
local new_mode
if x >= 1 and x <= 8 then new_mode = "count"
elseif x >= 10 and x <= (tw - 1) then new_mode = "name"
elseif x == tw then
search_open = not search_open
end
if new_mode ~= nil and new_mode == sort_mode then
sort_inverse = not sort_inverse
elseif new_mode ~= nil then
sort_mode = new_mode
sort_inverse = false
end
elseif search_open and x >= (tw - 14) and x < (tw - 4) and y > (th - 20) and y <= (th - 17) then
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
mon.setCursorPos(tw - 14, th - 20)
mon.write("> " .. search_query)
for i, line in ipairs(keyboard) do
mon.setCursorPos(tw - 14, th - 20 + i)
mon.setBackgroundColor(colors.brown)
mon.setTextColor(colors.cyan)
mon.write(line)
end
local char = keyboard[20 - th + y]:sub(15 - tw + x, 15 - tw + x)
if char >= "a" and char <= "z" then
os.queueEvent("key", keys[char], false)
os.queueEvent("char", char)
os.queueEvent("key_up", keys[char])
else
-- 27 backspace (ev=259)
-- 215 enter (ev=257)
local code = nil
if char == "\x1b" then code = keys.backspace
elseif char == "\xd7" then code = keys.enter
end
if code then
os.queueEvent("key", code, false)
os.queueEvent("key_up", code)
end
end
end
end
end
end, function() -- STATUS
while true do
local back_term = term.redirect(mon)
local tw, th = term.getSize()
term.setBackgroundColor(colors.black)
term.setTextColor(colors.white)
term.clear()
local usage = pipe(config.storage)
:map(function(_, storage)
local used, total = 0, storage_sizes.storage[storage] or 0
for slot = 1, total do
if item_state.storage[string.format("%s#%d", storage, slot)] then
used = used + 1
end
end
return { used = used, total = total }
end)
:reduce(function(_, info, acc)
return { used = info.used + acc.used, total = info.total + acc.total }
end, { used = 0, total = 0 }):get()
draw_bar(1, colors.lightGray, colors.gray, usage.used / usage.total, "Storage utilization: %7.3f%%", 100 * usage.used / usage.total)
term.setCursorPos(1, 3)
term.setBackgroundColor(colors.gray)
term.clearLine()
local infoline = string.format("Count %s Name %s",
(sort_mode == "count" and (sort_inverse and "\x1e" or "\x1f") or " "),
(sort_mode == "name" and (sort_inverse and "\x1e" or "\x1f") or " "))
local fg = sort_mode == "count" and "55555551f88888881" or "88888881f55555551"
term.blit(infoline, fg, "77777777777777777")
term.setCursorPos(tw, 3)
term.write("\x0c")
term.setCursorPos(1, th)
term.clearLine()
term.blit(infoline, fg, "77777777777777777")
term.setCursorPos(tw, th)
term.write("\x0c")
pipe(item_state.storage)
:groupby(function(slot, item) return item.name end)
:filter(function(name, slots)
if not search_open then return true end
return string.match(name, search_query)
end)
:map(function(name, slots) return reduce(slots, function(_, slot, acc) return slot.v.count + acc end, 0) end)
:sort(function(t, a, b)
local swap = false
if sort_mode == "count" then swap = t[a] > t[b] end
if sort_mode == "name" then swap = a > b end
return swap ~= sort_inverse
end)
:map(function(i, kv)
if i >= (th - 4) then return end
term.setCursorPos(1, 3 + i)
term.setBackgroundColor((i % 2) == 0 and colors.blue or colors.purple)
term.clearLine()
term.write(string.format("%8d %s", kv.v, kv.k))
end)
if search_open then
mon.setBackgroundColor(colors.gray)
mon.setTextColor(colors.magenta)
term.setCursorPos(tw - 14, th - 20)
term.write("> " .. search_query)
for i, line in ipairs(keyboard) do
term.setCursorPos(tw - 14, th - 20 + i)
term.setBackgroundColor(colors.brown)
term.setTextColor(colors.cyan)
term.write(line)
end
end
term.redirect(back_term)
os.sleep(1)
end
end)

11
mess/stream_video.py Normal file
View File

@ -0,0 +1,11 @@
# x-run: sanic stream_video:app
from sanic import Request, Sanic, Websocket
app = Sanic("CCVideoStreamer")
@app.websocket("/stream")
async def ws_stream(req: Request, ws: Websocket):
with open("./video.cani", "r") as fp:
for line in fp:
await ws.send(bytes.fromhex(line.strip()))
await ws.close()

83
mess/streamplay.lua Normal file
View File

@ -0,0 +1,83 @@
local url = "wss://kc.is.being.pet/ws"
local screen = peripheral.wrap("monitor_1")
local speakers = {
l = peripheral.wrap("speaker_1"),
r = peripheral.wrap("speaker_0"),
}
local ws = assert(http.websocket(url))
local function parse_u16(data)
local v = table.remove(data, 1)
v = bit.bor(v, bit.blshift(table.remove(data, 1), 8))
return v, data
end
local metadata_pkt = ws.receive()
metadata_pkt = { string.byte(metadata_pkt, 1, #metadata_pkt) }
local framerate = table.remove(metadata_pkt, 1)
local n_channels = table.remove(metadata_pkt, 1)
local sample_rate, data = parse_u16(metadata_pkt)
local screen_w, data = parse_u16(data)
local screen_h, data = parse_u16(data)
local samples_per_frame = math.floor(sample_rate / framerate)
print(string.format("FPS: %d", framerate))
print(string.format("Audio: %d channels, %d Hz", n_channels, sample_rate))
print(string.format("Video: %dx%d", screen_w, screen_h))
print(string.format("S/F: %d", samples_per_frame))
local function decode_s8(buf)
local buffer = {}
for i = 1, #buf do
local v = buf[i]
if bit32.band(v, 0x80) then
v = bit32.bxor(v, 0x7F) - 128
end
table.insert(buffer, v)
table.insert(buffer, v)
end
return buffer
end
local function mkSpeakerCoro(p, b)
return function()
while not p.playAudio(b) do
os.pullEvent("speaker_audio_empty")
end
end
end
while true do
local video_pkt = ws.receive()
local channels = { l = {}, r = {} }
local offset = 1
channels.l = decode_s8({ string.byte(video_pkt, offset, offset + samples_per_frame - 1) })
offset = offset + samples_per_frame
channels.r = decode_s8({ string.byte(video_pkt, offset, offset + samples_per_frame - 1) })
offset = offset + samples_per_frame
for y = 1, screen_h do
local tx, bg, fg = {}, {}, {}
for x = 1, screen_w do
table.insert(tx, string.sub(video_pkt, offset, offset))
local color = string.byte(video_pkt, offset + 1, offset + 1)
table.insert(bg, string.format("%x", bit.brshift(color, 4)))
table.insert(fg, string.format("%x", bit.band(color, 0xF)))
offset = offset + 2
end
screen.setCursorPos(1, y)
screen.blit(table.concat(tx), table.concat(bg), table.concat(fg))
end
for i = 1, 16 do
local r, g, b = string.byte(video_pkt, offset, offset + 3)
screen.setPaletteColor(bit.blshift(1, i - 1), bit.bor(bit.bor(bit.blshift(r, 16), bit.blshift(g, 8)), b))
offset = offset + 3
end
parallel.waitForAll(
mkSpeakerCoro(speakers.l, channels.l),
mkSpeakerCoro(speakers.r, channels.r))
end

View File

@ -26,9 +26,12 @@ local function send_chunk_subscribe_request(chunk)
ws.send(string.char(0x14, bit.band(chunk, 0xFF), bit.band(bit.brshift(chunk, 8), 0xFF)), true) ws.send(string.char(0x14, bit.band(chunk, 0xFF), bit.band(bit.brshift(chunk, 8), 0xFF)), true)
end end
local shutdown = false
parallel.waitForAll(function() parallel.waitForAll(function()
while true do while not shutdown do
local data, is_binary = ws.receive() local data, is_binary = ws.receive()
if data == nil then return end
data = { string.byte(data, 1, #data) } data = { string.byte(data, 1, #data) }
local opcode = table.remove(data, 1) local opcode = table.remove(data, 1)
if opcode == 0x00 then -- hello if opcode == 0x00 then -- hello
@ -63,20 +66,25 @@ function()
mon.setTextScale(0.5) mon.setTextScale(0.5)
mon.clear() mon.clear()
local tw, th = term.getSize() local tw, th = term.getSize()
term.clear()
send_chunk_request(chunk_id) send_chunk_request(chunk_id)
send_chunk_subscribe_request(chunk_id) send_chunk_subscribe_request(chunk_id)
term.setCursorPos(1, 3) term.setCursorPos(1, 3)
print("Showing chunk " .. chunk_id) print("Showing chunk " .. chunk_id)
print(string.format("Screen: %dx%d", mon.getSize())) print(string.format("Screen: %dx%d", mon.getSize()))
while true do while not shutdown do
local ev = { os.pullEvent() } local ev = { os.pullEvent() }
if ev[1] == "char" and ev[2] == "q" then if ev[1] == "char" and ev[2] == "q" then
shutdown = true
ws.close()
break break
elseif ev[1] == "obcb:hello" then elseif ev[1] == "obcb:hello" then
term.setCursorPos(1, 1) term.setCursorPos(1, 1)
term.clearLine()
print(string.format("Hello: obcb v%d.%d", ev[2], ev[3])) print(string.format("Hello: obcb v%d.%d", ev[2], ev[3]))
elseif ev[1] == "obcb:clients" then elseif ev[1] == "obcb:clients" then
term.setCursorPos(1, 2) term.setCursorPos(1, 2)
term.clearLine()
print("Clients: " .. ev[2]) print("Clients: " .. ev[2])
elseif ev[1] == "obcb:update" then elseif ev[1] == "obcb:update" then
for y = 1, 81 do for y = 1, 81 do
@ -96,4 +104,5 @@ function()
end end
end) end)
shutdown = true
ws.close() ws.close()