Control ethernet relays relays with http commands

Okay!

Thank you both for helping me get to where I am now. At this point the error I mentioned before happens every once in a while.

It is for local network control :slight_smile:

I will try upping the socket timeout on the Ethernet module as well.

It is strange how it is not consistent but we will figure it out.

I also tried closing and opening sockets after every use but I got an error. I think this error is because I am not re-opening the socket correctly.
What is the correct way to reopen a socket?

Thanks again!

Will post again if I run into anything else.

Inconsistent bugs are the worst.

Do you happen to know if the relay clicks when this bug happens?

Can you try a socket error sleep like this stack overflow post suggest in the answer? https://stackoverflow.com/questions/1410723/is-there-a-way-to-reopen-a-socket

for i in range(nb):
sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sck.connect((adr, prt)

while sck.error() == NO_SOCKETS_AVAIL:
    sleep 250 milliseconds
    sck.connect((adr, prt)

sck.send('question %i'%i)
sck.shutdown(SHUT_WR)
answer=sck.recv(4096)
print 'answer %i : %s' % (%i, answer)
sck.close()

Yeh I know what you mean.

So yes the relay will click once and then the error is thrown. So my guess is the data coming back from the relay board is doing something to interrupt the process.

I will be glad to try that code.

Also I have been toggling the Ethernet module times (it has many different times to toggle).
Is the one I should be toggling the keep alive time?

Thank you Jacob!

New update,

So I found last night that when I am only using one relay board at a time, I can toggle relays without the string error being thrown.

Then I did a test where I would turn on 1 relay from all of my boards in sequence.
When I did this the first relay in the sequence would always click on or sometimes they would all come on but when I had them turn off after a specific time they would throw the string error again.

So it seems like at least one or all of them will come on but after I have them turn off again they throw the string error.

I think what is happening has to do with me leaving all the sockets open all the time. For this reason I should probably close the sockets every time after a command is sent.

Is opening a socket just the command sock.open? I know that closing is just sock.close.
See I tried reloading the ip and port and passing it to a relay board but that also gave me an error.

Hopefully this provides more situational awareness to what I am seeing.

I still plan on trying that code from stack overflow to see if that helps.

So I could reproduce this using one board and constantly opening and closing the socket to it and writing commands. I believe it has to do with the unexpected way python/linux handles socket.recv. It uses an algorithm to determine if the stream is done from what I can gather. So I altered the library to hard code a bytes expected back.

import time

class Relay_Controller:
	def __init__(self, combus, kwargs = {}):
		self.__dict__.update(kwargs)
		self.renew_replace_interface(combus)

	def set_relay_bank_status(self, status, bank = 1):
		command = self.wrap_in_api([254, 140, status, bank])
		return self.process_control_command_return(self.send_command(command, 4))

	def toggle_relay_by_index(self, relay):
		lsb = relay-1 & 255
		msb = relay >> 8
		command = self.wrap_in_api([254, 47, lsb, msb, 1])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_on_relay_by_index(self, relay):
		lsb = relay-1 & 255
		msb = relay >> 8
		command = self.wrap_in_api([254,48,lsb,msb])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_off_relay_by_index(self, relay):
		lsb = relay-1 & 255
		msb = relay >> 8
		command = self.wrap_in_api([254,47,lsb,msb])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_off_relay_group(self, relay, bank, group_size):
		command = self.wrap_in_api([254, 99+relay, bank, group_size-1])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_on_relay_group(self, relay, bank, group_size):
		command = self.wrap_in_api([254, 107+relay, bank, group_size-1])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_on_relay_by_bank(self, relay, bank = 1):
		command = self.wrap_in_api([254, 107+relay, bank])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_off_relay_by_bank(self, relay, bank = 1):
		command = self.wrap_in_api([254, 99+relay, bank])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_on_relay_flasher(self, flasher, speed=1):
		command = self.wrap_in_api([254, 45, flasher, speed])
		return self.process_control_command_return(self.send_command(command, 4))

	def turn_off_relay_flasher(self, flasher):
		command = self.wrap_in_api([254, 45, flasher, 0])
		return self.process_control_command_return(self.send_command(command, 4))

	def set_flasher_speed(self, speed):
		command = self.wrap_in_api([254, 45, 0, speed])
		return self.process_control_command_return(self.send_command(command, 4))

	def start_relay_timer(self, timer, hours, minutes, seconds, relay):
		command = self.wrap_in_api([254, 50, 49+timer, hours, minutes, seconds, relay-1])
		return self.process_control_command_return(self.send_command(command, 4))

	def get_relay_bank_status(self, bank = 1):
		command = self.wrap_in_api([254,124, bank])
		return self.process_read_command_return(self.send_command(command, 4))

	def get_relay_status_by_index(self, relay):
		lsb = relay-1 & 255
		msb = relay >> 8
		command = self.wrap_in_api([254, 44, lsb, msb])
		return self.process_read_command_return(self.send_command(command, 4))

	def get_relay_status_by_index_fusion(self, relay):
		lsb = relay-1 & 255
		msb = relay >> 8
		command = self.wrap_in_api([254, 144, lsb, msb])
		return self.process_read_command_return(self.send_command(command, 4))

	def read_single_ad8(self, channel):
	#TODO read back single integer
		command = self.wrap_in_api([254, 149+channel])
		return self.translate_ad(self.process_read_command_return(self.send_command(command, 4)), 8)

	def read_all_ad8(self):
		command = self.wrap_in_api([254, 166])
		return self.translate_ad(self.process_read_command_return(self.send_command(command, 11)), 8)

	def read_single_ad10(self, channel):
		command = self.wrap_in_api([254, 157+channel])
		return self.translate_ad(self.process_read_command_return(self.send_command(command, 5)), 10)

	def read_all_ad10(self):
		command = self.wrap_in_api([254, 167])
		return self.translate_ad(self.process_read_command_return(self.send_command(command, 19)), 10)

	def get_relay_status_by_bank(self, relay, bank = 1):
		command = self.wrap_in_api([254,115+relay, bank])
		return self.process_read_command_return(self.send_command(command, 4))

	def convert_data(self, data):
		command_string = ''
		for character in data:
			command_string += chr(character)
		return command_string

	def add_checksum(self, data):
		data.append(int(sum(data) & 255))
		return data

	def wrap_in_api(self, data):
		bytes_in_packet = len(data)
		data.insert(0, bytes_in_packet)
		data.insert(0, 170)
		data = self.add_checksum(data)
		return data

	def send_command(self, command, bytes_back):
# 		The following line is causing problems and has been commented out until further study.
		command = self.convert_data(command)
		if self.combus_type == 'serial':
			self.combus.write(command)
			return self.combus.read(bytes_back)
		else:
			self.combus.send(command)
			recvBuffer = ""
			timeStart = int(round(time.time() * 1000))
			while (len(recvBuffer) < bytes_back):
				recvBuffer += self.combus.recv(bytes_back)
				if timeStart < (int(round(time.time() * 1000)) - (self.combus.gettimeout() * 1000)):
					raise ValueError('The device did not return the expected number of bytes.')
					continue
			return recvBuffer

	def process_control_command_return(self, data):
		handshake = self.check_handshake(data)
		bytes_back = self.check_bytes_back(data)
		checksum = self.check_checksum(data)
		if handshake and bytes_back and checksum:
			return True
		else:
			return False

	def process_read_command_return(self, data):
		handshake = self.check_handshake(data)
		bytes_back = self.check_bytes_back(data)
		checksum = self.check_checksum(data)
		if handshake and bytes_back and checksum:
			return self.get_payload(data)
		else:
			return False

	def get_payload(self, data):
		payload = []
		for byte in range(2, len(data)-1):
			payload.append(ord(data[byte:byte+1]))
		return payload

	def check_handshake(self, data):
		return ord(data[:1]) == 170

	def check_bytes_back(self, data):
		return ord(data[1:2]) == (len(data)-3)

	def check_checksum(self,data):
		dlength = len(data)
		dsum = 0
		for byte in range(0, dlength-1):
			dsum += (ord(data[byte:byte+1]))
		return dsum & 255 == ord(data[dlength-1:dlength])

	def translate_ad(self, data, resolution):
		read_array = []
		if resolution == 10:
			for index in range(0, len(data), 2):
				read_array.append(((data[index] & 3) << 8) + data[index+1])
			return read_array
		elif resolution == 8:
			return data
		return False

	def renew_replace_interface(self, combus):
		self.combus = combus
		if 'serial' in str(type(self.combus)):
			self.combus_type = 'serial'
		else:
			self.combus_type = 'socket'

Try that library and see if the error shows up, send me the error message if it does.

I did notice that if you try and open up a socket to a device immediately after closing it the ethernet module doesn’t finish cleanup on its end fast enough so I had to wait for it to be ready. Just something to keep in mind.

Here’s the code I tested with:

#import the pyserial module
import socket
import ncd_industrial_relay
import time

while True:
    #set up your socket with the desired settings.
    sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #instantiate the board object and pass it the network socket
    board1 = ncd_industrial_relay.Relay_Controller(sock1)
    #connect the socket using desired IP and Port
    IP_ADDRESS1 = "192.168.1.48"
    PORT1 = 2101
    sock1.settimeout(5)
    sock1.connect((IP_ADDRESS1, PORT1))
    board1.turn_on_relay_by_index(5)
    time.sleep(1)
    board1.turn_off_relay_by_index(5)
    sock1.close()

    time.sleep(1)

    #set up your socket with the desired settings.
    sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #instantiate the board object and pass it the network socket
    board2 = ncd_industrial_relay.Relay_Controller(sock2)
    #connect the socket using desired IP and Port
    IP_ADDRESS2 = "192.168.1.48"
    PORT2 = 2101
    sock2.settimeout(5)
    sock2.connect((IP_ADDRESS2, PORT2))
    board2.turn_on_relay_by_index(5)
    time.sleep(1)
    board2.turn_off_relay_by_index(5)
    sock2.close()

    time.sleep(1)

    #set up your socket with the desired settings.
    sock3 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #instantiate the board object and pass it the network socket
    board3 = ncd_industrial_relay.Relay_Controller(sock3)
    #connect the socket using desired IP and Port
    IP_ADDRESS3 = "192.168.1.48"
    PORT3 = 2101
    sock3.settimeout(5)
    sock3.connect((IP_ADDRESS3, PORT3))
    board3.turn_on_relay_by_index(5)
    time.sleep(1)
    board3.turn_off_relay_by_index(5)
    sock3.close()

    time.sleep(1)

    #set up your socket with the desired settings.
    sock4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #instantiate the board object and pass it the network socket
    board4 = ncd_industrial_relay.Relay_Controller(sock4)
    #connect the socket using desired IP and Port
    IP_ADDRESS4 = "192.168.1.48"
    PORT4 = 2101
    sock4.settimeout(5)
    sock4.connect((IP_ADDRESS4, PORT4))
    board4.turn_on_relay_by_index(5)
    time.sleep(1)
    board4.turn_off_relay_by_index(5)
    sock4.close()

    time.sleep(1)

    #set up your socket with the desired settings.
    sock5 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    #instantiate the board object and pass it the network socket
    board5 = ncd_industrial_relay.Relay_Controller(sock5)
    #connect the socket using desired IP and Port
    IP_ADDRESS5 = "192.168.1.48"
    PORT5 = 2101
    sock5.settimeout(5)
    sock5.connect((IP_ADDRESS5, PORT5))
    board5.turn_on_relay_by_index(5)
    time.sleep(1)
    board5.turn_off_relay_by_index(5)
    sock5.close()

    time.sleep(1)

Also the line:

command = self.convert_data(command)

Is back so if it was causing issues go ahead and comment it out.

Wow this is great!
I will implement all this and get back to you!
Thank you for the hard work!