Skip to content

Commit

Permalink
Merge branch 'future'
Browse files Browse the repository at this point in the history
Pull in a few useful fixes, particularly for Python 3.12
  • Loading branch information
petercorke committed Aug 5, 2024
2 parents a2fe453 + f593bcc commit 9d2b720
Show file tree
Hide file tree
Showing 9 changed files with 452 additions and 163 deletions.
20 changes: 17 additions & 3 deletions bdsim/blockdiagram.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ def connect(self, start, *ends, name=None):

# start.type = 'start'

# ensure all blocks are in the blocklist
for x in [start, *ends]:
if isinstance(x, Block):
if x.bd is None:
self.add_block(x)
elif isinstance(x, Plug):
if x.block.bd is None:
self.add_block(x.block)

for end in ends:
if isinstance(start, Block):
if isinstance(end, Block):
Expand Down Expand Up @@ -764,7 +773,7 @@ def schedule_dotfile(self, filename):
def _debugger(self, simstate=None, integrator=None):
if simstate.t_stop is not None and simstate.t < simstate.t_stop:
return

def print_output(b, t, inports, x):
out = b.output(t, inports, x)
if len(out) == 1:
Expand Down Expand Up @@ -803,7 +812,9 @@ def print_output(b, t, inports, x):
if b.nout > 0:
print_output(b, t, b.inputs, b._x)
elif cmd[0] == "i":
print(f"status={integrator.status}, dt={integrator.step_size:.4g}, nfev={integrator.nfev}")
print(
f"status={integrator.status}, dt={integrator.step_size:.4g}, nfev={integrator.nfev}"
)
elif cmd[0] == "s":
# step
break
Expand All @@ -825,7 +836,9 @@ def print_output(b, t, inports, x):
print(self.debug_watch)
self.debug_watch = None
else:
self.debug_watch = [self.blocklist[int(s.strip())] for s in cmd[2:].split(" ")]
self.debug_watch = [
self.blocklist[int(s.strip())] for s in cmd[2:].split(" ")
]
elif cmd == "pdb":
import pdb

Expand All @@ -845,6 +858,7 @@ def print_output(b, t, inports, x):
except (IndexError, ValueError, TypeError):
print("??")
pass

# ---------------------------------------------------------------------- #

