1
0
Fork 0

signage: bring in from external repo

This is b28e6f07aa48f1e2f01eb37bffa180f97a7b03bd from
https://code.hackerspace.pl/q3k/love2d-signage/. We only keep code
commited by inf and q3k, and we're both now licensing this code under
the ISC license, as per COPYING in the root of hscloud.

Change-Id: Ibeee2e6923605e4b1a17a1d295867c056863ef59
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1335
Reviewed-by: informatic <informatic@hackerspace.pl>
Reviewed-by: q3k <q3k@hackerspace.pl>
master
q3k 2022-07-07 14:24:53 +02:00 committed by q3k
parent 9c5d866105
commit 18c1a263cf
35 changed files with 3162 additions and 48 deletions

View File

@ -1,6 +1,6 @@
# RPi4 as TV kiosk.
{ config, pkgs, ... }:
{ config, pkgs, workspace, ... }:
let
nixos-hardware = builtins.fetchGit {
@ -32,12 +32,6 @@ in {
networking.domain = "waw.hackerspace.pl";
time.timeZone = "Europe/Warsaw";
nixpkgs.overlays = [
(self: super: {
signage = self.callPackage ./signage.nix {};
})
];
sound.enable = true;
services.pipewire = {
enable = true;
@ -107,7 +101,7 @@ in {
'';
in pkgs.writeScriptBin "signage-wrapped" ''
#!/usr/bin/env bash
SIGNAGE_CONFIG=${config} ${signage}/bin/signage
SIGNAGE_CONFIG=${config} ${workspace.hswaw.signage.prod}/bin/signage
'')
firefox foot wayvnc
vim rxvt-unicode-unwrapped.terminfo

View File

@ -1,40 +0,0 @@
{ love, curl, fetchFromGitHub, fetchgit, stdenv, lib, ... }:
let
love12 = (love.overrideAttrs (oa: {
version = "12.0-dev";
src = fetchFromGitHub {
owner = "love2d";
repo = "love";
rev = "d586d1847446f5212d5f7e9efb94e50fcfba7d77";
sha256 = "sha256-gTpVtyqXV6/GsezuCpooaY+x5tPfOF9p1b83v4kKR4E=";
};
makeFlags = [
"CPPFLAGS=-DHTTPS_BACKEND_CURL"
];
buildInputs = oa.buildInputs ++ [ curl ];
NIX_LDFLAGS = "-lcurl";
enableParallelBuilding = true;
}));
signage = stdenv.mkDerivation {
name = "signage";
src = fetchgit {
url = "https://code.hackerspace.pl/q3k/love2d-signage";
rev = "6c14716222e28b004861b3926560bf21d519fb00";
sha256 = "sha256-dfZ6Q320+ukMt9Q2igcARBM72LRbW5ltEvxrngSW8fQ=";
};
installPhase = ''
mkdir -p $out/share/signage
cp -rv $src/* $out/share/signage/
mkdir -p $out/bin/
cat <<EOF >$out/bin/signage
#!/usr/bin/env bash
${love12}/bin/love $out/share/signage
EOF
chmod +x $out/bin/signage
'';
};
in signage

24
hswaw/signage/README Normal file
View File

@ -0,0 +1,24 @@
love2d signage
===
As used in digital signage displays in HSWAW.
Originally hosted on code.hackerspace.pl/informatic/love2d-signage, now in hscloud.
Building & Running
---
$ nix-build -A hswaw.signage.prod
$ result/bin/signage
If the result doesn't run and complains about OpenGL not being available, try building the 'local' target instead, which will use your host `<nixpkgs>`:
$ nix-build -A hswaw.signage.local
For non-NixOS systems you'll need something like `nixgl` to work around Nixpkgs-GL-on-Non-NixOS badness anyways.
Custom configs
---
You can either modify config.lua and rebuild, or specify a custom config at runtime by settin gthe `SIGNAGE_CONFIG` environment variable.

13
hswaw/signage/config.lua Normal file
View File

@ -0,0 +1,13 @@
return {
displayTime = 2,
transitionTime = 0.5,
showProgress = true,
nodes = {
{'nodes.weather'},
{'nodes.newdash', displayTime=10},
{'nodes.misery', displayTime = 7},
},
environment = os.getenv('ENV') or 'prod',
renderWidth = 1280,
renderHeight = 720,
}

View File

@ -0,0 +1,114 @@
local NodeManager = class('NodeManager', {
state = 'running',
stateTime = 0,
currentNode = nil,
})
function NodeManager:init(config)
self.config = config
self.nodes = {}
self.nodeConfs = {}
self.currentNode = nil
print('Initializing NodeManager')
end
function NodeManager:load()
self:configChanged()
end
function NodeManager:resize(w, h)
self.secondaryCanvas = love.graphics.newCanvas(w, h)
end
function NodeManager:configChanged()
local cnt = {}
local newNodes = {}
local newNodeConfs = {}
for _, c_ in ipairs(self.config.nodes) do
local nodeConfig = lume.clone(c_)
local hash = inspect(nodeConfig)
if cnt[hash] == nil then cnt[hash] = 0 end
cnt[hash] = cnt[hash] + 1
hash = hash .. '-' .. tostring(cnt[hash])
local nodeName = nodeConfig[1]
table.remove(nodeConfig, 1)
if self.nodeConfs[hash] then
print('Using existing node:', self.nodeConfs[hash], hash)
newNodes[#newNodes + 1] = self.nodeConfs[hash]
else
print('Creating new node.', nodeName, inspect(nodeConfig))
local status, err = pcall(function()
newNodes[#newNodes + 1] = require(nodeName)(nodeConfig)
end)
if err then
print("Error occured while loading", nodeName, err)
return
end
end
newNodeConfs[hash] = newNodes[#newNodes]
end
self.nodes = newNodes
self.nodeConfs = newNodeConfs
end
function NodeManager:render()
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
-- love.graphics.print('state: ' .. self.state .. '; counter: ' .. tostring(self.stateTime), 50, 50)
self.currentNode:render()
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.secondaryCanvas:renderTo(function()
self.nextNode:render()
end)
love.graphics.setColor(1.0, 1.0, 1.0, 1.0 * (self.stateTime / self.config.transitionTime))
love.graphics.draw(self.secondaryCanvas, 0, 0)
end
love.graphics.setColor(1.0, 1.0, 1.0, 0.3)
if self.config.showProgress and self.state == 'running' then
local stateTime
stateTime = self.currentNode.displayTime or self.config.displayTime
local h = 5
love.graphics.rectangle("fill", 0, love.graphics.getHeight() - h, (self.stateTime / stateTime) * love.graphics.getWidth(), h)
end
end
function NodeManager:update(dt)
if not self.currentNode then self.currentNode = self.nodes[1] end
if not self.currentNode then return end
self.stateTime = self.stateTime + dt
if self.state == 'transitioning' and self.stateTime >= self.config.transitionTime then
self.stateTime = 0
self.state = 'running'
self.currentNode:afterExit()
-- self.currentNode, self.nextNode = self.nextNode, self.nodes[(lume.find(self.nodes, self.nextNode) or 1) % #self.nodes + 1]
self.currentNode = self.nextNode
self.currentNode:afterEnter()
elseif self.state == 'running' and self.stateTime >= (self.currentNode.displayTime or self.config.displayTime) then
self.stateTime = 0
self.state = 'transitioning'
self.currentNode:beforeExit()
self.nextNode = self.nodes[(lume.find(self.nodes, self.currentNode) or 1) % #self.nodes + 1]
self.nextNode:beforeEnter()
end
self.currentNode:update(dt)
if self.state == 'transitioning' and self.currentNode ~= self.nextNode and self.nextNode then
self.nextNode:update(dt)
end
end
return NodeManager

View File

@ -0,0 +1,16 @@
local Node = class('Node', {})
function Node:init(opts)
for i, n in pairs(opts) do
self[i] = n
end
end
function Node:update(dt) end
function Node:beforeEnter() end
function Node:afterEnter() end
function Node:beforeExit() end
function Node:afterExit() end
return Node

View File

@ -0,0 +1,40 @@
local ThreadNode = Node:extend('ThreadNode', {
threadFile = nil,
threadChannel = nil,
updateInterval = 5,
lastUpdate = 0,
state = nil,
})
function ThreadNode:update(dt)
if not self.state then self:checkUpdate() end
end
function ThreadNode:checkUpdate()
if self.threadFile and self.threadChannel then
if self.lastUpdate < love.timer.getTime() - self.updateInterval or
(not self.state and self.lastUpdate < love.timer.getTime() - 5) then
self.lastUpdate = love.timer.getTime()
print(self.threadChannel, "Updating...")
local updateThread = love.thread.newThread(self.threadFile)
updateThread:start()
end
local v = love.thread.getChannel(self.threadChannel):pop()
if v then
self:onUpdate(v)
end
end
end
function ThreadNode:afterExit()
print('exit')
self:checkUpdate()
end
function ThreadNode:onUpdate(v)
self.state = v
end
return ThreadNode

43
hswaw/signage/default.nix Normal file
View File

@ -0,0 +1,43 @@
{ pkgs, ... }: let
signageForPkgs = pkgs: with { inherit (pkgs) love fetchFromGitHub stdenv curl; }; let
# Build LÖVE2D 12, currently still in development. This gives us https
# support.
love12 = (love.overrideAttrs (oa: {
version = "12.0-dev";
src = fetchFromGitHub {
owner = "love2d";
repo = "love";
rev = "d586d1847446f5212d5f7e9efb94e50fcfba7d77";
sha256 = "sha256-gTpVtyqXV6/GsezuCpooaY+x5tPfOF9p1b83v4kKR4E=";
};
makeFlags = [
"CPPFLAGS=-DHTTPS_BACKEND_CURL"
];
buildInputs = oa.buildInputs ++ [ curl ];
NIX_LDFLAGS = "-lcurl";
enableParallelBuilding = true;
}));
signage = stdenv.mkDerivation {
name = "signage";
src = ./.;
installPhase = ''
mkdir -p $out/share/signage
cp -rv $src/* $out/share/signage/
mkdir -p $out/bin/
cat <<EOF >$out/bin/signage
#!/usr/bin/env bash
${love12}/bin/love $out/share/signage
EOF
chmod +x $out/bin/signage
'';
};
in signage;
in {
prod = signageForPkgs pkgs;
local = signageForPkgs (import <nixpkgs> {});
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

104
hswaw/signage/main.lua Normal file
View File

@ -0,0 +1,104 @@
class = require('vendor.30log')
inspect = require('vendor.inspect')
lume = require('vendor.lume')
Node = require('core.node')
ThreadNode = require('core.thread-node')
NodeManager = require('core.node-manager')
local config = nil
if os.getenv("SIGNAGE_CONFIG") ~= nil then
local f = loadfile(os.getenv("SIGNAGE_CONFIG"))
if f ~= nil then
config = f()
else
error("SIGNAGE_CONFIG given but could not be loaded")
end
else
config = require('config')
end
local push = require('vendor.push')
local lurker = require('vendor.lurker')
lurker.quiet = true
lurker.interval = 3
local debugGraph = require('vendor.debugGraph')
local fpsGraph = debugGraph:new('fps', 0, 0)
local memGraph = debugGraph:new('mem', 0, 30)
local gameWidth, gameHeight = config.renderWidth, config.renderHeight
local windowWidth, windowHeight = love.window.getDesktopDimensions()
windowWidth, windowHeight = windowWidth*.5, windowHeight*.5 --make the window a bit smaller than the screen itself
if config.environment == 'dev' then
push:setupScreen(gameWidth, gameHeight, windowWidth, windowHeight, {fullscreen = false, resizable = true})
else
push:setupScreen(gameWidth, gameHeight, gameWidth, gameHeight, {fullscreen = true})
end
function love.resize(w, h)
push:resize(w, h)
manager:resize(w, h)
end
function love.load()
manager = NodeManager(config)
manager:load()
manager:resize(push:getWidth(), push:getHeight())
love.mouse.setVisible( false )
end
function getw() return push:getWidth() end
function geth() return push:getHeight() end
function love.draw()
push:start()
-- Patch love.graphics.getWidth/Height to account for push
oldw, oldh = love.graphics.getWidth, love.graphics.getHeight
love.graphics.getWidth, love.graphics.getHeight = getw, geth
manager:render()
-- Draw graphs
love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
-- love.graphics.setNewFont(10)
-- love.graphics.print(inspect(state), 0, 60, 0)
fpsGraph:draw()
memGraph:draw()
love.graphics.getWidth, love.graphics.getHeight = oldw, oldh
push:finish()
end
function love.keypressed( key, scancode, isrepeat )
if key == "right" then
-- Cycle to next state
manager.stateTime = 2137
end
end
function love.update(dt)
manager:update(dt)
-- Update the graphs
fpsGraph:update(dt)
memGraph:update(dt)
lurker.update()
end
function lurker.preswap(f)
if f == 'config.lua' then
print('config reloaded, notifying nodemanager')
package.loaded['config'] = nil
manager.config = require('config');
manager:configChanged();
elseif f:match('_thread.lua') then
return false
end
end

View File

@ -0,0 +1,46 @@
local node = ThreadNode:extend('nodes.at', {
threadFile = 'nodes/at_thread.lua',
threadChannel = 'at',
api = 'http://at.hackerspace.pl/api',
})
local bigFont = love.graphics.newFont('fonts/Lato-Light.ttf', 80)
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(bigFont)
love.graphics.printf('Currently at hackerspace:', 50, 100, love.graphics.getWidth() - 100, 'center')
usersList = (table.concat(lume.map(self.state.users, function(v) return v.login end), ', ') or 'nobody...') .. '\n'
if self.state.unknown > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.unknown) .. ' unknown creatures'
end
if self.state.kektops > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.kektops) .. ' kektops'
end
if self.state.esps > 0 then
usersList = usersList .. '\n...and ' .. tostring(self.state.esps) .. ' ESPs'
end
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont)
love.graphics.printf(usersList, 50, 220, love.graphics.getWidth() - 100, 'center')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading at...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,14 @@
local socket = require("socket")
local http = require("socket.http")
local json = require("vendor.json")
local lume = require("vendor.lume")
local miseryURL = 'http://at.hackerspace.pl/api'
local r, c, h = http.request(miseryURL)
if c == 200 then
love.thread.getChannel('at'):push(json.decode(r))
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,55 @@
local node = Node:extend('nodes.countdown', {
target = 1498780800,
description = 'to get the fuck out of here',
precision = 3,
})
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 100)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 60)
function node:init(config)
node.super.init(self, config)
end
function timefmt(time, precision)
precision = precision or 3
local p = {
{60, "seconds"},
{60, "minutes"},
{24, "hours"},
{7, "days"},
{nil, "weeks"},
}
local parts = {}
local v
for i, e in ipairs(p) do
if e[1] == nil then
v = time
else
v = time % e[1]
time = math.floor(time / e[1])
end
if v ~= 0 then
table.insert(parts, 1, tostring(v) .. " " .. e[2])
end
end
return table.concat(lume.slice(parts, 1, precision), " ")
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont);
love.graphics.printf(timefmt(math.abs(self.target - os.time()), self.precision), 0, 0.3*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
love.graphics.setFont(smallFont);
love.graphics.printf(self.description, 0, 0.7*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
end
return node

View File

@ -0,0 +1,64 @@
local node = Node:extend('nodes.at', {
})
-- local papa = love.graphics.newImage("papa.png")
local h = 25.0
local v = {
{-h, -h, -h},
{ h, -h, -h},
{ h, h, -h},
{-h, h, -h},
{-h, -h, h},
{ h, -h, h},
{ h, h, h},
{-h, h, h}
}
local c = {
{0, 1, 31},
{1, 2, 31},
{2, 3, 31},
{3, 0, 31},
{0, 4, 34},
{1, 5, 34},
{2, 6, 34},
{3, 7, 34},
{4, 5, 32},
{5, 6,32},
{6, 7,32},
{7, 4,32}
}
local E = 100 * math.tan(2*math.pi/3)
function to2d(p) return p[1] * E / (p[3]+E), p[2] * E / (p[3]+E) end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
local w = love.graphics.getWidth() / 2
local h = love.graphics.getHeight() / 2
local scl = 6
for _, p in ipairs(c) do
x1, y1 = to2d(v[p[1]+1])
x2, y2 = to2d(v[p[2]+1])
love.graphics.line(x1 * scl + w, y1 * scl + h, x2 * scl + w, y2 * scl + h)
end
end
function node:update(dt)
for _, vec in ipairs(v) do
angle = -dt/3
nv0 = math.cos(angle) * vec[1] + math.sin(angle) * vec[3]
nv2 = -math.sin(angle) * vec[1] + math.cos(angle) * vec[3]
vec[1] = nv0
vec[3] = nv2
angle = dt
nv1 = math.cos(angle) * vec[2] - math.sin(angle) * vec[3]
nv2 = math.sin(angle) * vec[2] + math.cos(angle) * vec[3]
vec[2] = nv1
vec[3] = nv2
end
end
return node

View File

@ -0,0 +1,64 @@
local node = ThreadNode:extend('node.currency', {
threadFile = 'nodes/currency_thread.lua',
threadChannel = 'currency',
updateInterval = 10,
state = {
values = {},
changes = {},
},
})
local inspect = require('vendor.inspect')
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 90)
local headFont = love.graphics.newFont('fonts/Lato-Regular.ttf', 90)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state.values and self.state.values[2] then
local pad = 20
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(headFont)
love.graphics.printf("BTCPLN", 0, 180, love.graphics.getWidth()/2, 'right')
love.graphics.printf("TRYIDR", 0, 350, love.graphics.getWidth()/2, 'right')
love.graphics.setFont(textFont)
if self.state.changes[1] then
love.graphics.setColor( 0, 1.0, 0 )
else
love.graphics.setColor( 1.0, 0, 0 )
end
love.graphics.printf(self.state.values[1], love.graphics.getWidth()/2 + 2*pad, 180, love.graphics.getWidth()/2 - 2*pad, 'left')
if self.state.changes[2] then
love.graphics.setColor( 0, 1.0, 0 )
else
love.graphics.setColor( 1.0, 0, 0 )
end
love.graphics.printf(self.state.values[2], love.graphics.getWidth()/2 + 2*pad, 350, love.graphics.getWidth()/2 - 2*pad, 'left')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading currency...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
function node:onUpdate(v)
for n in ipairs(v.values) do
if self.state.values[n] then
if self.state.values[n] ~= v.values[n] then
self.state.changes[n] = self.state.values[n] > v.values[n]
end
else
self.state.changes[n] = true
end
end
self.state.values = v.values
end
return node

View File

@ -0,0 +1,22 @@
local socket = require("socket")
local http = require("socket.http")
local json = require("vendor.json")
local lume = require("vendor.lume")
local btcURL = 'http://www.bitmarket.pl/json/BTCPLN/ticker.json'
local tryURL = 'http://api.fixer.io/latest?base=TRY'
local r, c, h = http.request(btcURL)
if c == 200 then
btcpln = json.decode(r)['last']
local r, c, h = http.request(tryURL)
if c == 200 then
tryidr = json.decode(r)['rates']['IDR']
end
love.thread.getChannel('currency'):push({
values = {math.floor(btcpln), math.floor(tryidr)},
})
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,34 @@
local node = ThreadNode:extend('node.misery', {
threadFile = 'nodes/misery_thread.lua',
threadChannel = 'misery',
updateInterval = 10,
})
local inspect = require('vendor.inspect')
local textFont = love.graphics.newFont('fonts/Lato-Light.ttf', 50)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont)
love.graphics.printf(self.state.entry, 50, 180, love.graphics.getWidth() - 100, 'center')
love.graphics.setColor( 1.0, 1.0, 1.0, 0.8 )
love.graphics.setFont(smallFont)
local description = 'added by ' .. self.state.author .. ' on ' .. os.date('%Y/%m/%d %X', self.state.added)
love.graphics.printf(description, 200, love.graphics.getHeight() - 100, love.graphics.getWidth() - 400, 'center')
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading misery...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,14 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local lume = require("vendor.lume")
local miseryURL = 'https://oodviewer.q3k.me/randomterm.json/_,'
local c, r, h = https.request(miseryURL)
if c == 200 then
love.thread.getChannel('misery'):push(json.decode(r))
print("Update finished")
else
print("Update failed: " .. tostring(c) .. ": " .. tostring(r))
end

View File

@ -0,0 +1,123 @@
local node = ThreadNode:extend('nodes.newdash', {
threadFile = 'nodes/newdash_thread.lua',
threadChannel = 'newdash',
updateInterval = 60,
})
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 165)
local tempFont = love.graphics.newFont('fonts/Lato-Light.ttf', 120)
local timeFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 330)
local dateFont = love.graphics.newFont('fonts/Lato-Light.ttf', 90)
local headerFont = love.graphics.newFont('fonts/Lato-Regular.ttf', 40)
local valueFont = love.graphics.newFont('fonts/Lato-Light.ttf', 45)
local atFont = love.graphics.newFont('fonts/Lato-Light.ttf', 35)
local weatherGlyphs = {
snow = "",
mist = "",
clear = "",
-- clouds = "",
clouds = "", -- x---DDD
drizzle = "",
}
function node:spejsiotData(node_id, endpoint, parameter)
if self.state.spejsiot[node_id] and self.state.spejsiot[node_id]["$online"] and self.state.spejsiot[node_id][endpoint] and self.state.spejsiot[node_id][endpoint][parameter] ~= nil then
return self.state.spejsiot[node_id][endpoint][parameter]
else
return nil
end
end
function node:renderIOTState(node_id, endpoint, parameter, x, y)
local rawValue = self:spejsiotData(node_id, endpoint, parameter)
if rawValue == true then
love.graphics.setColor( 0, 1.0, 0 )
love.graphics.printf("ON", x, y, 400, 'left')
elseif rawValue == false then
love.graphics.setColor( 1.0, 0, 0 )
love.graphics.printf("OFF", x, y, 400, 'left')
elseif rawValue == nil then
love.graphics.setColor( 1.0, 0, 0 )
love.graphics.printf("OFFLINE", x, y, 400, 'left')
else
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.printf(rawValue, x, y, 400, 'left')
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
if weatherGlyphs[self.state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[self.state.weather], 100, 340)
else
love.graphics.setFont(atFont)
love.graphics.print(self.state.weather, 100, 370)
end
love.graphics.setFont(tempFont)
love.graphics.printf(string.format("%d°", self.state.temperature), 350, 390, 270, 'center')
love.graphics.setFont(headerFont)
love.graphics.printf("Ambient:", 720, 380, 160, 'right')
love.graphics.printf("Exhaust:", 720, 440, 160, 'right')
love.graphics.printf("Pope:", 720, 500, 160, 'right')
love.graphics.setFont(valueFont)
if self:spejsiotData("d106e1", "environment", "degree") then
love.graphics.printf(string.format(
"%d° / %d%%RH",
self:spejsiotData("d106e1", "environment", "degree"),
self:spejsiotData("d106e1", "environment", "humidity")
), 900, 378, 400, 'left')
else
love.graphics.printf("?!", 900, 378, 400, 'left')
end
self:renderIOTState("c0dbe7", "relay", "on", 900, 438)
self:renderIOTState("0eac42", "spin and blink", "on", 900, 498)
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(headerFont)
love.graphics.printf("at hackerspace:", 50, 593, 300, 'left')
love.graphics.setFont(atFont)
users = {}
if self.state.at then
users = lume.map(self.state.at.users, function(v) return v.login end)
if self.state.at.unknown > 0 then
users[#users + 1] = string.format("%d unknown creatures", self.state.at.unknown)
end
if self.state.at.kektops > 0 then
users[#users + 1] = string.format("%d kektops", self.state.at.kektops)
end
if self.state.at.esps > 0 then
users[#users + 1] = string.format("%d ESPs", self.state.at.esps)
end
end
usersList = (table.concat(users, ', ') or 'nobody...')
love.graphics.printf(usersList, 350, 598, 900, 'left')
else
love.graphics.setColor(1.0, 1.0, 1.0, 0.5)
love.graphics.setFont(valueFont)
love.graphics.printf("Loading...", 0, 530, love.graphics.getWidth(), "center")
end
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(timeFont)
love.graphics.printf(os.date("%H:%M"), 50, -10, 850, 'center')
love.graphics.setFont(dateFont)
love.graphics.printf(os.date("%Y\n%m/%d"), 960, 80, 270, 'center')
end
return node

View File

@ -0,0 +1,36 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local weatherURL = 'https://openweathermap.org/data/2.5/weather?id=6695624&units=metric&appid=439d4b804bc8187953eb36d2a8c26a02'
local spejsiotURL = 'https://spejsiot.waw.hackerspace.pl/api/1/devices'
local atURL = 'https://at.hackerspace.pl/api'
local spejsiotData = {}
local atData = nil
local c, r, h = https.request(weatherURL)
if c == 200 then
local data = json.decode(r)
local c, r, h = https.request(spejsiotURL)
if c == 200 then
spejsiotData = json.decode(r)
end
local c, r, h = https.request(atURL)
if c == 200 then
atData = json.decode(r)
end
love.thread.getChannel('newdash'):push({
weather = data.weather[1].main:lower(),
temperature = data.main.temp,
lastUpdate = data.dt,
spejsiot = spejsiotData,
at = atData,
})
print("Update finished")
else
print("Update failed")
end

View File

@ -0,0 +1,16 @@
local node = Node:extend('nodes.screen1', {})
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
for x = 0, love.graphics.getWidth() / 50, 1 do
for y = 0, love.graphics.getHeight() / 50, 1 do
local r = math.sin(x + love.timer.getTime() * 2) + math.cos(y + love.timer.getTime() * math.sin(x));
love.graphics.setColor(1.0, 1.0, 1.0, (r + 2) * 1.0)
love.graphics.circle("line", x * 50, y * 50, r * 10)
end
end
end
return node

View File

@ -0,0 +1,117 @@
local node = Node:extend('nodes.shadertoy', {})
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 20)
function node:init(config)
node.super.init(self, config)
self.path = self.path or "test.glsl"
self.resolution = self.resolution or {1280, 720}
self:loadShader()
end
function node:beforeEnter()
self:loadShader()
end
function node:loadShader()
local iSystem = {}
local header = ""
local ender=[[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec2 fragCoord = texture_coords * iResolution.xy;
mainImage( color, fragCoord );
return color;
}
]]
local file = io.open(self.path, "r")
local shaderData = file:read("*all")
shaderData = string.gsub(shaderData,"texture2D","Texel")
shaderData = string.gsub(shaderData,"iTime","iGlobalTime")
shaderData = string.gsub(shaderData,"precision highp float;","")
if string.find(shaderData,"iGlobalTime") then
iSystem.iGlobalTime=0
if not string.find(shaderData,"number iGlobalTime") then
header="extern number iGlobalTime;\n"..header
end
end
-- TODO
-- if string.find(shaderData,"iChannel") then
-- iSystem.iChannel={}
-- for k,v in pairs(shaderChannel) do
-- header="extern Image iChannel"..k..";\n"..header
-- end
-- end
if string.find(shaderData,"iMouse") then
iSystem.iMouse = {0, 0, -1, -1}
header = "extern vec4 iMouse;\n"..header
end
if string.find(shaderData,"iResolution") then
iSystem.iResolution = {self.resolution[1], self.resolution[2],1}
header = "extern vec3 iResolution;\n"..header
end
shaderData = header..shaderData
if not string.find(shaderData,"vec4 effect") then
shaderData = shaderData.."\n"..ender
end
print('Shader loaded')
self.shaderLoadError = nil
shaderLoaded, self.shader = pcall(love.graphics.newShader, shaderData)
if not shaderLoaded then
print('Shader load failed:', self.shader)
self.shaderLoadError = self.shader
self.shader = nil
else
print(shaderLoaded, self.shader)
if iSystem.iResolution then
self.shader:send("iResolution",iSystem.iResolution)
end
self.iSystem = iSystem
self.canvas = love.graphics.newCanvas(self.resolution[1], self.resolution[2])
self.renderCanvas = love.graphics.newCanvas(self.resolution[1], self.resolution[2])
end
end
function node:update(dt)
if self.shader ~= nil then
if self.iSystem.iGlobalTime then
self.iSystem.iGlobalTime=self.iSystem.iGlobalTime+dt
self.shader:send("iGlobalTime", self.iSystem.iGlobalTime)
end
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.shaderLoadError ~= nil then
print('render!')
love.graphics.setColor(1.0, 0.0, 0.0, 1.0)
love.graphics.setFont(smallFont)
love.graphics.printf(self.shaderLoadError, 0, 0.1*love.graphics.getHeight(), love.graphics.getWidth(), 'left');
elseif self.shader ~= nil then
oldCanvas = love.graphics.getCanvas( )
love.graphics.setColor( 1.0, 1.0, 1.0 )
self.canvas:renderTo(function ()
love.graphics.setShader(self.shader)
love.graphics.draw(self.renderCanvas)
love.graphics.setShader()
end)
love.graphics.setCanvas(oldCanvas)
love.graphics.draw(self.canvas,0,0,math.pi,love.graphics.getWidth() / self.resolution[1], love.graphics.getHeight() / self.resolution[2], self.resolution[1], self.resolution[2])
end
end
return node

View File

@ -0,0 +1,25 @@
local node = Node:extend('nodes.time', {})
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 400)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 60)
function node:init(config)
node.super.init(self, config)
self.timeFormat = self.timeFormat or '%H:%M'
self.dateFormat = self.dateFormat or '%Y/%m/%d'
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
love.graphics.setColor( 1.0, 1.0, 1.0 )
love.graphics.setFont(textFont);
love.graphics.printf(os.date(self.timeFormat), 0, 0.14*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
love.graphics.setFont(smallFont);
love.graphics.printf(os.date(self.dateFormat), 0, 0.8*love.graphics.getHeight(), love.graphics.getWidth(), 'center');
end
return node

View File

@ -0,0 +1,88 @@
local node = ThreadNode:extend('nodes.weather', {
threadFile = 'nodes/weather_thread.lua',
threadChannel = 'weather',
updateInterval = 5 * 60,
})
local weatherFont = love.graphics.newFont('fonts/weathericons-regular-webfont.ttf', 400)
local textFont = love.graphics.newFont('fonts/Lato-Thin.ttf', 300)
local smallFont = love.graphics.newFont('fonts/Lato-Light.ttf', 30)
local weatherGlyphs = {}
local weatherGlyphsSet = {
day = {
snow = "",
mist = "",
clear = "",
-- clouds = "",
clouds = "", -- x---DDD
drizzle = "",
},
night = {
snow = "",
mist = "",
clear = "",
clouds = "",
drizzle = "",
}
}
function node:timeOfDay()
local sunRise = tonumber(os.date("%H%M", self.state.sunRise))
local sunSet = tonumber(os.date("%H%M", self.state.sunSet))
local now = tonumber(os.date("%H%M"))
if sunRise == nil or sunSet == nil then
return weatherGlyphsSet["day"] -- smth gone wrong. assume daylight
end
if now < sunSet and now > sunRise then
print('day')
return weatherGlyphsSet["day"]
else
print('night')
return weatherGlyphsSet["night"]
end
end
function node:beforeEnter()
if self.state then
weatherGlyphs = self:timeOfDay()
else
weatherGlyphs = weatherGlyphsSet["day"] -- do not know sunraise and sunset yet. assume daylight
end
end
function node:render()
love.graphics.setColor( 0, 0, 0 )
love.graphics.rectangle("fill", 0, 0, love.graphics.getWidth(), love.graphics.getHeight())
if self.state then
love.graphics.setColor( 1.0, 1.0, 1.0 )
if weatherGlyphs[self.state.weather] then
love.graphics.setFont(weatherFont)
love.graphics.print(weatherGlyphs[self.state.weather], 120, 35)
else
love.graphics.setFont(smallFont)
love.graphics.print(self.state.weather, 150, love.graphics.getHeight()/2 - 60)
end
love.graphics.setFont(textFont)
love.graphics.printf(tostring(math.floor(self.state.temperature + 0.5)) .. "°", 600, 150, 650, 'center')
love.graphics.setFont(smallFont)
love.graphics.printf(os.date("Last update: %Y/%m/%d %H:%M", self.state.lastUpdate), 0, love.graphics.getHeight() - 60, love.graphics.getWidth(), 'center')
if self.state.insideTemperature then
love.graphics.printf("Room: " .. tostring(self.state.insideTemperature) .. "° / " .. tostring(self.state.insideHumidity) .. "%RH", 0, love.graphics.getHeight() - 100, love.graphics.getWidth(), 'center')
end
else
love.graphics.setColor( 1.0, 1.0, 1.0, 0.4 )
love.graphics.setFont(smallFont)
love.graphics.printf("Loading weather...", 0, love.graphics.getHeight() - 200, love.graphics.getWidth(), 'center')
end
end
return node

View File

@ -0,0 +1,32 @@
local socket = require("socket")
local https = require("https")
local json = require("vendor.json")
local weatherURL = 'https://openweathermap.org/data/2.5/weather?id=6695624&units=metric&appid=439d4b804bc8187953eb36d2a8c26a02'
--local insideURL = 'https://dht01.waw.hackerspace.pl/'
--local insideData = {}
local c, r, h = https.request(weatherURL)
if c == 200 then
local data = json.decode(r)
--local r, c, h = http.request(insideURL)
--if c == 200 then
-- for n in string.gmatch(string.gsub(r, ",", "."), ":%s*(%S+)[*%%]") do
-- insideData[#insideData+1] = n
-- end
--end
love.thread.getChannel('weather'):push({
weather = data.weather[1].main:lower(),
temperature = data.main.temp,
lastUpdate = data.dt,
sunRise = data.sys.sunrise,
sunSet = data.sys.sunset,
insideTemperature = nil,
insideHumidity = nil,
})
print("Update finished")
else
print("Update failed")
end

31
hswaw/signage/vendor/30log.lua vendored Normal file
View File

@ -0,0 +1,31 @@
local next, assert, pairs, type, tostring, setmetatable, baseMt, _instances, _classes, _class = next, assert, pairs, type, tostring, setmetatable, {}, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
local function assert_call_from_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end; local function assert_call_from_instance(instance, method) assert(_instances[instance], ('Wrong method call. Expected instance:%s.'):format(method)) end
local function bind(f, v) return function(...) return f(v, ...) end end
local default_filter = function() return true end
local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {}; for k,v in pairs(t) do if aType ~= nil and type(v) == aType then r[k] = (type(v) == 'table') and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v elseif aType == nil then r[k] = (type(v) == 'table') and k~= '__index' and ((_classes[v] or _instances[v]) and v or deep_copy(v)) or v end; end return r end
local function instantiate(call_init,self,...) assert_call_from_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); deep_copy(self, instance, 'table')
instance.__index, instance.__subclasses, instance.__instances, instance.mixins = nil, nil, nil, nil; setmetatable(instance,self); if call_init and self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end end; return instance
end
local function extend(self, name, extra_params)
assert_call_from_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); self.__subclasses[heir] = true; deep_copy(extra_params, deep_copy(self, heir))
heir.name, heir.__index, heir.super, heir.mixins = extra_params and extra_params.name or name, heir, self, {}; return setmetatable(heir,self)
end
baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...)
if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end; return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?', _classes[self]) or self end
}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring})
local class = {isClass = function(t) return not not _classes[t] end, isInstance = function(t) return not not _instances[t] end}
_class = function(name, attr) local c = deep_copy(attr); _classes[c] = tostring(c)
c.name, c.__tostring, c.__call, c.new, c.create, c.extend, c.__index, c.mixins, c.__instances, c.__subclasses = name or c.name, baseMt.__tostring, baseMt.__call, bind(instantiate, true), bind(instantiate, false), extend, c, setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'}), setmetatable({},{__mode = 'k'})
c.subclasses = function(self, filter, ...) assert_call_from_class(self, 'subclasses(class)'); filter = filter or default_filter; local subclasses = {}; for class in pairs(_classes) do if class ~= baseMt and class:subclassOf(self) and filter(class,...) then subclasses[#subclasses + 1] = class end end; return subclasses end
c.instances = function(self, filter, ...) assert_call_from_class(self, 'instances(class)'); filter = filter or default_filter; local instances = {}; for instance in pairs(_instances) do if instance:instanceOf(self) and filter(instance, ...) then instances[#instances + 1] = instance end end; return instances end
c.subclassOf = function(self, superclass) assert_call_from_class(self, 'subclassOf(superclass)'); assert(class.isClass(superclass), 'Wrong argument given to method "subclassOf()". Expected a class.'); local super = self.super; while super do if super == superclass then return true end; super = super.super end; return false end
c.classOf = function(self, subclass) assert_call_from_class(self, 'classOf(subclass)'); assert(class.isClass(subclass), 'Wrong argument given to method "classOf()". Expected a class.'); return subclass:subclassOf(self) end
c.instanceOf = function(self, fromclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(fromclass), 'Wrong argument given to method "instanceOf()". Expected a class.'); return ((self.class == fromclass) or (self.class:subclassOf(fromclass))) end
c.cast = function(self, toclass) assert_call_from_instance(self, 'instanceOf(class)'); assert(class.isClass(toclass), 'Wrong argument given to method "cast()". Expected a class.'); setmetatable(self, toclass); self.class = toclass; return self end
c.with = function(self,...) assert_call_from_class(self, 'with(mixin)'); for _, mixin in ipairs({...}) do assert(self.mixins[mixin] ~= true, ('Attempted to include a mixin which was already included in %s'):format(tostring(self))); self.mixins[mixin] = true; deep_copy(mixin, self, 'function') end return self end
c.includes = function(self, mixin) assert_call_from_class(self,'includes(mixin)'); return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end
c.without = function(self, ...) assert_call_from_class(self, 'without(mixin)'); for _, mixin in ipairs({...}) do
assert(self.mixins[mixin] == true, ('Attempted to remove a mixin which is not included in %s'):format(tostring(self))); local classes = self:subclasses(); classes[#classes + 1] = self
for _, class in ipairs(classes) do for method_name, method in pairs(mixin) do if type(method) == 'function' then class[method_name] = nil end end end; self.mixins[mixin] = nil end; return self end; return setmetatable(c, baseMt) end
class._DESCRIPTION = '30 lines library for object orientation in Lua'; class._VERSION = '30log v1.2.0'; class._URL = 'http://github.com/Yonaba/30log'; class._LICENSE = 'MIT LICENSE <http://www.opensource.org/licenses/mit-license.php>'
return setmetatable(class,{__call = function(_,...) return _class(...) end })

132
hswaw/signage/vendor/debugGraph.lua vendored Normal file
View File

@ -0,0 +1,132 @@
--[[
UNLICENSE
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
software, either in source code form or as a compiled binary, for any purpose,
commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this
software dedicate any and all copyright interest in the software to the public
domain. We make this dedication for the benefit of the public at large and to
the detriment of our heirs and successors. We intend this dedication to be an
overt act of relinquishment in perpetuity of all present and future rights to
this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTBILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT, IN NO EVENT SHALL THE AUTHORS BE
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
]]
-- Code based on https://github.com/icrawler/FPSGraph
local debugGraph = {}
function debugGraph:new(type, x, y, width, height, delay, label, font)
if ({mem=0, fps=0, custom=0})[type] == nil then
error('Acceptable types: mem, fps, custom')
end
local instance = {
x = x or 0, -- X position
y = y or 0, -- Y position
width = width or 50, -- Graph width
height = height or 30, -- Graph height
delay = delay or 0.5, -- Update delay
label = label or type, -- Graph label
font = font or love.graphics.newFont(8),
data = {},
_max = 0,
_time = 0,
_type = type,
}
-- Build base data
for i = 0, math.floor(instance.width / 2) do
table.insert(instance.data, 0)
end
-- Updating the graph
function instance:update(dt, val)
local lastTime = self._time
self._time = (self._time + dt) % self.delay
-- Check if the minimum amount of time has past
if dt > self.delay or lastTime > self._time then
-- Fetch data if needed
if val == nil then
if self._type == 'fps' then
-- Collect fps info and update the label
val = 0.75 * 1 / dt + 0.25 * love.timer.getFPS()
self.label = "FPS: " .. math.floor(val * 10) / 10
elseif self._type == 'mem' then
-- Collect memory info and update the label
val = collectgarbage('count')
self.label = "Memory (KB): " .. math.floor(val * 10) / 10
else
-- If the val is nil then we'll just skip this time
return
end