Skip to content

Commit e790a49

Browse files
committed
Squashed commit of the following:
Added docstrings to DigitalIO class Added raspberypi DigitialIO driver. First working implmentation of DIO abc and simulator device Added an abc for a DigitalIO device class
1 parent 7d222a6 commit e790a49

File tree

3 files changed

+272
-0
lines changed

3 files changed

+272
-0
lines changed

microscope/abc.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1555,3 +1555,119 @@ def move_to(self, position: typing.Mapping[str, float]) -> None:
15551555
15561556
"""
15571557
raise NotImplementedError()
1558+
1559+
class DigitalIO(Device, metaclass=abc.ABCMeta):
1560+
"""ABC for digital IO devices.
1561+
1562+
Digital IO devices (DIO) have a num,ber of digital lines that can
1563+
be for output, or optionally input and can switch between a on and
1564+
off state.
1565+
1566+
Args:
1567+
numLines: total number of digital lines numberes 0 to n-1.
1568+
1569+
"""
1570+
1571+
def __init__(self, numLines: int, **kwargs) -> None:
1572+
super().__init__(**kwargs)
1573+
if numLines < 1:
1574+
raise ValueError(
1575+
"NumLines must be a positive number (was %d)" % positions
1576+
)
1577+
self._numLines = numLines
1578+
1579+
#array to map wether lines are input or output
1580+
# true is output, start with all lines defined for output.
1581+
self._IOMap = [True]*self._numLines
1582+
1583+
@abc.abstractmethod
1584+
def get_num_lines(self):
1585+
"""Returns the number of Io lines present in this instance"""
1586+
return self._numLines
1587+
1588+
@abc.abstractmethod
1589+
def set_IO_state(self, line: int, state: bool):
1590+
"""Sets the state of a single Digital line to either Output or Input
1591+
1592+
Args:
1593+
line: The line to have its mode set.
1594+
1595+
state: True for Output or False for Input."""
1596+
raise NotImplementedError()
1597+
1598+
@abc.abstractmethod
1599+
def get_IO_state(self, line):
1600+
"""Returns the state of a single Digital line, either Output or Input
1601+
1602+
Args:
1603+
line: The line to have its mode set.
1604+
Return value is True for Output and False for Input"""
1605+
raise NotImplementedError()
1606+
1607+
@abc.abstractmethod
1608+
def set_all_IO_state(self, stateArray):
1609+
"""Sets the state of all lines to either Input or Output
1610+
Args:
1611+
line: The line to have its mode set.
1612+
stateArray: Boolean array for the lines, True in output False
1613+
is Input"""
1614+
for i, state in enumerate(stateArray):
1615+
#set each line as defined in stateArray
1616+
self.set_IO_state(i,state)
1617+
1618+
1619+
@abc.abstractmethod
1620+
def get_all_IO_state(self):
1621+
"""Returns the state of a all Digital line, either Output or Input
1622+
1623+
Returns a boolean array one entry for each line,
1624+
True for Output and False for Input"""
1625+
1626+
stateArray=[None]*self._numLines
1627+
for i in range(self._numLines):
1628+
stateArray[i]=self.get_IO_state(i)
1629+
return stateArray
1630+
1631+
@abc.abstractmethod
1632+
def write_line(self,line,ouput):
1633+
"""Sets the level of a single output line
1634+
1635+
Args:
1636+
line: the line to be set
1637+
output: the level True for high and Flase for low."""
1638+
1639+
raise NotImplementedError()
1640+
1641+
@abc.abstractmethod
1642+
def write_all_lines(self,ouput_array):
1643+
"""Sets the output level of every output line.
1644+
1645+
Args:
1646+
output_array: Boolean array of output states True for high,
1647+
False for low, array entries for lines set
1648+
as inputs are ignored."""
1649+
1650+
if len(output_array) != self._numLines :
1651+
raise("Output array must be numLines in length")
1652+
for i in range(self._numLines):
1653+
#set line i to the IOMap entry, true for output false for input.
1654+
if(not self._IOMap[i]):
1655+
self.write_line(i,output_array[i])
1656+
1657+
@abc.abstractmethod
1658+
def read_line(self,line):
1659+
"""Read a single input line.
1660+
Args:
1661+
line: the line to read
1662+
Return: A boolean of the line state"""
1663+
raise NotImplementedError()
1664+
1665+
@abc.abstractmethod
1666+
def read_all_lines(self):
1667+
"""Read all the input lines.
1668+
Return: Boolean Array with outline enteries set to None."""
1669+
1670+
readarray=[None]*self._numLines
1671+
for i in range(self._numLines):
1672+
readarray[i]=self.read_line(i)
1673+
return(readarray)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
3+
## Copyright (C) 2020 David Miguel Susano Pinto <carandraug@gmail.com>
4+
## Copyright (C) 2023 Ian Dobbie <ian.dobbie@jhu.edu>
5+
##
6+
##
7+
## This file is part of Microscope.
8+
##
9+
## Microscope is free software: you can redistribute it and/or modify
10+
## it under the terms of the GNU General Public License as published by
11+
## the Free Software Foundation, either version 3 of the License, or
12+
## (at your option) any later version.
13+
##
14+
## Microscope is distributed in the hope that it will be useful,
15+
## but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
## GNU General Public License for more details.
18+
##
19+
## You should have received a copy of the GNU General Public License
20+
## along with Microscope. If not, see <http://www.gnu.org/licenses/>.
21+
22+
"""Raspberry Pi Digital IO module.
23+
"""
24+
25+
import contextlib
26+
import re
27+
import threading
28+
import time
29+
import typing
30+
import logging
31+
32+
import microscope.abc
33+
34+
import RPi.GPIO as GPIO
35+
36+
37+
#Support for async digital IO control on the Raspberryy Pi.
38+
#Currently supports digital input and output via GPIO lines
39+
40+
41+
# Use BCM GPIO references (naming convention for GPIO pins from Broadcom)
42+
# instead of physical pin numbers on the Raspberry Pi board
43+
GPIO.setmode(GPIO.BCM)
44+
_logger = logging.getLogger(__name__)
45+
46+
47+
48+
class RPiDIO(microscope.abc.DigitalIO):
49+
'''Digital IO device implementation for a Raspberry Pi
50+
51+
gpioMap input array maps line numbers to specific GPIO pins
52+
[GPIO pin, GPIO pin]
53+
[27,25,29,...] line 0 in pin 27, 1 is pin 25 etc....
54+
55+
gpioState input array maps output and input lines.
56+
True maps to output
57+
False maps to input
58+
59+
with the gpioMap above [True,False,True,..] would map:
60+
27 to out,
61+
25 to in
62+
29 to out'''
63+
64+
def __init__(self,gpioMap = [], gpioState = [], **kwargs):
65+
super().__init__(numLines=len(gpioMap),**kwargs)
66+
#setup io lines 1-n mapped to GPIO lines
67+
self._gpioMap=gpioMap
68+
self._IOMap=gpioState
69+
self._numLines=len(self._gpioMap)
70+
self._outputCache = [False]*self._numLines
71+
self.set_all_IO_state(self._IOMap)
72+
73+
74+
75+
#functions needed
76+
77+
def set_IO_state(self, line: int, state: bool) -> None:
78+
_logger.debug("Line %d set IO state %s"% (line,str(state)))
79+
if state:
80+
#true maps to output
81+
GPIO.setup(self._gpioMap[line],GPIO.OUT)
82+
self._IOMap[line] = True
83+
#restore state from cache.
84+
state=self._outputCache[line]
85+
GPIO.output(self._gpioMap[line],state)
86+
else:
87+
GPIO.setup(self._gpioMap[line],GPIO.IN)
88+
self._IOMap[line] = False
89+
90+
def get_IO_state(self, line: int) -> bool:
91+
#returns
92+
# True if the line is Output
93+
# Flase if Input
94+
# None in other cases (i2c, spi etc)
95+
pinmode=GPIO.gpio_function(self._gpioMap[line])
96+
if pinmode==GPIO.OUT:
97+
return True
98+
elif pinmode==GPIO.IN:
99+
return False
100+
return None
101+
102+
def write_line(self,line: int, state: bool):
103+
#Do we need to check if the line can be written?
104+
_logger.debug("Line %d set IO state %s"% (line,str(state)))
105+
self._outputCache[line]=state
106+
GPIO.output(self._gpioMap[line],state)
107+
108+
def read_line(self,line: int) -> bool:
109+
# Should we check if the line is set to input first?
110+
#If input read the real state
111+
if (not self._IOMap[line]):
112+
state=GPIO.input(self._gpioMap[line])
113+
_logger.debug("Line %d returns %s" % (line,str(state)))
114+
return state
115+
else:
116+
#line is an outout so returned cached state
117+
return self._outputCache[line]
118+
119+
def _do_shutdown(self) -> None:
120+
pass

microscope/simulators/__init__.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Copyright (C) 2020 David Miguel Susano Pinto <carandraug@gmail.com>
44
## Copyright (C) 2020 Mick Phillips <mick.phillips@gmail.com>
5+
## Copyright (C) 2022 Ian Dobbie <ian.dobbie@gmail.com>
56
##
67
## This file is part of Microscope.
78
##
@@ -479,3 +480,38 @@ def move_by(self, delta: typing.Mapping[str, float]) -> None:
479480
def move_to(self, position: typing.Mapping[str, float]) -> None:
480481
for name, pos in position.items():
481482
self.axes[name].move_to(pos)
483+
484+
class SimulatedDigitalIO(microscope.abc.DigitalIO):
485+
def __init__(self, **kwargs):
486+
super().__init__(**kwargs)
487+
self._cache=[None]*self._numLines
488+
489+
def set_IO_state(self, line: int, state: bool) -> None:
490+
_logger.info("Line %d set IO state %s"% (line,str(state)))
491+
self._IOMap[line] = state
492+
if not state:
493+
#this is an input so needs to have a definite value,
494+
#default to False if not already set. If set leave alone
495+
if self._cache[line]== None:
496+
self._cache[line]=False
497+
498+
def get_IO_state(self, line: int) -> bool:
499+
return(self._IOMap[line])
500+
501+
def write_line(self,line: int, state: bool):
502+
_logger.info("Line %d set IO state %s"% (line,str(state)))
503+
self._cache[line]=state
504+
505+
def read_line(self,line: int) -> bool:
506+
_logger.info("Line %d returns %s" % (line,str(self._cache[line])))
507+
return self._cache[line]
508+
509+
def _do_shutdown(self) -> None:
510+
pass
511+
512+
513+
#DIO still to do:
514+
# raise exception if writing to a read line and vis-versa
515+
# raise exception if line <0 or line>num_lines
516+
# read all lines to return True,Flase if readable and None if an output
517+
#

0 commit comments

Comments
 (0)