This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Kizz's profile picture

Simplistic Modem API Listener

Started by Kizz, 10 July 2014 - 02:47 PM
Kizz #1
Posted 10 July 2014 - 04:47 PM
Greetings,

I'm rather new to the community here, and wouldn't be too surprised if I were to get trout slapped by a professional developer looking to prove to the world that I'm an idiot. In all fairness, I am an idiot.

I figure posting my code here will allow me to move into a greater realm of CC systems design as I get further into the yogspack and continue to grow my facility.

So here it goes, my first, nooby, incorrect syntax post:

This isn't specifically an API, more over a solution for those just getting into the meat and potatoes of networking in CC.

First, let me explain it's use.

Simply put, I needed a way to get the current status of my various servers and control computers around my facility. I have computers controlling power output, emergency power storage, reactors, ME computers, and various other systems I plan to add.

For the start, I have a control room, with a large screen (GO NASA!). This screen is controlled by a screen server, and pings the various control computers to check their statuses. I decided to use the modem API because it was new and had fancy channels, but as I got into it, I found rednet may have been a better choice. Either way, I am sticking with modem.

My first task in the system was to get a solution built that would ping the control servers, of which I have six.

Now, to any beginner, this isn't as easy as it seems. It can be challenging to grasp the idea of networking, sending data and receiving data. I often get stuck at trying to receive data. It will wait and wait and wait… and wait… forever. I needed to find a way to receive data whilst continuing to control screens and communicate with other systems. The code below is my first step in this direction. It isn't perfect, but it's, what I believe, a great step in the right direction.

Here we go:


--Modem Communication Numbering
-- 1/2 Screen to Server 1
-- 2/1 Server 1 to Screen
--3/4 Screen to Power Emergency PC
-- 4/3 Pwr Em. PC to Screen

local modem = peripheral.wrap('back')
local screen = peripheral.wrap('top')

function ps(n) --similar to print
	screen.write(n)
	x,y=screen.getCursorPos()
	y=y+1
	screen.setCursorPos(1,y)
end

function w(n) --write same line
	x1,y1=screen.getCursorPos()
	screen.setCursorPos(1,y1)
	screen.clearLine()
	screen.write(n)
end

function reset() --places cursor to left again
	x1,y1=screen.getCursorPos()
	screen.setCursorPos(1,y1)
end

function send(chan,reply,m) --transmits a message
	modem.transmit(chan,reply,m)
end

function get(chan,tmout)--listener
	if tmout==nil then
		tmout=100000000000000000000000000000 --change to call event w/o timer
	end
	modem.open(chan) --opens port
	chk=modem.isOpen(chan) --checks to make sure it is open
	if chk==true then --will set listener
		--mm = os.pullEvent("modem_message")
		timeout = os.startTimer(tmout)
		while true do
		event = {os.pullEvent()}
		if event[1] == "modem_message" then
			--print'Worked'
			--print(event[1].." "..event[2].." "..event[3].." "..event[4].." "..event[5].." "..event[6])
			msg=event[5]
			returnchan=event[4]
			chan=event[3]
			success=1
	   break
	   elseif event[1] == "timer" and event[2] == timeout then
			success=0
	   break
	   end
	   end
	end
	if chk==false then
	--send error# to screen
	print('Error 100: Failed to Bind Port '..chan..'.')
	end
end

function chksvr(sendchan,svrnum,gettmout)
	send(sendchan,2,"ping")
	get(2,gettmout)
	if success==1 then
		screen.setCursorPos(1,svrnum+2)
		w("Server "..svrnum..": Online")
	end
	if success==0 then
		screen.setCursorPos(1,svrnum+2)
		w("Server "..svrnum..": DOWN")
	end
   --sleep(5)
end


--main
screen.clear()
screen.setCursorPos(1,1)

ps("Kizz Tower Control: ONLINE")
ps("")

--MAIN LOOP for REFRESH

while true do
--Screen choice chk


--Power system checks/screen



--Server Checks/Screen
chksvr(1,1,2)
chksvr(3,2,2)
chksvr(5,3,2)
--loop refresh pause (removed due to timer in get function)

end

--end main

This link will make it easier to read: http://pastebin.com/RjdXMsbS

Alright, so that is my main screen, it will send a ping to all 6 servers and listen for a few seconds for a return ping. If there is no ping return, if assumes it is offline, and moves to the next server.

Let's look at the code a little closer and move away from some of these non-interesting functions…


First let's take a look at the send function, pretty simple:


function send(chan,reply,m) --transmits a message
	modem.transmit(chan,reply,m)
end

It's pretty basic, really just a way for me to simplify typing in my main body, nothing more really. chan is the desired channel to send, reply is the response channel, and m represents the message to send.


Next, the more complicated get function:


function get(chan,tmout)--listener
	if tmout==nil then
		tmout=100000000000000000000000000000 --change to call event w/o timer
	end
	modem.open(chan) --opens port
	chk=modem.isOpen(chan) --checks to make sure it is open
	if chk==true then --will set listener
		--mm = os.pullEvent("modem_message")
		timeout = os.startTimer(tmout)
		while true do
		event = {os.pullEvent()}
		if event[1] == "modem_message" then
			--print'Worked'
			--print(event[1].." "..event[2].." "..event[3].." "..event[4].." "..event[5].." "..event[6])
			msg=event[5]
			returnchan=event[4]
			chan=event[3]
			success=1
	   break
	   elseif event[1] == "timer" and event[2] == timeout then
			success=0
	   break
	   end
	   end
	end
	if chk==false then
	--send error# to screen
	print('Error 100: Failed to Bind Port '..chan..'.')
	end
