Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: 'AsyncModbusTcpClient' object has no attribute 'loop' #1578

Closed
MrWaloo opened this issue Jun 5, 2023 · 7 comments · Fixed by #1579
Closed

AttributeError: 'AsyncModbusTcpClient' object has no attribute 'loop' #1578

MrWaloo opened this issue Jun 5, 2023 · 7 comments · Fixed by #1579

Comments

@MrWaloo
Copy link
Contributor

MrWaloo commented Jun 5, 2023

Versions

  • Python: 3.9.16 (under pyenv)
  • OS: raspbian 10
  • Pymodbus: 3.3.0

Pymodbus Specific

  • Client: tcp - async

Description

When calling client.open(), an exception is raised.

Code and Logs

  async def connect(self):
    logging.debug('PyModbusClient: *' + self.eqConfig['name'] + '* connect called')
    try:
      if not self.connected or not self.client.connected:
        logging.debug('PyModbusClient: *' + self.eqConfig['name'] + '* connecting...')
        await self.client.connect()
      ret = True
    except Exception as e:
      logging.error('PyModbusClient: *' + self.eqConfig['name'] + '* Something went wrong while connecting to equipment id ' + self.eqConfig['id'] + ': ' + repr(e)) # + ' - ' + e.string)
      ret = False
    delay = float(self.eqConfig['eqFirstDelay'])
    await asyncio.sleep(delay)
    return ret

Traceback, when I don't catch the exception:

Traceback (most recent call last):
File "/var/www/html/plugins/mymodbus/ressources/mymodbusd/mymodbus.py", line 441, in connect
await self.client.connect()
File "/var/www/html/plugins/mymodbus/ressources/_pyenv/versions/3.9.16/lib/python3.9/site-packages/pymodbus/client/tcp.py", line 70, in connect
return await self.transport_connect()
File "/var/www/html/plugins/mymodbus/ressources/_pyenv/versions/3.9.16/lib/python3.9/site-packages/pymodbus/transport/transport.py", line 268, in transport_connect
self.call_connect_listen(),
File "/var/www/html/plugins/mymodbus/ressources/_pyenv/versions/3.9.16/lib/python3.9/site-packages/pymodbus/transport/transport.py", line 140, in 
self.call_connect_listen = lambda: self.loop.create_connection(
AttributeError: 'AsyncModbusTcpClient' object has no attribute 'loop'
@janiversen
Copy link
Collaborator

This happens if there are no active asyncio loop, when you make an instance of AsyncModbus*.

In short the function / method where you create an object of AsyncModbus* must be async.

It is not detected as and error until you call connect (the first async call).

@janiversen
Copy link
Collaborator

I am thinking of a fix for v3.3.1, but I am not sure it is 100%.

@MrWaloo
Copy link
Contributor Author

MrWaloo commented Jun 5, 2023

The code is unchanged and worked with the last version. This has been reported by a user. I will upgrade my dev machine and test it if you want but the code hasn't been chaged since days and it worked for several users.

This function is the first to be called: asyncio is used:

  def run(self, queue):
    self.queue = queue
    
    logging.getLogger('asyncio').setLevel(logging.WARNING)
    self.loop = asyncio.get_event_loop()
    
    if self.new_config is not None:
      self.apply_new_config()
    
    if self.eqConfig['eqRefreshMode'] == 'polling':
      self.loop.run_until_complete(self.run_polling())
      
    elif self.eqConfig['eqRefreshMode'] == 'cyclic':
      self.loop.run_until_complete(self.run_cyclic())
      
    elif self.eqConfig['eqRefreshMode'] == 'on_event':
      self.loop.run_until_complete(self.run_one())
      
    self.loop.close()
    self.shutdown()

Here is the shortest called function:

  async def run_one(self):
    self.connected = False
    
    while not self.should_stop.is_set():
      # for time measuring
      t_begin = time.time()
      
      self.check_queue()
      await self.execute_write_requests(True, self.connected)
      
      if self.new_config is not None:
        self.apply_new_config()
      
      if self.read_cmd.is_set():
        self.read_cmd.clear()
        # Connect
        if not self.connected or not self.client.connected:
          self.connected = await self.connect()
        
        await self.read_all()
        
        # The loop has exited
        self.connected = await self.disconnect()
        
        self.cycle_times[self.cycle % 5] = time.time() - t_begin

@janiversen
Copy link
Collaborator

With the fix you can instantiate without an async loop.

Clearly there are differences between v3.2.2 and v3.3.0 just look at the changelog, it includes a brand new transport layer for async clients. So it is natural that you need to do some adaptations in your program, if you do not want to do that then do not upgrade :-)

@janiversen
Copy link
Collaborator

janiversen commented Jun 5, 2023

Your code "def run" is NOT async, you create a new loop with get_event_loop(), to test if you have a running loop use get_running_loop().

In both version there are no "client = AsyncModbusTcp(...)", this is the call that must happen in an async function (unless you apply my fix).

@MrWaloo
Copy link
Contributor Author

MrWaloo commented Jun 5, 2023

The run function is called as a multiprocess.Process (one Process per device), so the loop has been created into the Process, I get the running loop in the called run() function:

import multiprocessing as mp

...

    pymodbus_client = PyModbusClient(eqConfig, self.jcom, jeedom_utils.convert_log_level(self._log_level))
    queue = mp.Queue()
    process = mp.Process(target=pymodbus_client.run, args=(queue, ), name=eqConfig['name'], daemon=True)
    process.start()

@janiversen
Copy link
Collaborator

janiversen commented Jun 5, 2023

multiprocessing is not the same as asyncio.

With version 3.3 you need to have an active asyncio loop when creating the client. get_running_loop() creates a loop but does not activate it.

Anyhow you have a fix for your situation.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants