Error in code. Lua on NodeMCU - lua

I have a motorized roller blind project.
I am following this instructable: https://www.instructables.com/id/Motorized-WiFi-IKEA-Roller-Blind/ .
I am using the code in the instructable, but I suspect tha fault is in one of these codes:
config.lua
-- file : config.lua
local module = {}
module.SSID = {}
module.SSID["ssid"] = "password"
-- example for local MQTT
--module.MQHOST = "ohab.local"
--module.MQPORT = 1883
--module.MQID = node.chipid()
--module.MQUSR = ""
--module.MQPW = ""
-- example for cloud MQTT
module.MQHOST = "192.***.*.*"
module.MQPORT = 1883
module.MQID = node.chipid()
module.MQUSR = "username"
module.MQPW = "password"
module.MQTLS = 1 -- 0 = unsecured, 1 = TLS/SSL
module.ENDPOINT = "/house/masterbedroom/rollerblind/"
module.ID = "0"
--module.SUB = "set"
module.SUB = {[module.ENDPOINT .. module.ID .. "/set"]=0,[module.ENDPOINT .. "all"]=0}
module.POST = module.ENDPOINT .. module.ID .. "/status"
return module
wifi_setup
-- file: setup.lua
local module = {}
local function wifi_wait_ip()
if wifi.sta.getip()== nil then
print("IP unavailable, Waiting...")
else
tmr.stop(1)
gpio.write(pin_led,1) --off
print("\n================== ==================")
print("ESP8266 mode is: " .. wifi.getmode())
print("MAC address is: " .. wifi.ap.getmac())
print("IP is "..wifi.sta.getip())
print("====================================")
mq.start()
end
end
local function wifi_start(list_aps)
if list_aps then
gpio.write(pin_led,0) --on
for key,value in pairs(list_aps) do
if config.SSID and config.SSID[key] then
wifi.setmode(wifi.STATION);
wifi.sta.config(key,config.SSID[key])
wifi.sta.connect()
print("Connecting to " .. key .. " ...")
--config.SSID = nil -- can save memory
tmr.alarm(1, 2500, 1, wifi_wait_ip)
end
end
else
print("Error getting AP list")
end
end
function module.start()
print("Configuring Wifi ...")
wifi.setmode(wifi.STATION);
wifi.sta.getap(wifi_start)
end
return module
Sadly I cant manage to go beyond step 4. After I have tried to download the code to the ESP8266 I only get this error:
PANIC: unprotected error in call to Lua API (wifi_setup.lua:25: bad argument #1 to 'config' (config table not found!))
I have only changed the stuff the instructable told me to change, and I have tried to look for faults myself but I cant find any... It's the first time I am using Lua, so this is totally new for me.
Hope somebody here can offer some help. Been stuck on this for days...
THIS PART OF THE PROBLEM IS SOLVED. CHECK COMMENTS FOR SOLUTION
Sadly when one problem is solved, a new one rises...
The chip is now successfully connecting to the wifi and mqtt service, but when I try to do the dry run by putting in the command 'step_move(1000,FWD,2)' nothing happens. The motor should rotate...
Also when I press the button I get a new panic error as follows:
PANIC: unprotected error in call to Lua API (button.lua:23: attempt to perform arithmetic on upvalue '?' (a nil value))
button.lua
--file button4.lua
do
-- use pin 1 as the input pulse width counter
local pin=5
local debounce = 150 --ms
local longpress = 2000 --ms
local pulse1, pulse2, du, now, trig = 1, 0, 0, tmr.now, gpio.trig
local prev_int_time, int_time, up_time = 0
local cal_steps = 100000
local cal_steps_dn = 0
local cal_steps_up = 0
local cal_state = 0 -- 0 = not calibration, 1 = calibrating down, 2 = calibrating up
state = 0 -- state: 0 = up, 1 = transition, 2 = down
gpio.mode(pin,gpio.INT)
local function pin4cb(level)
int_time = now() / 1000
if ((int_time - prev_int_time) > debounce) then
if (level == 0) then
up_time = int_time
else
if((int_time - up_time) > longpress) then
print("calibrating")
cal_state = 1
--cur_step = 100000
step_move(cal_steps,FWD,2)
else -- short press
print("short", cal_state)
if (cal_state == 2) then -- calibrated up (done)
print("calibration done")
state = 0 -- up
cur_step = 0
tot_steps = cal_steps - step_stepsleft
print("cal_steps: " .. cal_steps)
print("step_stepsleft: " .. step_stepsleft)
print("tot_steps: " .. tot_steps)
step_stop()
pins_disable()
cal_state = 0
if file.open("cfg_tot_steps.lua", "w+") then
file.write("tot_steps=" .. tot_steps .. '\n')
file.close()
end
elseif (cal_state == 1) then -- calibrated dn (switch direction)
print("calibration low point")
print(cal_steps - step_stepsleft)
step_stop()
step_move(cal_steps,REV,2)
cal_state = 2
elseif (cal_state == 0) then
if (state == 0 and step_stepsleft == 0) then -- i am up, go dowm
rollerblind.down()
-- state = 2
elseif (state == 1) then -- i am moving, do nothing
-- do nothing
elseif (state == 2 and step_stepsleft == 0) then -- i am down, go up
rollerblind.up()
-- state = 0
end
end
end
end
--print (level)
prev_int_time = int_time
end
end
gpio.trig(pin, "both", pin4cb)
end
Here is the code for the stepper.lua:
-- stepper.lua
-- code from: http://www.esp8266.com/viewtopic.php?f=19&t=2326
-- simple stepper driver for controlling a stepper motor with a
-- l293d driver
-- nodemcu pins: 0 5 6 7
stepper_pins = {1,3,2,4} -- (A-)blue, (A+)pink, (B-)yellow, (B+)orange
--stepper_pins = {1,2,3,4}
-- half or full stepping
step_states4 = {
{1,0,0,1},
{1,1,0,0},
{0,1,1,0},
{0,0,1,1}
}
step_states8 = {
{1,0,0,0},
{1,1,0,0},
{0,1,0,0},
{0,1,1,0},
{0,0,1,0},
{0,0,1,1},
{0,0,0,1},
{1,0,0,1},
}
step_states = step_states4 -- choose stepping mode
step_numstates = 4 -- change to match number of rows in step_states
step_delay = 10 -- choose speed
step_state = 0 -- updated by step_take-function
step_direction = 1 -- choose step direction -1, 1
step_stepsleft = 0 -- number of steps to move, will de decremented
step_timerid = 4 -- which timer to use for the steps
status_timerid = 2 -- timer id for posing of status messages
-- setup pins
function pins_enable()
for i = 1, 4, 1 do
gpio.mode(stepper_pins[i],gpio.OUTPUT)
end
end
function pins_disable()
-- for i = 1, 4, 1 do -- no power, all pins
for i = 2, 4, 1 do -- no power, all pins except one (to keep it in place)
gpio.mode(stepper_pins[i],gpio.INPUT)
end
end
-- turn off all pins to let motor rest
function step_stopstate()
for i = 1, 4, 1 do
gpio.write(stepper_pins[i], 0)
end
end
-- make stepper take one step
function step_take()
-- jump to the next state in the direction, wrap
step_state = step_state + step_direction
cur_step = cur_step + step_direction * FWD
if step_state > step_numstates then
step_state = 1;
elseif step_state < 1 then
step_state = step_numstates
end
-- write the current state to the pins
pins_enable()
for i = 1, 4, 1 do
gpio.write(stepper_pins[i], step_states[step_state][i])
end
-- might take another step after step_delay
step_stepsleft = step_stepsleft-1
if step_stepsleft > 0 then
-- if cur_step > 0 and cur_step < tot_steps and step_stepsleft > 0 then
tmr.alarm(step_timerid, 10, 0, step_take )
--tmr.alarm(step_timerid, 10, 0, step_take )
else
step_stopstate()
step_stop()
pins_disable()
mq.post_status()
if file.open("cfg_cur_step.lua", "w+") then
file.write("cur_step=" .. cur_step .. '\n')
file.close()
end
end
end
-- public method to start moving number of 'int steps' in 'int direction'
function step_move(steps, direction, delay)
tmr.stop(step_timerid)
step_stepsleft = steps
step_direction = direction
step_delay = delay
step_take()
end
function step_go_to(step, delay)
if step >= cur_step then
steps = step - cur_step
step_move(steps, FWD, delay)
end
if step <= cur_step then
steps = cur_step - step
step_move(steps, REV, delay)
end
end
function percent_go_to(percent, delay)
if(percent >= 0 and percent <= 100) then
step_stop()
tmr.register(status_timerid, 1000, tmr.ALARM_AUTO, function () mq.post_status() end)
tmr.start(status_timerid)
step = percent * tot_steps / 100
step_go_to(step, delay)
end
end
-- public method to cancel moving
function step_stop()
tmr.stop(step_timerid)
tmr.stop(status_timerid)
step_stepsleft = 0
step_stopstate()
end

Let's parse the error message one by one:
unprotected error in call to Lua API (wifi_setup.lua:25: bad argument #1 to 'config' (config table not found!))
Unprotected error means that you did a normal function call, as opposed to a protected call (aka pcall), which is a function call where you expect errors to occur and want to provide means to handle them. Since you did not do a protected call, Lua does not know how to handle the error and terminates right away (which is not a bad thing).
wifi_setup.lua:25 tells you the file and line at which the error occured.
bad argument #1 to 'config' means that the problem is due to the first argument passed to a function named config.
config table not found! is the error message provided by the implementer of that function.
So in summary, there is a problem with the function call wifi.sta.config(key,config.SSID[key]) as it expects a table as first argument, while you provided something different (ie. a string with the BSSID). Indeed checking with the NodeMCU docs shows that you need to pass a table to this function:
wifi.sta.config()
Sets the WiFi station configuration. [...]
Syntax
wifi.sta.config(station_config)
Parameters
station_config table containing configuration data for
station
The expected layout of the table is documented in detail on that page as well.

ComicSansMS provided a great answer the essence of which is that you need to replace
wifi.sta.config(key,config.SSID[key])
with
wifi.sta.config{ssid=key,pwd=config.SSID[key]}
Thus a standalone example could be like this:
--wifi.setmode(wifi.NULLMODE)
config = {}
config.SSID = {}
config.SSID["ssid"] = "password"
function wifi_wait_ip()
if wifi.sta.getip() == nil then
print("IP unavailable, Waiting...")
else
tmr.stop(1)
print("\n====================================")
print("ESP8266 mode is: " .. wifi.getmode())
print("MAC address is: " .. wifi.ap.getmac())
print("IP is " .. wifi.sta.getip())
print("====================================")
end
end
function wifi_start(list_aps)
if list_aps then
for key, value in pairs(list_aps) do
if config.SSID and config.SSID[key] then
wifi.setmode(wifi.STATION);
wifi.sta.config{ssid=key, pwd=config.SSID[key]}
-- wifi.sta.connect() not needed as config() uses auto-connect=true by default
print("Connecting to " .. key .. " ...")
tmr.alarm(1, 2500, 1, wifi_wait_ip)
end
end
else
print("Error getting AP list")
end
end
function start()
print("Configuring Wifi ...")
wifi.setmode(wifi.STATION)
wifi.sta.getap(wifi_start)
end
start()

Related

GMOD Lua Player death causing game to crash

I'm working on a FFA gamemode in Garry's mod where players have to survive each other and a nextbot NPC hunting them. My override of the GM:PlayerDeath adequately indicates when the current round should be over, however after SpawnGhoulInitial() is called, any deaths cause the game to crash. Im doing all this round stuff in my roundsystem_2.lua file:
-- GLOBAL VALUES
roundActive = false
roundCountdownStartTime = 0
roundStartTime = 0
phase = 1
countDownLength = 5
initialGhoulDelay = 10
spawnWepDelay = 15
ghoulMadnessDelay = 20
roundLength = 30
DEFINE_BASECLASS( "gamemode_base" )
-- vector list dictionary of nexbot spawn locations per map, starting with initial spawn location
nextbot_spawns = {gm_backrooms_classic = {Vector(-7413, 318, 93), Vector(-4270, 4079, 93), Vector(-3279, -4620, 93), Vector(-11092, -4632, 93), Vector(-11086, 4316, 93)}}
function GM:UpdateTimer(time)
net.Start("round_timer")
net.WriteInt(time, 10)
net.Broadcast()
end
-- function used to spawn the first ghoul of the round
function SpawnGhoulInitial()
local name = RandomizeNextbot()
local Ent = ents.Create(name)
if( !IsValid(Ent)) then
print("entity is not valid.")
return
end
Ent:SetPos(nextbot_spawns.gm_backrooms_classic[1])
Ent:Spawn()
print("Spawned ghoul ".. name)
PrintMessage(4, "The Ghoul has spawned. SURVIVE!")
end
-- function called to spawn the next four ghouls to sweep the map and kill off straggling players
function SpawnGhoulMadness()
local name = RandomizeNextbot()
local Ent = ents.Create(name)
if( !IsValid(Ent)) then
print("entity is not valid.")
return
end
for i=2, 5 do
Ent:SetPos(nextbot_spawns.gm_backrooms_classic[i])
Ent:Spawn()
print("Spawned ghoul madness ghoul number ".. tostring(i))
end
PrintMessage(4, "The Ghoul has summoned some friends. RUN!")
end
-- function called to spawn in weapons for the players
function SpawnWeps()
for k, v in pairs(player.GetAll()) do
if (v:Alive()) then
v:Give("weapon_crowbar", false)
v:Give("weapon_pistol", false)
end
end
PrintMessage(4, "You've found supplies. Kill other survivors!")
end
function CountAlivePlayers()
local players = player.GetAll()
local alive = 0
for i = 1, #players do
local player = players[i]
if (player:Alive()) then
alive = alive + 1
end
end
return alive
end
-- function called to give us a random nextbot to spawn in
function RandomizeNextbot()
local names = {"table of nextbot names here, not posting them for stackoverflow because its embarrassing"}
local random_nextbot = math.random(#names)
return names[random_nextbot]
end
-- function called under the conditions that the round has ended, will start the next round.
function GM:EndRound(winner)
-- clear the map, clear sounds, print winner on screen, allow the respawning
if (winner == nil) then
PrintMessage(4, "Nobody Wins!")
else
PrintMessage(4, winner:Nick().." Wins!")
end
game.CleanUpMap(false, {})
RunConsoleCommand("stopsound")
timer.Remove("RoundTimer")
timer.Create("roundEndTimer", 5, 1, function()
for k, v in pairs(player.GetAll()) do
if (v:Alive()) then
v:StripWeapons()
v:KillSilent()
end
end
net.Start("round_active")
net.WriteBool(false)
net.Broadcast()
roundActive = false
end)
end
function GM:StartCountDownTimer(repetitions)
self:UpdateTimer(repetitions)
timer.Create("CountdownTimer", 1, repetitions, function()
repetitions = repetitions - 1
self:UpdateTimer(repetitions)
end)
end
function GM:StartRoundTimer(repetitions)
self:UpdateTimer(repetitions)
timer.Create("RoundTimer", 1, repetitions, function()
repetitions = repetitions - 1
self:UpdateTimer(repetitions)
if (repetitions <= 0) then
self:EndRound(nil)
end
end)
end
--overriding playerdeath function for best death results
function GM:PlayerDeath(victim, inflictor, attacker)
BaseClass.PlayerDeath(self, victim, inflictor, attacker)
local players = player.GetAll()
local lastAlive = NULL
for i = 1, #players do
local p = players[i]
if (p:Alive()) then
lastAlive = p
end
end
if (CountAlivePlayers() <= 1 && roundActive) then
self:EndRound(lastAlive)
end
end
-- hook for starting the round on player spawn if there are enough players.
-- countdown for round start will restart if a new player spawns in during the countdown.
function GM:DoRoundSpawn()
--every time player joins reset the countdown
if (CountAlivePlayers() > 1) then
roundCountdownStartTime = CurTime()
self:StartCountDownTimer(countDownLength)
end
end
-- The main timing of the round. Checks against curtime to find when
-- specific events in the round need to happen.
function GM:Think()
-- if there is more than one player, and the time elapsed is a full 30 sec, start round
if (CountAlivePlayers() > 1 && roundActive == false) then
if ((roundCountdownStartTime + countDownLength) < CurTime()) then
roundStartTime = CurTime()
roundActive = true
phase = 1
net.Start("round_active")
net.WriteBool(true)
net.Broadcast()
timer.Remove("CountdownTimer")
self:StartRoundTimer(roundLength)
PrintMessage(4, "The Round has begun. Last player to survive wins!")
end
end
-- if the round is active, do this stuff
if (roundActive == true) then
-- if its been 10 sec since round start, spawn the ghoul
if ((roundStartTime + initialGhoulDelay) < CurTime() && phase == 1) then
SpawnGhoulInitial()
phase = 2
end
-- if its been 30 sec since round start, spawn in weapons for players
if((roundStartTime + spawnWepDelay) < CurTime() && phase == 2) then
SpawnWeps()
phase = 3
end
-- if its been 120 sec (2 minutes) since round start, begin the ghoul madness
if((roundStartTime + ghoulMadnessDelay) < CurTime() && phase == 3) then
SpawnGhoulMadness()
phase = 4
end
end
end
However, I do call my GM:DoRoundSpawn function in my init.lua file:
AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
include("shared.lua")
include("roundsystem_2.lua")
util.AddNetworkString("round_timer")
util.AddNetworkString("round_active")
roundActive = false
playerSpawns = {--table of spawnpoints, too much to paste for stackoverflow}
function GM:PlayerSpawn(ply)
ply:SetGravity(.85)
ply:SetMaxHealth(100)
ply:SetupHands()
ply:SetWalkSpeed(250)
ply:SetRunSpeed(450)
ply:SetModel("models/player/Kleiner.mdl")
local random_spawn = math.random(#playerSpawns)
ply:SetPos(playerSpawns[random_spawn])
--this section basically will make it so if someone spawns
--in while the round is active, they will stay dead until
--the round is no longer active
print("Player: " .. ply:GetName() .. " has spawned!")
if (roundActive == true) then
ply:KillSilent()
return
end
self:DoRoundSpawn()
end
function GM:PlayerDeathThink(ply)
if (roundActive == false) then
ply:Spawn()
return true
else
return false
end
end
I've combed through both of these files a couple times over and I cant for the life of me figure out why deaths don't break the game until after the nextbot spawn happens. Is there something here I'm missing?

How to Put Data Into a Table From an Existing File

I have a program for a turtle in ComputerCraft that is meant to be a storage unit. I input an item, and it should read a file to find where to put the item. If there is a new item, it adds the information to a file.
When I do
blockTypes = {}
local file = fs.open(blockTable","r")
line = file.readAll()
table.insert(blockTypes,line)
file.close()
The information gets put in as a string. This means that I can't do
print(blockTypes[1][1])
And recieve the value that would normally be in that position.
Here's my code:
blockAmount = 3
highestVal = {2,0,5}
blockTypes = {}
function addBlock()
local file = fs.open("blockTable", "a")
file.write(name)
file.write(" = {")
file.write(highestVal[1])
file.write(",")
file.write(highestVal[2])
file.write(",")
file.write(highestVal[3])
file.writeLine("};")
file.close()
end
function getBlock()
local file = fs.open("blockTable","r")
line = file.readAll()
table.insert(blockTypes,line)
file.close()
end
--This was used to test before implementing the new file system that I am trying to get to work
blockTypesOld = {
log = {2.0,0,1};
dirt = {2,0,2};
cobblestone = {2,0,3};
iron_ingot = {2,0,4};
planks = {2,0,5};
}
pos = {0,0,0}
looking = 0
function fuel()
if turtle.getFuelLevel() < 20 then
turtle.select(16)
turtle.refuel(1)
end
end
function left()
turtle.turnLeft()
looking = looking - 1
if looking < 0 then
looking = 3
end
end
function right()
turtle.turnRight()
looking = looking + 1
if looking > 3 then
looking = 0
end
end
function forward()
fuel()
if turtle.forward() then
if looking == 0 then
pos[1] = pos[1] - 1
elseif looking == 1 then
pos[3] = pos[3] - 1
elseif looking == 2 then
pos[1] = pos[1] + 1
elseif looking == 3 then
pos[3] = pos[3] + 1
else
end
end
end
function up()
fuel()
turtle.up()
pos[2] = pos[2] + 1
end
function down()
fuel()
turtle.down()
pos[2] = pos[2] - 1
end
function goHome()
while pos[3] > 0 do
while looking > 1 do
left()
end
forward()
end
while pos[2] > 0 do
down()
end
while pos[1] > 0 do
while looking > 0 do
left()
end
forward()
end
end
function goTo(a,b,c)
goHome()
while looking < 2 or looking > 2 do
right()
end
for i = pos[1],a do
forward()
end
while looking > 3 or looking < 3 do
right()
end
for i = pos[3],c do
forward()
end
for i = pos[2],b do
up()
end
while looking < 2 or looking > 2 do
left()
end
end
while true do
turtle.select(15)
while not turtle.suck() do
sleep(1)
end
itemDetails = turtle.getItemDetail()
--Finding what mod item is from and removing corresponding labels--
--EX: "minecraft:log" becomes "log"
if itemDetails.name:match("^ae2stuff:(.+)$") then
name = itemDetails.name:match("^ae2stuff:(.+)$")
elseif itemDetails.name:match("^minecraft:(.+)$") then
name = itemDetails.name:match("^minecraft:(.+)$")
elseif itemDetails.name:match("^appliedenergistics2:(.+)$") then
name = itemDetails.name:match("^appliedenergistics2:(.+)$")
elseif itemDetails.name:match("^buildcraftbuilders:(.+)$") then
name = itemDetails.name:match("^buildcraftbuilders:(.+)$")
elseif itemDetails.name:match("^forge:(.+)$") then
name = itemDetails.name:match("^forge:(.+)$")
elseif itemDetails.name:match("^buildcraftenergy:(.+)$") then
name = itemDetails.name:match("^buildcraftenergy:(.+)$")
elseif itemDetails.name:match("^buildcraftfactory:(.+)$") then
name = itemDetails.name:match("^buildcraftfactory:(.+)$")
elseif itemDetails.name:match("^buildcraftsilicon:(.+)$") then
name = itemDetails.name:match("^buildcraftsilicon:(.+)$")
elseif itemDetails.name:match("^buildcrafttransport:(.+)$") then
name = itemDetails.name:match("^buildcrafttransport:(.+)$")
elseif itemDetails.name:match("^buildcraftcore:(.+)$") then
name = itemDetails.name:match("^buildcraftcore:(.+)$")
elseif itemDetails.name:match("^buildcraftlib:(.+)$") then
name = itemDetails.name:match("^buildcraftlib:(.+)$")
elseif itemDetails.name:match("^computercraft:(.+)$") then
name = itemDetails.name:match("^computercraft:(.+)$")
elseif itemDetails.name:match("^enderstorage:(.+)$") then
name = itemDetails.name:match("^enderstorage:(.+)$")
elseif itemDetails.name:match("^extracells:(.+)$") then
name = itemDetails.name:match("^extracells:(.+)$")
elseif itemDetails.name:match("^thermaldynamics:(.+)$") then
name = itemDetails.name:match("^thermaldynamics:(.+)$")
elseif itemDetails.name:match("^thermalexpansion:(.+)$") then
name = itemDetails.name:match("^thermalexpansion:(.+)$")
elseif itemDetails.name:match("^thermalfoundation:(.+)$") then
name = itemDetails.name:match("^thermalfoundation:(.+)$")
elseif itemDetails.name:match("^tconstruct:(.+)$") then
name = itemDetails.name:match("^tconstruct:(.+)$")
elseif itemDetails.name:match("^webdisplays:(.+)$") then
name = itemDetails.name:match("^webdisplays:(.+)$")
elseif itemDetails.name:match("^ironchest:(.+)$") then
name = itemDetails.name:match("^ironchest:(.+)$")
else
print("ERROR MOD NOT FOUND")
end
getBlock()
local elem = blockTypes[name]
--Gets fuel from fuel chest
right()
turtle.select(16)
turtle.suck(5)
turtle.select(15)
if elem then
--If the item does exist, the turtle goes to it's chest and places it in the chest
goTo(elem[1]-1, elem[2]-1, elem[3]-1)
turtle.select(15)
turtle.drop()
goHome()
right()
turtle.select(16)
turtle.drop()
turtle.select(15)
left()
else
--Creates information for new item--
addBlock()
blockAmount = blockAmount + 1
highestVal[3] = highestVal[3] + 1
if highestVal[3] > 5 then
highestVal[3] = 1
highestVal[2] = highestVal[2] + 1
end
if highestVal[2] > 4 then
highestVal[2] = 0
highestVal[1] = highestVal[1] + 2
end
blockTypes[blockAmount] = name
blockTypes[name] = {highestVal[1],highestVal[2],highestVal[3]}
local elem = blockTypes[name]
left()
turtle.select(15)
turtle.drop()
right()
turtle.select(16)
turtle.suck(2)
fuel()
turtle.drop()
goTo(1,-1,4)
--Crafts an Iron Chest for the New Item
for i = 1,3 do
turtle.select(i)
turtle.suck(1)
end
turtle.select(5)
turtle.suck(1)
turtle.select(7)
turtle.suck(1)
for i = 9,11 do
turtle.select(i)
turtle.suck(1)
end
turtle.select(6)
turtle.craft()
goTo(1,-1,3)
for i = 1,3 do
turtle.select(i)
turtle.suck(1)
end
turtle.select(5)
turtle.suck(1)
turtle.select(7)
turtle.suck(1)
for i = 9,11 do
turtle.select(i)
turtle.suck(1)
end
turtle.select(1)
turtle.craft()
goHome()
right()
turtle.select(16)
turtle.suck(5)
goTo(elem[1]-1, elem[2]-1, elem[3]-1)
turtle.select(1)
turtle.place()
goHome()
end
end
The file containing the coordinates for the items is called blockTable and consist of this:
--blockName = {xCoord,yCoord,zCoord};--
oak_stairs = {2.0,0.0,5.0};
iron_ingot = {2.0,0.0,4.0};
turtle = {2.0,0.0,5.0};
When I put a new item in, it registers the item, makes a chest, and puts it in. When I put the same item in, it goes straight to the chest without crafting a new one. However, when I reboot the turtle and put the same item in, it creates another new chest and tries to place it for the new item. I want it to be able to keep its information even after a reboot. I've been struggling with this program for a while now, any help will be appreciated. Thank you!
Reading / Writing the entire file every time you add a new block seems a bit messy, specially if you consider that you might restart the turtle in the process of writing the new file and end up with half your data being lost.
You do have a file system, so why not use that instead? Create a directory and write a new file for every block name, then save the coordinates in it. This also allows you to store more than one chest for each block-type, in case you get filthy rich happens sooner than you'd expect when using turtles to excavate huge chunks of the map.
I just realized that I didn't actually answer the question, so here goes that:
When you have a file file, you can easily read a single line with file.readLine(). This returns either the line it read as a string, or nil if you're at the end of a file. Assuming you just write three coordinates separated by spaces after the block name, you can then parse them into a table like this:
local file = fs.open('chests')
local chests = {}
while true do
local line = file.readLine()
if line then
local name, x, y, z = line:match("(%a+) ([%d.+-]+) ([%d.+-]+) ([%d.+-]+)")
chests[name] = {
tonumber(x),
tonumber(y),
tonumber(z)
}
else
break
end
end
Wrap this all up in a function for extra neatness.
Side note:
Computercraft is somewhat inconvenient for dealing with files. In regular Lua I would have done this:
local function map(f, elem, ...)
if elem then return f(elem), map(f, ...) end
end
local function readchests(file)
local res = {}
for line in io.open(file):lines() do
local name, x, y, z = line:match("(%a+)"..(" [%d.-+]+"):rep(3))
res[name]={map(tonumber, x, y, z)}
end
return res
end
If you want to keep the format of storing data as valid Lua code, you could read the entire file with file.readAll(), add "return {" at the start and "}" at the end and then just load and execute that string. In general I don't recommend that though. Id't be better if the entire file was just valid Lua code that you can read and run.
While it is not advisable to read the file every time you add to a new block as stated by DarkWiiPlayer.
To answer your question, you can read the table in and the execute the code from the string using loadstring.
To do this using the existing strings in the file you provided there is some extra work that is needed.
This extra work can be removed by adjusting your the string in the file.
blockTypes = {}
line = "oak_stairs = {2.0,0.0,5.0};"
table_name = line:match("[%a_]+%s")
do_line = assert(loadstring('local ' .. line .. ' return ' .. table_name))
table = do_line();
table.insert(blockTypes,table)
Here we get the name of the table being loaded using match.
Create a string for loadstring that builds and returns the table.
Execute the loaded string, and insert it to the blockTypes.
Alternatively you can adjust the file you are saving to that it acts as a module.
local blocktypes = {
oak_stairs = {2.0,0.0,5.0},
iron_ingot = {2.0,0.0,4.0},
turtle = {2.0,0.0,5.0},
}
return blockTypes
Then you would this to load the data:
blockTypes = require("blockTable")

How to Call a Table from a Variable in Lua?

I'm creating a program for a turtle in ComputerCraft. The program is going to make the turtle control a warehouse of storage for my items in the game. It will check what item I put in, then it will figure out the chest's location, go there, and dump it in. I am storing the locations of each chest in a table. For example:
cobblestone = {2,0,1}
That tells the turtle that the cobblestone chest is stored at position x=2 y=0 and z=1. To get the turtle to tell what it needs to store, it does:
itemDetails = turtle.getItemDetail()
name = string.gsub(itemDetails.name, "minecraft:", "")
This gets the turtle to get the details of the item. It then sets a variable to be the name of the item minus the minecraft: at the beginning of it (I can't have a variable called "minecraft:cobblestone"). I don't know how to use this variable to call the table and find its position for the turtle to go to it. If anybody can help, I would appreciate any of the help. Thanks in advance!
Also, just a heads up, the code is still set up for debugging and testing purposes. The setup is a turtle, with an input chest in front of it, a fuel chest to the right, and the warehouse behind it.
I've tried doing:
cobblestone = {2,0,1}
--Putting a piece of cobblestone in--
itemDetails = turtle.getItemDetail()
--Returning name of "minecraft:cobblestone"--
name = string.gsub(itemDetails.name, "minecraft:", "")
print name[1]
So far this hasn't worked.
pos = {0,0,0}
looking = 0
cobblestone = {2,0,1}
function fuel()
if turtle.getFuelLevel() < 20 then
turtle.select(16)
turtle.refuel(1)
end
end
function left()
turtle.turnLeft()
looking = looking - 1
if looking < 0 then
looking = 3
end
print(looking)
end
function right()
turtle.turnRight()
looking = looking + 1
if looking > 3 then
looking = 0
end
print(looking)
end
function forward()
fuel()
if turtle.forward() then
if looking == 0 then
pos[1] = pos[1] - 1
elseif looking == 1 then
pos[3] = pos[3] - 1
elseif looking == 2 then
pos[1] = pos[1] + 1
elseif looking == 3 then
pos[3] = pos[3] + 1
else
print("wot")
end
end
end
function up()
fuel()
turtle.up()
pos[2] = pos[2] + 1
end
function down()
fuel()
turtle.down()
pos[2] = pos[2] - 1
end
function goHome()
while pos[3] > 0 do
while looking > 1 do
left()
end
forward()
end
while pos[2] > 0 do
down()
end
while pos[1] > 0 do
while looking > 0 do
left()
end
forward()
end
end
while true do
turtle.select(1)
while not turtle.suck() do
sleep(1)
end
itemDetails = turtle.getItemDetail()
name = string.gsub(itemDetails.name, "minecraft:", "")
print(name)
while looking < 2 or looking > 2 do
left()
end
for i = pos[1],name[1]-1 do
forward()
end
while looking > 3 or looking < 3 do
right()
end
for i = pos[3],name[3]-1 do
forward()
end
for i = pos[2],name[2]-1 do
up()
end
while looking < 2 or looking > 2 do
left()
end
turtle.select(1)
turtle.drop()
goHome()
end
I want to be able to do:
cobblestone = {2,0,1}
--Putting a piece of cobblestone in--
itemDetails = turtle.getItemDetail()
--Returning name of "minecraft:cobblestone"--
name = string.gsub(itemDetails.name, "minecraft:", "")
print name[1]
and have the turtle print the same thing as
cobblestone[1]
which would be 2. However, it returns with nil.
Currently, your cobblestone is global, what is bad for this example. At first, store all elements like this:
local myelems = {
cobblestone = {2,0,1};
grass = {3,1,2};
}
and so on. Use local variables when you can, it's good practice in programming at all.
So let's look at your code:
-- Returning name of "minecraft:cobblestone"
local name = string.gsub(itemDetails.name, "minecraft:", "")
print(name[1])
What it does? (I've changed it a bit)
At first,string.gsub look like an overkill here, use string.match instead:
local name = itemDetails.name:match("^minecraft:(.+)$")
Here itemDetails.name:match is same as calling string.match with itemDetails.name as first argument. It can be used for all string functions.
So here name is string variable. Indexing strings in different languages may have different results. In lua in actually provide access to all string functions to make calls to them easier (as above). There is no such function as string[1], so you get nil. However, now we have table myelems in which keys are strings and values are your tables.
Full code:
-- Table for all elements
local myelems = {
cobblestone = {2,0,1};
grass = {3,1,2};
}
-- Putting a piece of cobblestone in
local itemDetails = turtle.getItemDetail()
--Returning name of "minecraft:cobblestone"
local name = itemDetails.name:match("^minecraft:(.+)$")
-- Get our table
local elem = myelems[name]
if elem then
print(elem[1], elem[2], elem[3])
end

lua error: attempt to index field 'frames'(a nil value)(but self and lacal errors)

I'm trouble to understand self and local value.
Here is my asteroid.lua. I created local asteroid inside :create and was trying to use it other function but it is not working correctly.
-- Class Declaration
Asteroid = {}
Asteroid.__index = Asteroid
function Asteroid:create()
local asteroid = {}
setmetatable(asteroid, Asteroid)
-- Animation Data
asteroid.frames = {}
asteroid.currentFrame = 1
asteroid.frameDuration = 0.04 -- 0.016
asteroid.frameTimeRemaining = asteroid.frameDuration
asteroid.x = 0
asteroid.y = 0
return asteroid
end
function Asteroid:init()
-- Use a loop to load a bunch of files!
for index= 0, 15 do
-- Use logic to build the filename...
file = 'art/large/a100'
-- Take into account the extra 0
if index < 10 then
file = file .. '0'
end
-- Add the file number and then .png
file = file .. tostring(index) .. '.png'
-- Load the file... (we'll use lua's 1 index to be kind)
self.frames[index + 1] = love.graphics.newImage(file)
if self.frames[index + 1] ~= nil then
print('Loaded frame ' .. tostring(index + 1))
end
end
-- Set the velocity randomly!
self.velocity = {}
self.velocity.x = math.random(-76.0, 76.0)
self.velocity.y = math.random(-76.0, 76.0)
end
Asteroid.updateAnimation = function(deltaTime)
-- Catch variable into 'easy-to-type' one
local ftr = Asteroid.frameTimeRemaining
THIS PART(local ftr) IS MY PROBLEM: ATTEMPT TO INDEX A NIL VALUE.
I tried different ways like self.frameTimeRemaing but I couldn't figure out.
Is anyone know how I can fix this local?
-- Subtract time
ftr = ftr - deltaTime
-- If the frame is over...
if ftr < 0 then
Asteroid.nextFrame()
Asteroid.frameTimeRemaining = Asteroid.frameDuration
else
Asteroid.frameTimeRemaining = ftr
end
end
Without seeing the rest of the code, I can only guess. Try this:
function Asteroid:updateAnimation(deltaTime)
-- use self instead of Asteroid in this function
end

Yet another module memory loss issue

This problem is driving me nuts. Its consumed way too much time and I could do with some help, please!
Basically, Im downloading weather data from my server using Get(). This works well. All I want to do is display it on a 16x2 i2c lcd screen, however I am plagued with 'out of memory' issues.
I have tried several modules provided by the community and all seem to suffer the same problems, so Im convinced the problem lays with my code / calling convention.
Im currently using module lcd.lua, a slight variation on Vladimir Dronnikov's offering. and calling it in the prescribed manor
<pre>
i2c.setup(0, 3, 4, i2c.SLOW)
lcd = dofile("lcd.lua")()
</pre>
the calling lua script is liberally sprinkled with print statements:
<pre>
lcd.put(lcd:locate(0, 0),"Mast Temp: ")
</pre>
My file structure looks like this
<pre>
Init.lua -- opportunity to prevent script execution. Calls wifiConnect
wifiConnect.lua -- connects to wifi and calls dofile(DetServerData())
GetServerData.lua -- Fetches data and displays on lcd module.
</pre>
When the wifiConnect calls dofile against GetServerData it will usually fall over with 'not enough memory' sometimes the script will run 2 or 3 times before encountering 'out of memory' errors.
I have however had it run 8000-9000 times before falling over. (only one or twice.)
If I allow wifiConnect to run GetFileData it will stop with
<pre>
PANIC: unprotected error in call to Lua API (not enough memory)
</pre>
loading GetServerData seems to consume 13816 bytes on the heap.. leaving 18800. surely enough to run.
I have tried require(getServerData), slightly different numbers, same result.
GetServerData is as follows
-- 0 DS18B20_temp_C
-- 1 WindSpeed_kmph
-- 2 WindBearing;
-- 3 WindDir
-- 4 BMP_temp
-- 5 BMP_pressure
-- BMP_altitude --------------REMOVED
-- 6 DHT22_temperature
-- 7 DHT22_humidity
-- DH22_dewPoint--------------REMOVED
-- DHT22_heatIndex--------------REMOVED
local WDPin = 1
local LCDscreenPin = 2
local LCDmaxScreen = 4
local cycleCount=0
local WDogLED = false
local dataTable={} ; i=1
local connClosed = true
local LCDpageID = 1
function GetData()
if connClosed == true then
local conn=net.createConnection(net.TCP, 0)
print("Open connection...")
WatchDog()
if conn == nil then
print("Connection error")
else
conn:connect(80,'31.220.16.114')
connClosed = false
tmr.alarm(4,3000,0,ResetConn)
conn:send("GET /test.txt? HTTP/1.1\r\n")
conn:send("Host: theoldschool.esy.es\r\n")
conn:send("Accept: */*\r\n")
conn:send("User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n")
conn:send("\r\n")
conn:on("disconnection", function(conn)
connClosed = true
WatchDog()
print("Disconnected...")
end) -- on:"disconection"
conn:on("receive",function(conn,payload)
cleanData = extractWeatherData(payload)
DbugGarbageCollect()
fillDataTable(cleanData,'\r\n')
for k,v in pairs(dataTable) do print(k,v) end
cycleCount = cycleCount +1
print(cycleCount)
end)-- on recieve
end-- if conn == nil
end -- if connClosed
end--GetData
function ResetConn()
print("Stuck")
connClosed = true
end -- ResetConn
function fillDataTable(inputstr, sep)
local i=0
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
dataTable[i] = str
i = i + 1
end--for
end--splitCmd
function stripMarkers(str, chrs)
local s = str:gsub("["..chrs:gsub("%W","%%%1").."]", '')
return s
end
function extractWeatherData (payload)
local startChar = string.find(payload, '%')
local s = string.sub(payload, startChar,endChar)
s = stripMarkers(s, "")
return s
end -- extractWeatherData
function WatchDog()
if WDogLED == false then
WDogLED = true
gpio.write(WDPin, gpio.HIGH)
else
WDogLED = false
gpio.write(WDPin, gpio.LOW)
end --if
end--WatchDog
function DbugGarbageCollect()
local before = collectgarbage("count")
collectgarbage()
print(before - collectgarbage("count"))
print(node.heap())
end --DbugGarbageCollect
function LCDdisplay()
lcd.clear()
if LCDpageID == 1 then
lcd.put(lcd:locate(0, 0),"Mast Temp: ")
lcd.put(lcd:locate(1, 0),"Shade Tmp: ")
elseif LCDpageID == 2 then
lcd.put(lcd:locate(0, 0),"Wind Dir: ")
lcd.put(lcd:locate(1,0),"Wind Knts: ")
elseif LCDpageID == 3 then
lcd.put(lcd:locate(0, 0),"Pressure: ")
lcd.put(lcd:locate(1, 0),"BMP Temp: ")
elseif LCDpageID == 4 then
lcd.put(lcd:locate(0, 0),"DHT Temp: ")
lcd.put(lcd:locate(1, 0),"Humidity: ")
else
lcd.put(lcd:locate(0, 0),"SCREEN ERROR 1")
end --if
--updateLCDDisplay()
collectgarbage()
end --LCDdisplay
function updateLCDDisplay()
if LCDpageID == 1 then
lcd.put(lcd:locate(0, 11),dataTable[0])
-- LCDScreenOP.lcdprint(dataTable[0],1,11)
-- LCDScreenOP.lcdprint(dataTable[7],2,11)
elseif LCDpageID == 2 then
--LCDScreenOP.lcdprint(dataTable[2],1,10)
-- LCDScreenOP.lcdprint(dataTable[3],1,14)
-- LCDScreenOP.lcdprint(dataTable[1],2,11)
elseif LCDpageID == 3 then
-- LCDScreenOP.lcdprint(dataTable[5],1,10)
-- LCDScreenOP.lcdprint(dataTable[4],2,10)
elseif LCDpageID == 4 then
--LCDScreenOP.lcdprint(dataTable[6],1,10)
-- LCDScreenOP.lcdprint(dataTable[7],2,10)
else
-- LCDScreenOP.cls()
-- LCDScreenOP.cursor(0)
-- LCDScreenOP.lcdprint("U/D ERROR",1,0)
end --if
-- package.loaded.LCDScreenOP = nil
DbugGarbageCollect()
end -- updateDisplay
function LCDScreenChange(level)
LCDpageID = LCDpageID + 1
if LCDpageID == LCDmaxScreen + 1 then LCDpageID = 1 end
LCDdisplay()
end-- buttonpress
--============================ CODE ==============================
i2c.setup(0, 3, 4, i2c.SLOW)
lcd = dofile("lcd.lua")()
print ("here")
gpio.mode(WDPin, gpio.OUTPUT)
gpio.write(WDPin, gpio.HIGH)
gpio.mode(LCDscreenPin, gpio.INPUT, gpio.PULLUP)
DbugGarbageCollect()
gpio.trig(LCDscreenPin, "down",LCDScreenChange)
tmr.alarm(2,1500,1,GetData)
and a screen grab of the esplorer window looks like this when loading GetServerData and allowing it to run
<pre>
abort = true
startup aborted
=node.heap()
>**32616**
file.remove("GetServerData.lua");
file.open("GetServerData.lua","w+");
w = file.writeline
-- Above code in here !!
file.close();
dofile("GetServerData.lua");
>not enough memory
dofile('GetServerData.lua')
not enough memory
=node.heap()
>**18800**
dofile('GetServerData.lua')
>not enough memory
</pre>
ANY help would be gratefully received and any other info that may help will be happily supplied
Many Thanks
Philip
That's quite a lot of code to comb through. It'd be a more helpful if you provided a Minimal, Complete, and Verifiable example which will reproduce the problem (-> Minimal).
A quick glance revealed 2.5 issues but there are likely some more.
Closed upvalues
conn:on("receive",function(conn,payload) leaks because the callback parameter shouldn't be conn but something else instead. See https://github.com/nodemcu/nodemcu-firmware/issues/194#issuecomment-184817667 or https://stackoverflow.com/a/37379426/131929
Sending is asynchronous
conn:send shouldn't be called in quick succession because each of those calls is asynchronous and thus the invocation order is not guaranteed. See the net.socket:send documentation for details and a nice example.
Consider http module
Using http.get may help you reduce the complexity in your code.

Resources