Lynxmotion LSS speed and position command in Python

Hi!

I would like to send both position and speed commands to each LSS object. I can see that there is a legacy command (speed (S)) but no indication of how to implement it.

Hardware concerned: Lynxmotion LSS ST1 & HT1

Software concerned: Python

Troubleshooting steps already taken: At the moment I am sending setMaxSpeed commend before move command e.g.:
myLSS = lss.LSS(1)
myLSS.setMaxSpeed(50)
myLSS.move(0)

Happy to bob along like this, but is there a better solution? If not I will make my own function to join these two together. e.g.

  def move(position, speed)
       myLSS.setMaxSpeed(speed)
       myLSS.move(postion)

Additional information:

Thank you so much in advance for your help!

1 Like

The LSS communication protocol itself does support adding a parameter (modifier) to an action command as you may already know (Wiki page here).

The modifiers are listed here (Wiki page). I recommend you use SD (degrees/s) instead of S (kinda awkward to convert to/from unless you are used to it already).

That’s not a bad idea at all and certainly a good starting point. Though you may want to go a step further and combine both commands into one command like you’d typically do in this case; i.e.: have the speed as a modifier to the move command.

Here are some info about this:

  • Here’s where you can find the definitions for the move command (D) and the speed (degrees/s) modifier (SD).
  • As you probably already noticed, the move function in the Python library (see actions commands here) takes only the position as input. And it itself calls an internal function called genericWrite which takes two mandatory parameters and one optional:
    • servo ID (mandatory)
    • command (mandatory)
    • parameter (optional); some commands don’t need this, such as “H” (Hold), but others, like motion, most certain do!

So, I see multiple ways to improve this:

  1. The quick and dirty: create a moveSpeed command (or something similar) and have it do like your above example, call both commands individually.
  2. The slightly better version: create a moveSpeed command that relies on a genericWriteModifier that adds support for a modifier and its parameter.
  3. The golden standard: create a moveSpeed function that uses the normal genericWrite but also supports the extra (but also optional) modifier & modifierParameter arguments! :slight_smile: I’d personally go for the last one since this shouldn’t be too difficult…

If going for # 3, the process would be something along the lines of:

  1. Modify genericWrite to add support for modifiers. This in general feels like a good idea since the LSS Python library is missing this right now. Of course, it must not break all the other calls to it without a modifier (but that seems trivial from what I can see)! :slight_smile:
  2. Add a moveSpeed function (instead of just a new/modified signature for move, since that would fit better the current set of commands and style in the library) that makes uses of the new signature of the genericWrite function.

So, genericWrite would go from:

# Write with a an optional parameter
def genericWrite(id, cmd, param = None):
	if LSS.bus is None:
		return False
	if param is None:
		LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + lssc.LSS_CommandEnd).encode())
	else:
		LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + lssc.LSS_CommandEnd).encode())
	return True

To something like:

# Write with a an optional parameter and possibly an optional modifier & parameter
def genericWrite(id, cmd, param = None, mod = none, modParam = none):
	if LSS.bus is None:
		return False
	# Assuming modifiers always need a parameter
	if (mod is None) or (modParam is None):
		if param is None:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + lssc.LSS_CommandEnd).encode())
		else:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + lssc.LSS_CommandEnd).encode())
	else:
		if param is None:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + mod + str(modParam) + lssc.LSS_CommandEnd).encode())
		else:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + mod + str(modParam) + lssc.LSS_CommandEnd).encode())
	return True

As for the new moveSpeed function, we’d copy the regular move function and go from this:

	def move(self, pos):
		return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos))

To this:

	def moveSpeed(self, pos, speed):
		return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ActionMaxSpeed, speed))

Adding some new functionality to the library and also opening quite a few other doors, such as using the other modifiers (T, CH, CL).

Unfortunately, I do not have any hardware to test this right now… so this may not even run! Haha! :stuck_out_tongue: Python is definitely not my strongest language… :sweat_smile:

@craigvear:
If you are willing to try it out for me, I can fork this repo and make the changes required. I’d just want it tested before I submit a pull request… :grin:

Sincerely,

edit: It does seem like the LSS Arduino library already has a genericWrite function with the ability to pass a modifier & modifier parameter. You can see it here. It was added to the Arduino library afterwards though, but I guess no one asked for it for the Python library until now (or worked around that, like your example above). I guess it is due for an upgrade! :stuck_out_tongue:

Ah, and also, if we are talking adding features… might as well refactor too! :smiley: So you’d go from the proposed change:

# Write with a an optional parameter and possibly an optional modifier & parameter
def genericWrite(id, cmd, param = None, mod = None, modParam = None):
	if LSS.bus is None:
		return False
	# Assuming modifiers always need a parameter
	if (mod is None) or (modParam is None):
		if param is None:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + lssc.LSS_CommandEnd).encode())
		else:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + lssc.LSS_CommandEnd).encode())
	else:
		if param is None:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + mod + str(modParam) + lssc.LSS_CommandEnd).encode())
		else:
			LSS.bus.write((lssc.LSS_CommandStart + str(id) + cmd + str(param) + mod + str(modParam) + lssc.LSS_CommandEnd).encode())
	return True

To maybe something like this (and remove repetition and make it more Python-like! plus removing complexity is always a good idea!)?

