Topics

Servos, Ardunios, and JMRI #arduino

Sam Simons
 

I've been working on getting my staging turnouts powered by servos and controlled by JMRI through Arduinos using Geoff Bunza's direct JMRI to Arduino scripts. The ends of the staging yards are far enough apart that I've ended up positioning one Arduino at each end of the yard, controlling five servos each. One Arduino is on COM4 and the other on COM5. I've got a pair of the "TurnoutDataTransfer.py" scripts created, one of which I'll refer to as P, the other S. On the one, I've renamed the global extportin to extportinP and the other script has extportinS. Com ports correspond to the Arduino ports. I've created a panel that has a basic diagram of the staging yard and can control the turnouts. This is where I am running into problems. I had JMRI automatically load the panel and run both scripts (P first, then S). The S turnouts worked normally but the P turnouts did not. I stopped the scripts from running automatically and have managed to test my way to the following:

1. If I load the P script (automatically or manually, doesn't make a difference), all the P turnouts work normally. 
2. If I then load the S script, all the S turnouts work normally but the P turnouts stop responding, although the script output and panel act like they are working.
3. It doesn't matter which script is loaded first; the second script will stop the first script's turnouts. 

Any thoughts on why this may be happening?

Thanks,

Sam

Bob Jacobsen
 

Could you post your scripts so we can see the details?

I suspect they’re defining objects with the same names.

Bob

On Jul 23, 2019, at 2:58 PM, Sam Simons <samusi01@...> wrote:

I've been working on getting my staging turnouts powered by servos and controlled by JMRI through Arduinos using Geoff Bunza's direct JMRI to Arduino scripts. The ends of the staging yards are far enough apart that I've ended up positioning one Arduino at each end of the yard, controlling five servos each. One Arduino is on COM4 and the other on COM5. I've got a pair of the "TurnoutDataTransfer.py" scripts created, one of which I'll refer to as P, the other S. On the one, I've renamed the global extportin to extportinP and the other script has extportinS. Com ports correspond to the Arduino ports. I've created a panel that has a basic diagram of the staging yard and can control the turnouts. This is where I am running into problems. I had JMRI automatically load the panel and run both scripts (P first, then S). The S turnouts worked normally but the P turnouts did not. I stopped the scripts from running automatically and have managed to test my way to the following:

1. If I load the P script (automatically or manually, doesn't make a difference), all the P turnouts work normally.
2. If I then load the S script, all the S turnouts work normally but the P turnouts stop responding, although the script output and panel act like they are working.
3. It doesn't matter which script is loaded first; the second script will stop the first script's turnouts.

Any thoughts on why this may be happening?

Thanks,

Sam
--
Bob Jacobsen
@BobJacobsen

Sam Simons
 

Bob, 

Here you go - the 'P' script first, then the 'S' script.

Also, this is happening on current production 4.16.

Sam

# Transfer "TurnOut" Data from to an Arduino via Serial Transmission
# Author: Geoff Bunza 2018 based in part on a script by
# Bob Jacobsen as part of the JMRI distribution
# Version 1.1
# Connects JMRI Turnout "Watcher" to an Arduino Output Channel
# Note that JMRI must be set up to have a valid
# turnout table; if you're not using some other DCC connection,
# configure JMRI to use LocoNet Simulator
import jarray
import jmri
import java
import purejavacomm
# find the port info and open the port
global extportinP
portname = "COM5"
portID = purejavacomm.CommPortIdentifier.getPortIdentifier(portname)
try:
    port = portID.open("JMRI", 50)
except purejavacomm.PortInUseException:
    port = extportinP
extportinP = port
# set options on port
baudrate = 19200
port.setSerialPortParams(baudrate,
    purejavacomm.SerialPort.DATABITS_8,
    purejavacomm.SerialPort.STOPBITS_1,
    purejavacomm.SerialPort.PARITY_NONE)
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
# define a turnout listener that will
class Datatransfer(java.beans.PropertyChangeListener):
  # initialization
  # registers to receive events
  def __init__(self, id, value) :
        self.name = "PT"+str(id)
        self.closed = value     # write this value to close
        self.thrown = value    # write this value to throw
        turnout = turnouts.provideTurnout(self.name)
        turnout.addPropertyChangeListener(self)
        turnout.setCommandedState(CLOSED)
        return
 
  # on a property change event, first see if
  # right type, and then write appropriate
  # value to port based on new state
  def propertyChange(self, event):
    #print "change",event.propertyName
    #print "from", event.oldValue, "to", event.newValue
    #print "source systemName", event.source.systemName
    if (event.propertyName == "CommandedState") :
      if (event.newValue == CLOSED and event.oldValue != CLOSED) :
        print "set CLOSED for", event.source.userName
        outputStream.write(event.source.userName)
        outputStream.write(",0")
      if (event.newValue == THROWN and event.oldValue != THROWN) :
        print "set THROWN for", event.source.userName
        outputStream.write(event.source.userName)
        outputStream.write(",1")  
    return
# The following will set up 5 listeners for Turnouts PT1 though PT6 (by username)
for x in range(1,6) :
    Datatransfer(x,x+100)

# Transfer "TurnOut" Data from to an Arduino via Serial Transmission
# Author: Geoff Bunza 2018 based in part on a script by
# Bob Jacobsen as part of the JMRI distribution
# Version 1.1
# Connects JMRI Turnout "Watcher" to an Arduino Output Channel
# Note that JMRI must be set up to have a valid
# turnout table; if you're not using some other DCC connection,
# configure JMRI to use LocoNet Simulator
import jarray
import jmri
import java
import purejavacomm
# find the port info and open the port
global extportinS
portname = "COM4"
portID = purejavacomm.CommPortIdentifier.getPortIdentifier(portname)
try:
    port = portID.open("JMRI", 50)
except purejavacomm.PortInUseException:
    port = extportinS
extportinS = port
# set options on port
baudrate = 19200
port.setSerialPortParams(baudrate,
    purejavacomm.SerialPort.DATABITS_8,
    purejavacomm.SerialPort.STOPBITS_1,
    purejavacomm.SerialPort.PARITY_NONE)
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
# define a turnout listener that will
class Datatransfer(java.beans.PropertyChangeListener):
  # initialization
  # registers to receive events
  def __init__(self, id, value) :
        self.name = "ST"+str(id)
        self.closed = value     # write this value to close
        self.thrown = value    # write this value to throw
        turnout = turnouts.provideTurnout(self.name)
        turnout.addPropertyChangeListener(self)
        turnout.setCommandedState(CLOSED)
        return
 
  # on a property change event, first see if
  # right type, and then write appropriate
  # value to port based on new state
  def propertyChange(self, event):
    #print "change",event.propertyName
    #print "from", event.oldValue, "to", event.newValue
    #print "source systemName", event.source.systemName
    if (event.propertyName == "CommandedState") :
      if (event.newValue == CLOSED and event.oldValue != CLOSED) :
        print "set CLOSED for", event.source.userName
        outputStream.write(event.source.userName)
        outputStream.write(",0")
      if (event.newValue == THROWN and event.oldValue != THROWN) :
        print "set THROWN for", event.source.userName
        outputStream.write(event.source.userName)
        outputStream.write(",1")  
    return
# The following will set up 5 listeners for Turnouts ST1 though ST6 (by username)
for x in range(1,6) :
    Datatransfer(x,x+100)
 

Cliff Anderson
 

Sam,

From your original post:

Item 3. It doesn't matter which script is loaded first; the second script will stop the first script's turnouts.

This symptom is consistent with the use of the same name for what should be considered two variables.

In the text you supplied in your most recent post, it is apparent that the variables named below are overwritten by the most recently executed script file:

  • portname
  • portID
  • port
  • baudrate
  • inputStream
  • outputStream
  • class Datatransfer

and perhaps others. Only the constant for baudrate is benign in this case.

With the possible exception of the class name, all of the other variable names are shared by all of the instances of the most recent definition of the Datatransfer class that are created in the lines:

# The following will set up 5 listeners for Turnouts PT1 though PT6 (by username)
for x in range(1,6) :
    Datatransfer(x,x+100)

which appear at the bottom of both script texts. In effect, these instances will each use the most recently set values for each of the above identified variable names.

If these are correct copies of your scripts, It is my understanding that these lines set up duplicate copies of Turnouts PT1 through PT6.

If my understanding of these lines is correct, either script would only connect with what JMRI labels as turnouts with systemName choices PT1 though PT6. Did you in fact receive state change information concerning either set of your yard turnouts but using the same JMRI systemNames?

The original structure of the class definition in TurnoutDataTransfer.py does not lend itself to easy modification to multiple input-output connections to separate Arduino devices.

My preference would be to define a more robust class with a separate groupings of instances for each Arduino. But without the hardware resources to test with at least three Arduino devices it is not prudent for me to make such an attempt. In no case would I recommend executing two script files that define copies of the same class, but that is moot here.

In your case, the quick and dirty method to simply modify all of the variable names, other than the baudrate, as you have already done for a few variables might prove to be sufficient. It would not be surprising if you chose this path.

It would be prudent to review the tutorial at https://docs.python.org/3/tutorial/classes.html and in particular the section 9.2 "Python Scopes and Namespaces."

If indeed your plan is to consider adding numerous other controls with more variations of the script, this process could easily get both tedious and difficult to maintain.

If you wish to respond off list and are willing to put up with perhaps many iterative attempts, I could provide some help.

By the way, if you had made a subfolder in https://groups.io/g/jmriusers/files/ProblemsBeingWorkedOn with a unique identifier referencing either this problem or your name, and supplied that link in your post, I believe you would have gotten more responses from a wider source of knowledge. Only by accident did I discover your pasted text marked as "Show quoted text" which usually means previous postings in the same thread.

Cliff in Baja SoCal

Bob Jacobsen
 

The problem is that both scripts are using the same global variable for `outputStream`. A single script sets that, then installs some code that listens for changes so that it can later forward those changes to the hardware via outputStream. But when you run two scripts, the second one changes the value of that variable, so now both the first set of listening code _and_ the second set are sending to the second port.

The quickest reliable fix is to make sure that all the variable named in the global (not intended) part of the first script are different from those in the second script. Append a P to one and a S to the other, or something like that.

A better solution would be to move those variables into the Datatransfer class, and then pass the port in as another argument. That would make it much easier for people to use two copies of the script. But that’s a bit more programming.

Note another thing: This script creates internal Turnout objects with names at start with AT and ST. Although it woks for you now, there’s no guarantee that always will. Better would be names like ITAT or ITST, which are the standard form and won’t (possibly) break in the future.

Bob


On Jul 23, 2019, at 4:59 PM, Sam Simons <samusi01@...> wrote:

Bob,

Here you go - the 'P' script first, then the 'S' script.

Also, this is happening on current production 4.16.

Sam
# Transfer "TurnOut" Data from to an Arduino via Serial Transmission
# Author: Geoff Bunza 2018 based in part on a script by
# Bob Jacobsen as part of the JMRI distribution
# Version 1.1
# Connects JMRI Turnout "Watcher" to an Arduino Output Channel
# Note that JMRI must be set up to have a valid
# turnout table; if you're not using some other DCC connection,
# configure JMRI to use LocoNet Simulator
import jarray
import jmri
import java
import purejavacomm
# find the port info and open the port
global extportinP
portname = "COM5"
portID = purejavacomm.CommPortIdentifier.getPortIdentifier(portname)
try:
port = portID.open("JMRI", 50)
except purejavacomm.PortInUseException:
port = extportinP
extportinP = port
# set options on port
baudrate = 19200
port.setSerialPortParams(baudrate,
purejavacomm.SerialPort.DATABITS_8,
purejavacomm.SerialPort.STOPBITS_1,
purejavacomm.SerialPort.PARITY_NONE)
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
# define a turnout listener that will
class Datatransfer(java.beans.PropertyChangeListener):
# initialization
# registers to receive events
def __init__(self, id, value) :
self.name = "PT"+str(id)
self.closed = value # write this value to close
self.thrown = value # write this value to throw
turnout = turnouts.provideTurnout(self.name)
turnout.addPropertyChangeListener(self)
turnout.setCommandedState(CLOSED)
return

# on a property change event, first see if
# right type, and then write appropriate
# value to port based on new state
def propertyChange(self, event):
#print "change",event.propertyName
#print "from", event.oldValue, "to", event.newValue
#print "source systemName", event.source.systemName
if (event.propertyName == "CommandedState") :
if (event.newValue == CLOSED and event.oldValue != CLOSED) :
print "set CLOSED for", event.source.userName
outputStream.write(event.source.userName)
outputStream.write(",0")
if (event.newValue == THROWN and event.oldValue != THROWN) :
print "set THROWN for", event.source.userName
outputStream.write(event.source.userName)
outputStream.write(",1")
return
# The following will set up 5 listeners for Turnouts PT1 though PT6 (by username)
for x in range(1,6) :
Datatransfer(x,x+100)

# Transfer "TurnOut" Data from to an Arduino via Serial Transmission
# Author: Geoff Bunza 2018 based in part on a script by
# Bob Jacobsen as part of the JMRI distribution
# Version 1.1
# Connects JMRI Turnout "Watcher" to an Arduino Output Channel
# Note that JMRI must be set up to have a valid
# turnout table; if you're not using some other DCC connection,
# configure JMRI to use LocoNet Simulator
import jarray
import jmri
import java
import purejavacomm
# find the port info and open the port
global extportinS
portname = "COM4"
portID = purejavacomm.CommPortIdentifier.getPortIdentifier(portname)
try:
port = portID.open("JMRI", 50)
except purejavacomm.PortInUseException:
port = extportinS
extportinS = port
# set options on port
baudrate = 19200
port.setSerialPortParams(baudrate,
purejavacomm.SerialPort.DATABITS_8,
purejavacomm.SerialPort.STOPBITS_1,
purejavacomm.SerialPort.PARITY_NONE)
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
# define a turnout listener that will
class Datatransfer(java.beans.PropertyChangeListener):
# initialization
# registers to receive events
def __init__(self, id, value) :
self.name = "ST"+str(id)
self.closed = value # write this value to close
self.thrown = value # write this value to throw
turnout = turnouts.provideTurnout(self.name)
turnout.addPropertyChangeListener(self)
turnout.setCommandedState(CLOSED)
return

# on a property change event, first see if
# right type, and then write appropriate
# value to port based on new state
def propertyChange(self, event):
#print "change",event.propertyName
#print "from", event.oldValue, "to", event.newValue
#print "source systemName", event.source.systemName
if (event.propertyName == "CommandedState") :
if (event.newValue == CLOSED and event.oldValue != CLOSED) :
print "set CLOSED for", event.source.userName
outputStream.write(event.source.userName)
outputStream.write(",0")
if (event.newValue == THROWN and event.oldValue != THROWN) :
print "set THROWN for", event.source.userName
outputStream.write(event.source.userName)
outputStream.write(",1")
return
# The following will set up 5 listeners for Turnouts ST1 though ST6 (by username)
for x in range(1,6) :
Datatransfer(x,x+100)

--
Bob Jacobsen
@BobJacobsen

Cliff Anderson
 

Correction to my previous post:

# The following will set up 5 listeners for Turnouts PT1 though PT6 (by username) for x in range(1,6) :
    Datatransfer(x,x+100)

Your changes within the Datatransfer class definition, while using the same name for what are two different classes got overlooked in my previous analysis.

Thus, my comments about duplicate Turnout change of state reports are wrong.

Cliff

Sam Simons
 

Cliff & Bob,

Thank you for your responses. Apologies for the hidden code; some other forums have a "code" tag and I was trying to see if it would work as such in an effort to keep things neat... failed miserably.

I am heading out the door in about thirty minutes for a work trip and, if I'm lucky, I should be back Sunday so this response will be a bit brief.

Over the next couple of days I'll have some time to study the Python scopes and namespaces link. To be candid, I may well just rename the variables initially by appending P and S but - and I am afraid my knowledge of scripting as a whole is quite limited - moving the variables over to the Datatransfer class to make the whole thing more robust makes sense. I don't plan on having more than the two Arduinos at the moment but I do have a third Arduino (a Mega - don't think that'll make a difference though) available, and a single servo should Cliff wish to experiment a bit more. There are others in the local area who have been considering an Arduino solution and this may be able to help them out as they work on their layouts.

Last, regarding turnout names, my turnout table has ten rows. User names ST1-5 are assigned system names IT1-5 and PT1-5 are IT6-10. Is this not correct?

Sam

Geoffb
 

Bob, et al.,

If :
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
...
        outputStream.write(event.source.userName)
        outputStream.write(",0")
...
        outputStream.write(event.source.userName)
        outputStream.write(",1")

were changed to
# get I/O connections for later
inputStreamPT = port.getInputStream()
outputStreamPT = port.getOutputStream()
...
        outputStreamPT.write(event.source.userName)
        outputStreamPT.write(",0")
...
        outputStreamPT.write(event.source.userName)
        outputStreamPT.write(",1")

and
# get I/O connections for later
inputStreamST = port.getInputStream()
outputStreamST = port.getOutputStream()
...
        outputStreamST.write(event.source.userName)
        outputStreamST.write(",0")
...
        outputStreamST.write(event.source.userName)
        outputStreamST.write(",1")

would these changes be enough to solve the problem?

also Bob, your comment:
This script creates internal Turnout objects with names at start with AT and ST. Although it woks for you now, there’s no guarantee that always will. Better would be names like ITAT or ITST, which are the standard form and won’t (possibly) break in the future.
Uses ATxx and STxx as the User names for the internal Turnout objects or they could refer to hardware objects. I would think that User names as an independent alias for internal or hardware references would be "protected" as a system design  feature of JMRI in the future, as it seems to be "advertised" as such (as consistent in future releases)?
No?  Did I misunderstand?

Have fun!  :-)
Best regards,
Geoff Bunza
scalemodelanimation.com

Bob Jacobsen
 

If you have a panel file loaded (before the script is run) that has created internal Turnouts with the appropriate user names, then all is well.

However, it’s good to have scripts like this (which can be distributed to others) be more general. When the script calls provideTurnout(..) that will create the turnout if it doesn’t already exist, using the argument as a system name. (It could call getTurnout(..), which will find an existing turnout with that user name or system name, and return None / null if it doesn’t exist already).

So to make the script general, I think it would be best to either use getTurnout and document then should already exist, or use valid system names.

(Some people might want to give _other_ user names, so valid system names might be best, but either is pretty good)

Bob

On Jul 24, 2019, at 1:21 PM, Geoffb <@Geoffb> wrote:

also Bob, your comment:
This script creates internal Turnout objects with names at start with AT and ST. Although it woks for you now, there’s no guarantee that always will. Better would be names like ITAT or ITST, which are the standard form and won’t (possibly) break in the future.
Uses ATxx and STxx as the User names for the internal Turnout objects or they could refer to hardware objects. I would think that User names as an independent alias for internal or hardware references would be "protected" as a system design feature of JMRI in the future, as it seems to be "advertised" as such (as consistent in future releases)?
No? Did I misunderstand?
--
Bob Jacobsen
@BobJacobsen

Bob Jacobsen
 

I think those changes will do it.

Another approach would be to pass the output stream into the listener objects, so that two separate copies of the class wouldn’t be needed.

Bob

On Jul 24, 2019, at 1:21 PM, Geoffb <@Geoffb> wrote:

If :
# get I/O connections for later
inputStream = port.getInputStream()
outputStream = port.getOutputStream()
...
outputStream.write(event.source.userName)
outputStream.write(",0")
...
outputStream.write(event.source.userName)
outputStream.write(",1")

were changed to
# get I/O connections for later
inputStreamPT = port.getInputStream()
outputStreamPT = port.getOutputStream()
...
outputStreamPT.write(event.source.userName)
outputStreamPT.write(",0")
...
outputStreamPT.write(event.source.userName)
outputStreamPT.write(",1")

and
# get I/O connections for later
inputStreamST = port.getInputStream()
outputStreamST = port.getOutputStream()
...
outputStreamST.write(event.source.userName)
outputStreamST.write(",0")
...
outputStreamST.write(event.source.userName)
outputStreamST.write(",1")

would these changes be enough to solve the problem?
--
Bob Jacobsen
@BobJacobsen

Geoffb
 

Hi Bob,
Thanks for all those comments and pointers.
Since way more people are using these than I had imagined, I will try to incorporate these and test them with multiple
USB channels when I can pull everything together.

Much appreciated.
Have fun!  :-)
Best regards,
Geoff Bunza
scalemodelanimation.com

Robert Schworm
 

Geoff write a py script.  I assume you have one like it and made the edits as required.  Sounds like perhaps  your relay is not being driven thru the library with a PWM signal??  REally do not know what to say as I do not have this sitution running here.  But a responder mentioned looking at the signal and duration going to the relay coil.

Bob

On Wed, Jul 24, 2019 at 6:50 PM Geoffb <gbglacier@...> wrote:
Hi Bob,
Thanks for all those comments and pointers.
Since way more people are using these than I had imagined, I will try to incorporate these and test them with multiple
USB channels when I can pull everything together.

Much appreciated.
Have fun!  :-)
Best regards,
Geoff Bunza
scalemodelanimation.com

Sam Simons
 

All,

A quick update on this - had some time this afternoon to implement the changes recommended by Geoff (namely, appending PT and ST to outputStream in the two scripts) and, after some work, I am pleased to report that the two arduinos are working as intended.

I'll have to continue to study the python scopes and namespaces to get an understanding of what is trying to be communicated, and examine other recommended solutions.

Sam