end

So one step at a time…

First we decide if the timer is needed or not. A listener may never need to timeout, but a ping may only want to listen for a few seconds.
I was lazy and just put a large number in. I will eventually change this to a version without a timer at all. Just lazy.

Next we open the channel we wish to listen on, and check that it opened… If it failed to open we throw an error. If it succeeds, we then begin the message listening. We open an event named event, upon opening the event, we listen for two things. First, was the event modem_message? If so we then define the message and break the loop. The ping was received, yay! However, assuming a timer was set, if we get the timer event, our timer has timed out and it is time to move on, a ping was not received. Boo :(/>

… Will continue when I return from lunch :)/>)

Alright… back :D/>… So we've used a timer to limit our listening time (if we choose to).

So how do we use get? In the end, it's really simple, get(chan,tmout) where chan represents channel and tmout represents the time, in seconds, we want to listen before timing out and moving on.

That concludes send and get. We now have some functions to allow us to get and send messages.

But how can we use this?

I specifically built this function:


function chksvr(sendchan,svrnum,gettmout)
    send(sendchan,2,"ping")
    get(2,gettmout)
    if success==1 then
	    screen.setCursorPos(1,svrnum+2)
	    w("Server "..svrnum..": Online")
    end
    if success==0 then
	    screen.setCursorPos(1,svrnum+2)
	    w("Server "..svrnum..": DOWN")
    end
   --sleep(5)
end

It will send a ping on the chosen channel, and offer a response channel, then listen for "gettmout" amount of time to get a return ping. If it gets a return ping, it uses my writing function to write the status to the big screen. If it times out, it will display that server x is down.

The combination to make this revolve through several servers can be seen in my main program:


screen.clear()
screen.setCursorPos(1,1)
ps("Kizz Tower Control: ONLINE")
ps("")
--MAIN LOOP for REFRESH
while true do
--Screen choice chk

--Power system checks/screen

--Server Checks/Screen
chksvr(1,1,2) --channel,server,timeout
chksvr(3,2,2)
chksvr(5,3,2)
--loop refresh pause (removed due to timer in get function)
end
--end main

Essentially, I clear the screen, move the cursor to the top left, display that the control system is online, then use a while loop to check server 1,2 and 3 on channel 1,3, and 5. I originally planned to have multiple return channels for each server, but decided to only use channel 2. You can also see in my comments that I plan to also check some other servers and also have a screen check. I will update this post once I have completed that.


Also very quickly I will discuss my listening code on the server to show how it works:


local modem = peripheral.wrap('back')

function send(chan,reply,m)
    modem.transmit(chan,reply,m)
end

function get(chan,tmout)--listener
    if tmout==nil then
        tmout=100000000000000000000000000000 --change to call event w/o timer
    end
    modem.open(chan) --opens port
    chk=modem.isOpen(chan) --checks to make sure it is open
    if chk==true then --will set listener
        --mm = os.pullEvent("modem_message")
        timeout = os.startTimer(tmout)
        while true do
        event = {os.pullEvent()}
   if event[1] == "modem_message" then
       --print'Worked'
       --print(event[1].." "..event[2].." "..event[3].." "..event[4].." "..event[5].." "..event[6])
       msg=event[5]
       returnchan=event[4]
       chan=event[3]
    break
    elseif event[1] == "timer" and event[2] == timeout then
    break
     end
    end
  end
  if chk==false then
      --send error# to screen
      print('Error 100: Failed to Bind Port '..chan..'.')
  end
end


function listen()
    while true do
        get(1)
        --print(msg)
        send(2,1,"ping")
        --sleep(.5)
        --os.reboot()
    end
end

function test()
    print'Welcome to Server 1.'
    print'System online.'
end

parallel.waitForAny(test(),listen())


So basically what I've done on each server is use a get function with no timeout to listen for the initial ping from the screen server. It will reply and begin to listen again. Parallel it will also complete the test() function. This does limit me to using non-interactive functions :(/>. IE I can't run one function that listens for user input (io.read()) because it will wait for the input before running the listen function. :(/> Does anyone have a better solution?

Is this code total junk? If you have suggestions, questions, comments etc, please feel free to share. Just remember, I'm a beginner, and I like learning, but please don't be a jerk. No one likes a jerk.

Thanks guys!
Edited on 10 July 2014 - 04:34 PM
Kizz #2
Posted 11 July 2014 - 03:51 PM
UPDATE:

Thanks to Lyqyd, I was able to figure out a way to control my server while listening.

Simply changing
 parallel.waitForAny(test(),listen()) 

to

parallel.waitForAny(test,listen)

forces both programs to run correctly.

My final test function is as follows:


function test()
    print'Welcome to Server 1.'
    print'System online.'
    print'Please make a choice:'
    print'1: Shutdown'
    print'2: Reboot'
    print'3: Terminate'

    ch=io.read()
    ch=ch+1-1

    if ch==1 then
    print'Shutting down!'
    sleep(1)
    os.shutdown()
    end

    if ch==2 then
    print'Rebooting!'
    sleep(1)
    os.reboot()
    end

    if ch==3 then
    print'Terminating program!'
    sleep(1)
    term.clear()
    term.setCursorPos(1,1)
    end
end

This will run while the listener listens… Allowing the user to choose functions they want the server to complete while the control screen monitors the server uptime.

What a wonderful discovery!
visionfear #3
Posted 13 July 2014 - 05:07 PM
nice :)/>