# Write with a an optional parameter and possibly an optional modifier & parameter
def genericWrite(id, cmd, param = None, mod = None, modParam = None):
	if LSS.bus is None:
		return False
	# Assuming modifiers always need a parameter
	if (mod is None) or (modParam is None):
		cmdStr = ""
	else:
		cmdStr = mod + str(modParam)
	if (param is None):
		cmdStr = cmd + cmdStr
	else:
		cmdStr = cmd + str(param) + cmdStr
	# Add header, ID and footer and send to bus
	cmdStr = (lssc.LSS_CommandStart + str(id) + cmdStr + lssc.LSS_CommandEnd)
	LSS.bus.write(cmdStr.encode())
	return True

Now that feels clean…er! :smiley:

edit: I guess the combining line could change too…
from:

# Add header, ID and footer and send to bus|
|cmdStr = (lssc.LSS_CommandStart + str(id) + cmdStr + lssc.LSS_CommandEnd)|

to:

# Add header, ID and footer and send to bus
cmdStr = "{}{}{}{}".format(lssc.LSS_CommandStart, str(id), cmdStr, lssc.LSS_CommandEnd)

If one was so inclined. I’ve seen this style around in Python code before… :thinking:

edit # 2: Corrected a (repeated) typo of none instead of None! Thanks @craigvear !

2 Likes

sincere thanks for the code and your help. Very simple, clean and clear. Will implement.

1 Like

Works a treat. Here is what I implemented in lss.py (NB I corrected a typo in your def line [None not none]:

# Write with an optional parameter and possibly an optional modifier & parameter
def genericWrite(id, cmd, param = None, mod = None, modParam = None):
	if LSS.bus is None:
		return False
	# Assuming modifiers always need a parameter
	if (mod is None) or (modParam is None):
		cmdStr = ""
	else:
		cmdStr = mod + str(modParam)
	if (param is None):
		cmdStr = cmd + cmdStr
	else:
		cmdStr = cmd + str(param) + cmdStr
	# Add header, ID and footer and send to bus
	cmdStr = "{}{}{}{}".format(lssc.LSS_CommandStart, str(id), cmdStr, lssc.LSS_CommandEnd)
	LSS.bus.write(cmdStr.encode())
	return True

def moveSpeed(self, pos, speed):
	return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ActionMaxSpeed, speed))

And the command in my script of:

myLSS.moveSpeed(300, 100)  # moves LSS servo to 30 degree at speed 100

Unfortunately, it means that any maxSpeed() setting I declare as a init parameter, gets ignored, but I could implement that safety check in my script.

Once again, thank you for your help

1 Like

Tryig again to get code formatting correct:

    # Write with an optional parameter and possibly an optional modifier & parameter
    def genericWrite(id, cmd, param = None, mod = None, modParam = None):
    	if LSS.bus is None:
    		return False
    	# Assuming modifiers always need a parameter
    	if (mod is None) or (modParam is None):
    		cmdStr = ""
    	else:
    		cmdStr = mod + str(modParam)
    	if (param is None):
    		cmdStr = cmd + cmdStr
    	else:
    		cmdStr = cmd + str(param) + cmdStr
    	# Add header, ID and footer and send to bus
    	cmdStr = "{}{}{}{}".format(lssc.LSS_CommandStart, str(id), cmdStr, lssc.LSS_CommandEnd)
    	LSS.bus.write(cmdStr.encode())
    	return True

    def moveSpeed(self, pos, speed):
        return (genericWrite(self.servoID, lssc.LSS_ActionMove, pos, lssc.LSS_ActionMaxSpeed, speed))
1 Like

Good. That was the goal, glad it was achieved! :stuck_out_tongue:

Woohoo! I’ll make an official pull request for it then… unless you want to? :smiley: It is all GPLv3 anyway! :stuck_out_tongue:

Thanks for noticing! I don’t have my dev environment setup for any of this in my office and I was too lazy to go to my other computer… haha! Yeah, that’s the reason… :stuck_out_tongue:
I fixed the post above too, so no one else is lead as astray!

No problem. I edited your post above by adding: [ code ] before and [ /code ] after the code block. That gives it auto formatting (like in my examples). I hope you don’t mind! :wink: Just go in edit for your post and you can see the change (or view history in the top right of the post header).

See above for how to get auto color for your code block using the proper markdown! :slight_smile:

This one’s pretty easy to fix! :slight_smile:
What users typically do is use the LSS Config (or command line, programmatically, etc.) to send a CSR (or CSD) command to set the maximum “config” speed for the LSS in question (instead of simply the session value).

As far as I know, the configuration value should always be respected. Actually, the session value should probably be respected, too… :thinking: Though it is possible it gets ignored if you request a higher speed as a modifier… You’ll have to try it out and report back! :smiley: I know for sure it is respected in wheel mode commands… but unsure in position ones! :open_mouth:

Perfect - that makes sense. And thanks for the edits, very much appreciated.

1 Like

Just tested setMaxSpeed with lssc.LSS_SetConfig and it did not respect this setting with moveSpeed() e.g.:

my_max_speed = 10
myLSS = lss.LSS(1)  # NB 0 doesnt work for my arm
myLSS.setMaxSpeed(my_max_speed, lssc.LSS_SetConfig)
myLSS.moveSpeed(300, 100)
1 Like

OK, so in “position control” mode, with commands like D and MD, a “speed modifier” allows you to ignore the set limit.
I think that was done by design… but I can’t really remember the rationale behind it to be honest… :stuck_out_tongue:

Maybe @cbenson can help us figure that one out. He has access to the tracker where that info was kept! :slight_smile:

2 Likes