node-dmx / dmx

DMX controller library for node.js
MIT License
295 stars 96 forks source link

Make driver reentrant #153

Open nospam2k opened 1 year ago

nospam2k commented 1 year ago

I'm trying to figure out how to move this into node-red and I need to figure out how I can close the serial port when node-red reloads. I'm down to, in the driver (this case is enttec-open-usb-dmx):

 this.dev = new SerialPort(deviceId, {
    'baudRate': 250000,
    'dataBits': 8,
    'stopBits': 2,
    'parity': 'none',
  }, err => {
    if (!err) {
      this.start();
    } else {
      console.warn(err);
    }
  });

I get err, but I can't figure out how to close the serial port from the previous open.

[Error: Error Resource temporarily unavailable Cannot lock port]

hrueger commented 1 year ago

Can you close the serialport when receiving the close event? https://nodered.org/docs/creating-nodes/node-js#closing-the-node

nospam2k commented 1 year ago

This is the problem. I'm not sure how to close the port from within the dmx variable. I know how to use the node-red close. In the latest node red, there is actually separate tabs in the function node for start, message and stop. I can see the code in the enttec-open-usb-dmx.js but I'm not sure how to get to it. The serialport variable is inside the dmx variable and not global.

hrueger commented 1 year ago

Can you share a couple of screenshots or the code that you've already written? That way it is always easier to help 😃

nospam2k commented 1 year ago

I'm just reusing code from dmx-web and adding a few variables for reentrance. This is in a function node and I pass two template nodes for config and html also taken from dmx-web:

const fs = global.get('fs');
const http = global.get('http');
const body = global.get('bodyParser');
const express = global.get('express');
const basicAuth = global.get('basicAuth');
const socketio = global.get('socketio');
const program = global.get('commander');
const DMX = global.get('dmx');
const A = DMX.Animation;
const os = global.get("os");
const userHomeDir = os.homedir();
const config = msg.config;

// This sort of works but if I restart to quickly I still get the error.
if (flow.get('dmx') !== undefined)
{
  if (flow.get('dmx').universes.main.dev.isOpen) {
    flow.get('dmx').universes.main.close(function (err) {
      if (err !== null) node.warn('Error:' + err);
    });
  }
  setTimeout(DMXWeb, 5000);
  //node.warn('Starting server in 5 seconds');
}
else
{
  DMXWeb();
  //node.warn('Starting server clean');
}

