Subscribe to this Thread
(Receive email notifications)
Pin To Profile
Community Tags
Mature Content
Mark as Spam
Mark as Abuse
I've made a spike for cartriges to send http requests and receive their response.
The way it works is the cartrige communicates with the browser through gpio. In here I'm using 2 assumptions (I guess dangerous ones) that make it quite simple to send messages:
We get synchronous interrupts from p8 -> JS when p8 writes to a GPIO.
We read an analog value 0-255 in perfect accuracy.
So... yep, this code atm only works in a browser. And it might be posible that it breaks later on (specially for assumption 1, as it depends on the runtime implementation)
It's separated in two layers: The first one just abstracts over sending and receiving any message
html_messaging.lua
html_messaging={
send_message=function(self, message)
-- sends a `message` string to the host HTML
end,
add_listener=function(self, listener)
-- adds the `listener` function to be called with receiving messages
end,
remove_listener=function(self, listener)
-- stops sending message updates to the listener
end,
update=function(self)
-- this function must be called on every update, so that html_messaging can check for new messages
end
}
html_messaging={
gpio=0x5f80,
pins={
send_req=1, -- output
read_req=3, -- input
clk_out=5, --input
data_out=7, -- output
data_in=8 -- input
},
send_message=function(self, message)
if #message == 0 then return end
poke(self.gpio + self.pins.send_req, 0xFF)
for i=1,#message do
poke(self.gpio + self.pins.data_out, ord(message, i))
end
poke(self.gpio + self.pins.send_req, 0x00)
end,
_listeners={},
add_listener=function(self, listener)
add(self._listeners, listener)
end,
remove_listener=function(self, listener)
del(self._listeners, listener)
end,
update=function(self)
local message = __messaging_receive_message()
if not message then return end
for listener in all(self._listeners) do
listener(message)
end
end
}
function __messaging_receive_message()
local gpio = html_messaging.gpio
local pins = html_messaging.pins
local is_sending = peek(gpio + pins.read_req) == 0xff
if not is_sending then return false end
local input_buffer = ""
while peek(gpio + pins.read_req) == 0xff do
input_buffer = input_buffer..chr(peek(gpio + pins.data_in))
poke(gpio + pins.clk_out, 0xff)
poke(gpio + pins.clk_out, 0x00)
end
return input_buffer
end
gpio_messaging.js
// This file also sets window.pico8_gpio - Make sure not to override it, or it won't get the interrupts!
window.pico8_gpioMessaging={
sendMessage(message) {
// sends the string `message` to p8
// returns a promise that resolves when the message was completely sent
// (we don't have synchronous interrupts from js -> p8)
}
}
// pico8_gpioMessaging is an EventEmitter of 'message' events
// which have a 'message' prop with the received message
The other layer needs to have that one imported first - This is the one to send and receive HTTP requests
http_request.lua
http_request={
get=function(self,url,cb)
-- sends a GET request to url, sending the message as a string to callback once it's received
end
}
-- requires html_messaging.lua
http_request={
_cid=0,
get=function(self,url,cb)
local cid = self._cid
self._cid = self._cid + 1
local handle_message = function(message)
local match_header = "HTTP RES "..cid
local msg_header = sub(message, 1, #match_header)
if match_header != msg_header then return end
local response = sub(message, #match_header + 2)
cb(response)
html_messaging:remove_listener(handle_message)
end
html_messaging:add_listener(handle_message)
html_messaging:send_message("HTTP REQ "..cid.." GET "..url)
end
}
http_request.js
// Exports nothing: This file listens for p8 http requests and proxies them to the real BE
I can't embed an example cartrige here because it also needs some setup on the HTML/JS side, but a simple script that requests the datetime from worldclockapi:
#include html_messaging.lua
#include http_request.lua
function _update()
if btnp(🅾️) then
http_request:get("http://worldclockapi.com/api/json/utc/now",
function (response)
printh(response)
end
)
end
html_messaging:update()
end
The only thing the host HTML needs is importing both gpio_messaging.js and http_request.js.
Now I only need to write a JSON parser :'D (/s - I know this would be a bad idea).
I think this might be useful for simple ad-hoc services, like global highscores. Also maybe to run around the size limit in cartridge for things like level data, or maybe even to some extend turn-based multiplayer games? :exploding_head:
@freds72 x-site calls are enforced by the browser as you said, but it's the server who defines what policies apply.
For instance, the call to the worldclockapi (http://worldclockapi.com/api/json/utc/now) replies with a header "Access-Control-Allow-Origin: *", which means it can be requested from any domain, so the browser will allow that one through. Most public APIs have open CORS requests, and if you build a server for your own game you can also set it up so it's available for all domains or a whitelist.