def report_summary(self, sortby="name", **kwargs):
Expand Down
96 changes: 96 additions & 0 deletions bdsim/blocks/IO/Firmata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Define real-time i/o blocks for use in block diagrams. These are blocks that:
- have inputs or outputs
- have no state variables
- are a subclass of ``SourceBlock`` or ``SinkBlock``
"""
# The constructor of each class ``MyClass`` with a ``@block`` decorator becomes a method ``MYCLASS()`` of the BlockDiagram instance.

from bdsim.components import SinkBlock, SourceBlock
import time
import sys


class FirmataIO:
board = None
port = "/dev/cu.usbmodem1441401"

def __init__(self):
from pyfirmata import Arduino, util

if FirmataIO.board is None:
print(f"connecting to Arduino/firmata node on {self.port}...", end="")
sys.stdout.flush()
FirmataIO.board = Arduino(self.port)
print(" done")

# start a background thread to read inputs
iterator = util.Iterator(FirmataIO.board)
iterator.start()
time.sleep(0.25) # allow time for the iterator thread to start

def pin(self, name):
return FirmataIO.board.get_pin(name)


class AnalogIn(SourceBlock):
nin = 0
nout = 1

def __init__(self, pin=None, scale=1.0, offset=0.0, **blockargs):
super().__init__(**blockargs)
self.board = FirmataIO()
self.pin = self.board.pin(f"a:{pin}:i")
self.scale = scale
self.offset = offset

# deal with random None values at startup
while self.pin.read() == None:
time.sleep(0.1)

def output(self, t, inports, x):
return [self.scale * self.pin.read() + self.offset]


class AnalogOut(SinkBlock):
nin = 1
nout = 0

def __init__(self, pin=None, scale=1.0, offset=0.0, **blockargs):
super().__init__(**blockargs)
self.board = FirmataIO()
self.pin = self.board.pin(f"d:{pin}:p") # PWM output
self.scale = scale
self.offset = offset

def step(self, t, inports):
self.pin.write(self.scale * inports[0] + self.offset)


class DigitalIn(FirmataIO, SourceBlock):
nin = 0
nout = 1

def __init__(self, pin=None, bool=False, **blockargs):
super().__init__(**blockargs)
self.pin = self.board.get_pin(f"d:{pin}:i")

def output(self, t, inports, x):
if self.bool:
return [self.pin.read()]
else:
return [self.pin.read() > 0]


class DigitalOut(FirmataIO, SinkBlock):
nin = 1
nout = 0

def __init__(self, pin=None, **blockargs):
super().__init__(**blockargs)
self.pin = self.board.get_pin(f"d:{pin}:o")

def step(self, t, inports):
self.pin.write(inports[0] > 0)
19 changes: 19 additions & 0 deletions bdsim/blocks/IO/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
This folder contains "drivers" for particular i/o hardware configurations.

Each file contains a set of class definitions which are imported in the normal
Python fashion, not dynamically as are most bdsim blocks.

| File | Purpose |
|------|---------|
|Firmata.py | analog and digital i/o for Arduino + Firmata |

Notes:

- For [Firmata](https://github.com/firmata/protocol) you need to load a Firmata sketch onto the Arduino.
- For now the port and the baudrate (57600) are hardwired. Perhaps the i/o system is initialized by a
separate function, or options passed through BDRealTime.
- There are myriad Firmata variants, but StandardFirmata is provided as a built-in
example with the Arduino IDE and does digital and analog i/o. It runs fine on a Uno.
- ConfigurableFirmata has more device options but needs to be installed, and its default
baud rate is 115200. It does not include quadrature encoders :(
- The Firmata interface is [pyfirmata](https://github.com/tino/pyFirmata), old but quite solid and efficient.
1 change: 0 additions & 1 deletion bdsim/blocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ The class definitions are grouped by block class
|discrete.py | discrete transfer blocks |
|displays.py | graphical sink blocks |
|functions.py | function blocks without state |
|io.py | |
|linalg.py | linear algebra blocks |
|sinks.py | signal sink blocks |
|sources.py | signal source blocks |
Expand Down
24 changes: 12 additions & 12 deletions bdsim/blocks/displays.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@

import matplotlib.pyplot as plt
from matplotlib.pyplot import Polygon
from numpy.lib.shape_base import expand_dims


import spatialmath.base as sm

from bdsim.components import SinkBlock
from bdsim.graphics import GraphicsBlock


# ------------------------------------------------------------------------ #


class Scope(GraphicsBlock):
"""
r"""
:blockname:`SCOPE`
Plot input signals against time.
Expand All @@ -50,7 +47,7 @@ class Scope(GraphicsBlock):
Create a scope block that plots multiple signals against time.
For each line plotted we can specify the:
* line style as a heterogeneous list of:
* Matplotlib `fmt` string comprising a color and line style, eg. ``"k"`` or ``"r:"``
Expand Down Expand Up @@ -90,7 +87,7 @@ class Scope(GraphicsBlock):
bd.SCOPE(styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}])
**Single input port with NumPy array**
The port is fed with a 1D-array, and ``vector`` is an:
* int, this is the expected width of the array, all its elements will be plotted
Expand All @@ -101,11 +98,11 @@ class Scope(GraphicsBlock):
bd.SCOPE(vector=[0,1,2]) # display elements 0, 1, 2 of array on port 0
bd.SCOPE(vector=[0,1], styles=[{'color': 'blue'}, {'color': 'red', 'linestyle': '--'}])
.. note::
* If the vector is of width 3, by default the inputs are plotted as red, green
and blue lines.
.. note::
* If the vector is of width 3, by default the inputs are plotted as red, green
and blue lines.
* If the vector is of width 6, by default the first three inputs are plotted as
solid red, green and blue lines and the last three inputs are plotted as
solid red, green and blue lines and the last three inputs are plotted as
dashed red, green and blue lines.
"""

Expand Down Expand Up @@ -239,7 +236,6 @@ def start(self, simstate):
np.array([]),
] * self.nplots


# create the figures
self.fig = self.create_figure(simstate)
self.ax = self.fig.add_subplot(111)
Expand Down Expand Up @@ -303,12 +299,16 @@ def start(self, simstate):
if self.scale != "auto":
self.ax.set_ylim(*self.scale)
if self.labels is not None:

def fix_underscore(s):
if s[0] == "_":
return "-" + s[1:]
else:
return s
self.ax.legend([fix_underscore(label) for label in self.labels], loc=self.loc)

self.ax.legend(
[fix_underscore(label) for label in self.labels], loc=self.loc
)

if self.watch:
for wire in self.input_wires:
Expand Down
42 changes: 0 additions & 42 deletions bdsim/blocks/io.py

This file was deleted.

Loading

0 comments on commit 9d2b720

Please sign in to comment.