function DMXWeb()
{
  const app = express();

  if (flow.get('dmxWebServer') !== undefined) flow.get('dmxWebServer').close();
  flow.set('dmxWebServer', http.createServer(app));

  const io = socketio.listen(flow.get('dmxWebServer'));

  flow.set('dmx', new DMX(config));

  for (const universe in config.universes)
  {
    flow.get('dmx').addUniverse(
        universe,
        config.universes[universe].output.driver,
        config.universes[universe].output.device,
        config.universes[universe].output.options
    );
  }

  flow.get('dmx').update('main', config.presets[0].values.main);

  const listenPort = config.server.listen_port || 8888;
  const listenHost = config.server.listen_host || '::';

  try
  {
    flow.get('dmxWebServer').listen(listenPort, listenHost, null, () =>
    {
      if (config.server.uid && config.server.gid)
      {
        try
        {
          process.setuid(config.server.uid);
          process.setgid(config.server.gid);
        }
        catch (err)
        {
          node.warn(err);
          process.exit(1);
        }
      }
    });
  }
  catch (e)
  {
    node.warn(e);
  }

  app.use(basicAuth({
    users: {'test': 'test1234'},
    challenge: true,
    realm: 'Dmx Web'
  }));

  app.use(body.json());

  app.get('/', (req, res) =>
  {
    res.send(msg.html);
  });

  app.get('/config', (req, res) =>
  {
    const response = { 'devices': flow.get('dmx').devices, 'universes': {} };

    Object.keys(config.universes).forEach(key =>
    {
      response.universes[key] = config.universes[key].devices;
    });

    res.json(response);
  });

  app.get('/state/:universe', (req, res) =>
  {
    if (!(req.params.universe in flow.get('dmx').universes))
    {
      res.status(404).json({ 'error': 'universe not found' });
      return;
    }

    res.json({ 'state': flow.get('dmx').universeToObject(req.params.universe) });
  });

  app.post('/state/:universe', (req, res) =>
  {
    if (!(req.params.universe in flow.get('dmx').universes))
    {
      res.status(404).json({ 'error': 'universe not found' });
      return;
    }

    flow.get('dmx').update(req.params.universe, req.body);
    res.json({ 'state': flow.get('dmx').universeToObject(req.params.universe) });
  });

  app.post('/animation/:universe', (req, res) =>
  {
    try
    {
      const universe = flow.get('dmx').universes[req.params.universe];

      // preserve old states
      const old = flow.get('dmx').universeToObject(req.params.universe);

      const animation = new A();

      for (const step in req.body)
      {
        animation.add(
            req.body[step].to,
            req.body[step].duration || 0,
            req.body[step].options || {}
        );
      }
      animation.add(old, 0);
      animation.run(universe);
      res.json({ 'success': true });
    }
    catch (e)
    {
      node.warn(e);
      res.json({ 'error': String(e) });
    }
  });

  io.sockets.on('connection', socket =>
  {
    socket.emit('init', { 'devices': flow.get('dmx').devices, 'setup': config });

    socket.on('request_refresh', () =>
    {
      for (const universe in config.universes)
      {
        socket.emit('update', universe, flow.get('dmx').universeToObject(universe));
      }
    });

    socket.on('update', (universe, update) =>
    {
      flow.get('dmx').update(universe, update);
    });

    flow.get('dmx').on('update', (universe, update) =>
    {
        socket.emit('update', universe, update);
    });
  });
}
hrueger commented 1 year ago

Hm. What are you trying to accomplish? I don't completely understand why you are using dmx-web inside Node RED. Doesn't it have it's own control UI (I believe it is called dashboard)?

Edit: just read the code more thoroughly, you are not creating a control UI but a REST API. Do you need that? Otherwise, we could greatly simplify the code. Do you want to trigger updates via other NodeRED nodes?

nospam2k commented 1 year ago

I'm not a big fan of dashboard, but I like node red for rapid implementation of nodejs. My habit of implementing nodejs into node-red is to just add the requireds into settings.js and then start adding code just to see if it will work at all, which I've accomplished here, almost. To answer the question "what are you trying to accomplish?" is to add dmx and dmx-web into node-red with the minimum amount of recoding possible. ;) So, I just need to figure out how to release the port in between reloads and I'm done, for now. The truth is, running dmx-web from a command line nodejs and ctrl-c out is causing the same problem.

hrueger commented 1 year ago

Ah, I see. I haven't though of this but it's quite a neat trick ;-)

The truth is, running dmx-web from a command line nodejs and ctrl-c out is causing the same problem.

Do I understand correctly that if you have a node js script with the code, running it via node and exiting with Ctrl + C, you can't run it again? That is very strange, because Ctrl + C should terminate the whole node process and also free up the port again. Do you see an instance of node still in the Task Manager (or equivalent on other os'ses)?

nospam2k commented 1 year ago

It may be a timing issue of how long it takes for the port to release but I noticed, running in Ubuntu, if I ctrl-c out and restart, Sometimes, I will get an error on the port. I don't think I would worry about that, unless other people are complaining. This is on an old mac-mini running Ubuntu.

hrueger commented 1 year ago

Hm, I'm sorry, but now that I've understood your setup, I unfortunately think this is outside of my scope. I don't have time to build a similar setup to try and debug this, and I don't even know if the issue lies in dmx or deeper, maybe at node or driver level.

nospam2k commented 1 year ago

Hey no problem. I'll be playing with this more anyway and I get the code well enough now that I've been playing with it. If I come up with something I think is worthy of your attention, I'll check back! I'll leave it open for now.