I'm new to Lua and trying to implement TCP server and client in Openwrt using luasocket and copas. The goal is to make 3 program communicate with each other via socket in asynchronous networking.
Below is the script
local copas = require("copas")
local socket = require("socket")
local host = "localhost"
local port = 20000
local hostcl1 = "localhost"
local portcl1 = 20001
local hostcl2 = "localhost"
local portcl2 = 20002
local function echoHandler(skt)
skt = copas.wrap(skt)
while true do
local data = skt:receive()
print("data received:", data, "from:", skt:getsockname())
if not data or data == nil then
break
end
end
end
local function sendToNeighbor(host, port, data)
skt = socket.connect(host, port)
if (skt ~= nil) then
skt = copas.wrap(skt)
print("client connected to " ..host.. ":" ..port.. "...")
copas.send(skt, data.."\n")
print("data sent")
skt:close()
print("Closed!")
else
print("client failed to send to " ..host.. ":" ..port.. "...")
end
end
local server = socket.bind(host, port)
copas.addserver(server, echoHandler, 0)
SendInterval = 10
SecBefore = os.date('%S')
SecSend = (SecBefore + SendInterval)%60
while true do
copas.step(0)
local Sec = os.date('%S')
if ( tonumber(Sec) == SecSend ) then
dataToClient1 = "Test1"
dataToClient2 = "Test2"
sendToNeighbor(hostcl1, portcl1, dataToClient1)
sendToNeighbor(hostcl2, portcl2, dataToClient2)
SecBefore = Sec
SecSend = (SecBefore + SendInterval)%60
end
end
On script above, I use 3 similar program in host = "localhost" and 3 different port (20000, 20001, and 20002). I want each program listen to each other and send each other data every 10 seconds. The problem is every time the program send data with copas.send function, this error occurs.
luajit: /usr/local/share/lua/5.1/copas.lua:285: attempt to yield across C-call boundary
I have try using lua 5.1, lua 5.1 + CoCo, and LuaJIT and this error always occur.
Any idea to solve this? thanks
Related
So... I am trying to explode everyone and send them a message but it is not working. Here is the code.
Serverside:
local gui = game:GetService("StarterGui")
local Players = game:GetService("Players")
local pp = game:GetService("ProximityPromptService")
local phone = game:GetService("ReplicatedStorage")
local world = game.Workspace
local function pptrig (obj, ply)
for i,v in pairs(game.Players:GetChildren()) do
local player = world:FindFirstChild(v.Name)
local nuke = Instance.new("Explosion", world)
nuke.BlastRadius = 0.9
nuke.BlastPressure = 1000000
nuke.Position = player.HumanoidRootPart.Position
print("run")
phone.Exploded:FireClient(v, v.Name, ply.Name)
end
end
pp.PromptTriggered:Connect(pptrig)
Clientside:
local phone = game:GetService("ReplicatedStorage")
local gui = game:GetService("StarterGui")
gui:SetCore("test", {Text = "Ran"})
local function humiliation(me, ply)
gui:SetCore("test", {Text = "Ran"})
if ply ~= me then
gui:SetCore("Humiliation", {
Title = "Exploded!",
Text = "You have been exploded by "..ply..".",
Duration = 10,
})
elseif ply == me then
gui:SetCore("Humiliation", {
Title = "Exploded!",
Text = "You, "..me..", exploded yourself."
})
end
end
phone.Exploded.OnClientEvent:Connect(humiliation())
When I try to run it it gives me this:
StarterGui:SetCore must be called from a local script. (x2) - Studio
Players.GoldenRStar.PlayerGui.Script:19: attempt to concatenate nil with string - Server - Script:19
Exploded is a remote event and I wanted to Explode everyone and send them a message:
"You have been exploded by GuyThatPressesButtons." and
"You, GuyThatPressesButtons, exploded yourself."
this program tries to loop though every player, explodes them and sends them a fire from the remote event Exploded, then a client script catches it and processes it acordingly.
on the fire, it is sent the players name: v.Name and the player that pressed the button, on the client script both values are nil.
So I am using a remote function, as seen at the end, and for some reason, a normal assignment of a variable won't work, it is giving me the error message, "Unable to cast value to Object," what is wrong?
local storeEvent = script.Parent.Parent.OpenStore
local slotNum = 1
script.Parent.Touched:Connect(function (hit)
if game.Players:GetPlayerFromCharacter(hit.Parent) then
storeEvent:InvokeClient(slotNum)
end
end)
and connecting to the other script:
script.Parent.OnInvoke:Connect(function (slot)
local StoreArrows = game.ReplicatedStorage.StoreArrows
StoreArrows.SlotNum.Value = slot
local cam = game.Workspace.Camera
local storeButtons = script.Parent
local camNum = game.ReplicatedStorage.StoreArrows.CamNum.Value
local camNumInst = game.Workspace.CamStorage:WaitForChild("Cam-"..camNum)
cam.CameraType = Enum.CameraType.Scriptable
cam.CFrame = camNumInst.CFrame
local clonedStoreButtons = StoreArrows:Clone()
clonedStoreButtons.Parent = player.PlayerGui.ScreenGui
end)
Keep in mind that many clients connect to a server at a time. So when you call a RemoteEvent's InvokeClient function, you have to tell it which client to invoke it on. The first parameter to InvokeClient is supposed to be the player, that's why the error is telling you that it cannot cast the slotNum value to a player object.
local storeEvent = script.Parent.Parent.OpenStore
local slotNum = 1
script.Parent.Touched:Connect(function(hit)
-- check that the thing that we touched is actually a player
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if player then
-- tell that player to open the store
storeEvent:InvokeClient(player, slotNum)
end
end)
I've been trying to connect to Twitch chat IRC using Love2D. It manages to connect (if connect == 1 then). I'm just not sure how to receive any IRC messages that are being sent to me (love.update()).
function love.load()
oauth = "oauth:someoauthhere"
user = "botname"
channel = "channeltojoin"
love.graphics.setFont(love.graphics.newFont(32))
socket = require("socket")
irc = socket.tcp()
connect = irc:connect("irc.chat.twitch.tv", 6667)
if connect == 1 then -- MAKES IT PAST THIS
irc_messages = {}
irc:send("PASS " .. oauth)
irc:send("USER " .. user)
irc:send("JOIN #" .. channel)
end
end
function update(dt)
line, err = irc:receive() --> Returns nothing
if line then
table.insert(irc_messages, line)
end
end
function love.draw()
if not next(irc_messages) == nil then
love.graphics.printf(table.concat(irc_messages, "\n"), 0, 0)
end
end
I found a solution after spending a long time searching.
Instead of this:
connect = irc:connect("irc.chat.twitch.tv", 6667)
I needed this:
connect = irc:connect(socket.dns.toip("irc.chat.twitch.tv"), 6667)
I want to turn on and off a led from a web server using NodeMCU but every time I compile the same error occurs. I am in a beginner level so I need some help. I believe this error has something to do with the function listen(). Maybe because I changed my DNS, the port 80 can't be used.
Error:
dofile("ConectarRedeWireless.lua");
192.168.137.57 255.255.255.0 192.168.137.1
ConectarRedeWireless.lua:13: address in use
stack traceback:
[C]: in function 'listen'
ConectarRedeWireless.lua:13: in main chunk
[C]: in function 'dofile'
stdin:1: in main chunk
Code:
-- Conexao na rede Wifi
wifi.setmode(wifi.STATION)
wifi.sta.config("goncalo","936674888")
print(wifi.sta.getip())
-- Definicoes do pino do led
led1 = 1
gpio.mode(led1, gpio.OUTPUT)
-- Definicoes do Web Server
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(client,request)
local buf = "";
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
local _GET = {}
if (vars ~= nil)then
for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
_GET[k] = v
end
end
buf = buf.."<h1><u>FILIPEFLOP</u></h1>";
buf = buf.."<h2><i>ESP8266 Web Server</i></h2>";
buf = buf.."<p><button><b>LED 1 LIG</b></button> <br/><br/><button><b>LED 1 DES</b></button></p>";
local _on,_off = "",""
if(_GET.pin == "LIGA1")then
gpio.write(led1, gpio.HIGH);
elseif(_GET.pin == "DESLIGA1")then
gpio.write(led1, gpio.LOW);
end
client:send(buf);
client:close();
collectgarbage();
end)
end)
First of all I suggest you use a proper init sequence in which you wait for your device to be fully connected to the WiFi until you set up the server. We keep a template in our documentation.
srv:listen(80,function(conn) can only be called once for any given port because you can't have multiple server sockets listening on the same port. That's why you get the
address in use
error. Our net.server:listen() documentation shows how to check whether srv is available before you listen. You can extend that by running srv:close() first before you listen again. Something like this:
sv = net.createServer(net.TCP, 30)
if sv then
sv:close()
sv:listen(80, function(conn)
-- do something
end)
end
Btw, where did you get that code sample from? I've seen this several times here on Stack Overflow but it's got more flaws that should be corrected.
I have a completely working esp chip that connects to wifi and creates a server. When I send it an OTA command, it runs a function that downloads a file using a socket connection.
This is the upgrader.lua that I am using:
--------------------------------------
-- Upgrader module for NODEMCU
-- LICENCE: http://opensource.org/licenses/MIT
-- cloudzhou<wuyunzhou#espressif.com> - Heavily modified by aschmois
--------------------------------------
--[[
update('file.lua', 'http://IP.ADRESS/path/file.lua')
]]--
local header = ''
local isTruncated = false
local function save(filename, response)
if isTruncated then
file.write(response)
return
end
header = header..response
local i, j = string.find(header, '\r\n\r\n')
if i == nil or j == nil then
return
end
prefixBody = string.sub(header, j+1, -1)
file.write(prefixBody)
header = ''
isTruncated = true
return
end
----
function update(filename, url, cn)
local tmpError = nil
local running = true
local error = nil
local success = false
print("Downloading from: " .. url)
local ip, port, path = string.gmatch(url, 'http://([0-9.]+):?([0-9]*)(/.*)')()
if ip == nil then
return false
end
if port == nil or port == '' then
port = 80
end
port = port + 0
if path == nil or path == '' then
path = '/'
end
print("-- Detailed Connection Info --")
print("IP: ".. ip)
print("Port: ".. port)
print("Path: ".. path)
print("-- END --")
local function timeout()
error = tmpError
file.remove(filename)
conn:close()
running = false
end
conn = net.createConnection(net.TCP, false)
conn:on('connection', function(sck, response)
tmr.stop(1)
file.open(filename, 'w')
conn:send('GET '..path..' HTTP/1.0\r\nHost: '..ip..'\r\n'..'Connection: close\r\nAccept: */*\r\n\r\n')
tmpError = "READ TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
end)
conn:on('receive', function(sck, response)
tmr.stop(1)
tmpError = "READ(2) TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
print(response)
save(filename, response)
end)
conn:on('disconnection', function(sck, response)
tmr.stop(1)
local function reset()
local list = file.list()
for k,v in pairs(list) do
if(filename == k) then
if(v == 0) then
success = false
file.close()
file.remove(filename)
else
file.close()
success = true
end
end
end
print(header)
header = ''
isTruncated = false
if(success) then
print(filename..' saved')
else
print("Could not download `".. filename.."`")
end
running = false
end
tmr.alarm(0, 2000, 0, reset)
end)
conn:connect(port, ip)
tmpError = "CONN TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
tmr.alarm(2, 1000, 1, function()
if(running == false) then
tmr.stop(2)
local buf = ''
if(success) then
buf = buf.."HTTP/1.1 200 OK\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n"
buf = buf.."1"
else
buf = buf.."HTTP/1.1 500\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n"
buf = buf.."0"
buf = buf.."\n"
if(error ~= nil) then
buf = buf..error
else
buf = buf.."UNKNOWN ERROR"
end
end
cn:send(buf)
cn:close()
end
end)
return true
end
As a test I am sending it: filename = rz.lua and url = http://192.168.1.132/rz.lua. The cn variable is the connection to send back information to the client.
The esp chip prints:
Downloading from: http://192.168.1.132/rz.lua
-- Detailed Connection Info --
IP: 192.168.1.132
Ò_ÇRöfJSúfÊÃjêÐÿ (junk reset data)
The problem seems to be connected with the conn:send() command. If it's inside the on connect function it resets. If it's outside, I will get a read timeout (since on read is never called). I really have no idea what else to do.
This is the ESP firmware info:
NodeMCU custom build by frightanic.com
branch: master
commit: 93421f2702fb02ce169f82f96be7f2a8865511e1
SSL: false
modules: node,file,gpio,wifi,net,tmr,uart
You are resetting. The "junk" is a BootROM message at the wrong baud rate.
Don't do a send followed by a close in the same callback. Use an on('sent', ... ) to trigger the close. So the 21 line body of your alarm 2 callback would be better written:
local response = "HTTP/1.1 200 OK\r\nServer: WiFi Relay\r\nContent-Type: text/plain\r\n\r\n%s"
cn:send(response:format(success and "1" or ("0\n\r" .. (error or "UNKNOWN ERROR")))
cn:on('sent', function(cn) cn:close() end)
On that note your 27 line disconnect callback would be better written:
tmr.stop(1)
tmr.alarm(0, 2000, 0, function()
local len = file.list()(filename)
success = len and len > 0
file.close()
if not success then file.remove(filename)
file.flush()
end)
Note that it's always wise to flush the SPIFFS after writing or removing files.
You use a standard pattern, so why not encapsulate it:
local conn = net.createConnection(net.TCP, false)
local function setTimeout(reason)
-- tmr.stop(1) -- not needed is the next line resets the alarm
tmr.alarm(1, 10000, 0, function ()
-- you don't need tmpError as reason is a local and bound as an upval
error, running = reason, false
file.remove(filename) file.flush()
return conn:close()
end)
end
I could go on but I leave this to you. With a little thought, your code would be a third of the size and more readable.
I can't be sure but the problem seems to have been a memory error (weird since there was no panic) so this is what I did to fix it:
local request = table.concat({"GET ", path,
" / HTTP/1.1\r\n",
"Host: ", ip, "\r\n",
"Connection: close\r\n",
"Accept: */*\r\n",
"User-Agent: Mozilla/4.0 (compatible; esp8266 Lua;)",
"\r\n\r\n"})
conn = net.createConnection(net.TCP, false)
conn:on('connection', function(sck, response)
tmr.stop(1)
tmpError = "READ TIMEOUT"
tmr.alarm(1, 10000, 0, timeout)
conn:send(request)
end)
I created the request using the table.concat method and using a table instead of one big string. Hopefully this will help those in need.