Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "src/__main__.py",
// "module": "examples.dyscom.example_dyscom_send_file",
// "program": "src/__main__.py",
"module": "examples.mid_level.example_mid_level_update",
"justMyCode": false,
// "args": ["COM3"],
"console": "integratedTerminal"
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Python 3.11 or higher
- Demonstrates how to use mid level layer, where a stimulation pattern is send to the stimulator and the device automatically executes the pattern by itself for 15s
- `python -m examples.mid_level.example_mid_level`
- Demonstrates how to use mid level layer, where a stimulation pattern is send to the stimulator and the device automatically executes the pattern by itself until user ends stimulation by keyboard
- `python -m examples.mid_level.example_mid_level_update`
- Demonstrates how to use mid level layer, to toggle stimulation channels by keyboard
- Low level layer
- `python -m examples.low_level.example_low_level`
- Demonstrates how to use low level layer, where host has to trigger stimulation manually, in this case by pressing a key
Expand Down Expand Up @@ -128,4 +130,4 @@ Python 3.11 or higher
- Added sample that demonstrates how to read measurement data files from I24 devices

## 0.0.18
- More documentation
- Fixed error for mid level update when not using all channels
99 changes: 99 additions & 0 deletions examples/mid_level/example_mid_level_update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Provides an example how to use mid level layer"""

import sys
import asyncio

from science_mode_4 import DeviceP24
from science_mode_4 import MidLevelChannelConfiguration
from science_mode_4 import ChannelPoint
from science_mode_4 import SerialPortConnection
from examples.utils.example_utils import ExampleUtils, KeyboardInputThread


async def main() -> int:
"""Main function"""

# some points
p1: ChannelPoint = ChannelPoint(200, 20)
p2: ChannelPoint = ChannelPoint(100, 0)
p3: ChannelPoint = ChannelPoint(200, -20)
# channel configuration
# we want to ignore first and last two channels (just for demonstration purpose how to handle unused channels)
# we need to pad list with None to achieve correct indices
# [None, None, ChannelConfig, ChannelConfig, ChannelConfig, ChannelConfig]
channel_config = [MidLevelChannelConfiguration(False, 3, 20, [p1, p2, p3]) for x in range(4)]
channel_config.insert(0, None)
channel_config.insert(0, None)

# keyboard is our trigger to end program
def input_callback(input_value: str) -> bool:
"""Callback call from keyboard input thread"""
# print(f"Input value {input_value}")

if input_value == "q":
# end keyboard input thread
return True
if "1" <= input_value <= "8":
index = int(input_value) - 1
# check if index is in range of channel_config
if 0 <= index < len(channel_config):
cc = channel_config[index]
# check if index contains a ChannelConfiguration object
if cc is not None:
# toggle active
cc.is_active = not cc.is_active
asyncio.run(mid_level.update(channel_config))
else:
print("Channel config is None")
else:
print("Invalid channel config index")

return False


print("Invalid command")
return False

print("Usage: press 1-8 to toggle channel, press q to quit")
print("Only channels 3-6 have a channel configuration (see comments where configuration is created)")
# create keyboard input thread for non blocking console input
keyboard_input_thread = KeyboardInputThread(input_callback)

# get comport from command line argument
com_port = ExampleUtils.get_comport_from_commandline_argument()
# create serial port connection
connection = SerialPortConnection(com_port)
# open connection, now we can read and write data
connection.open()

# create science mode device
device = DeviceP24(connection)
# call initialize to get basic information (serial, versions) and stop any active stimulation/measurement
# to have a defined state
await device.initialize()

# get mid level layer to call mid level commands
mid_level = device.get_layer_mid_level()
# call init mid level, we want to stop on all stimulation errors
await mid_level.init(True)
# set stimulation pattern, P24 device will now stimulate according this pattern
await mid_level.update(channel_config)

while keyboard_input_thread.is_alive():
# we have to call get_current_data() every 1.5s to keep stimulation ongoing
update = await mid_level.get_current_data() # pylint:disable=unused-variable
# print(update)

await asyncio.sleep(1)

# call stop mid level
await mid_level.stop()

# close serial port connection
connection.close()
return 0


if __name__ == "__main__":
res = asyncio.run(main())
sys.exit(res)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "science_mode_4"
version = "0.0.17"
version = "0.0.18"
authors = [
{ name="Marc Hofmann", email="marc-hofmann@gmx.de" },
]
Expand Down
4 changes: 3 additions & 1 deletion src/science_mode_4/mid_level/mid_level_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ async def stop(self):


async def update(self, channel_configuration: list[MidLevelChannelConfiguration]):
"""Send mid level update command and waits for response"""
"""Send mid level update command and waits for response.
channel_configuration describes configuration for each channel, channel number is the index in list,
if some channels are not used, use None for this index"""
p = PacketMidLevelUpdate()
p.channel_configuration = channel_configuration
ack: PacketMidLevelUpdateAck = await self.send_packet_and_wait(p)
Expand Down
24 changes: 24 additions & 0 deletions src/science_mode_4/mid_level/mid_level_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,43 @@ def is_active(self) -> bool:
return self._is_active


@is_active.setter
def is_active(self, is_active: bool):
"""Setter for is active"""
self._is_active = is_active


@property
def ramp(self) -> int:
"""Getter for ramp"""
return self._ramp


@ramp.setter
def ramp(self, ramp: int):
"""Setter for ramp"""
self._ramp = ramp


@property
def period_in_ms(self) -> int:
"""Getter for period"""
return self._period_in_ms


@period_in_ms.setter
def period_in_ms(self, period_in_ms: int):
"""Setter for period in ms"""
self._period_in_ms = period_in_ms


@property
def points(self) -> list[ChannelPoint]:
"""Getter for points"""
return self._points


@points.setter
def points(self, points: list[ChannelPoint]):
"""Setter for points"""
self._points = points
4 changes: 3 additions & 1 deletion src/science_mode_4/mid_level/mid_level_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def channel_configuration(self) -> list[MidLevelChannelConfiguration]:
@channel_configuration.setter
def channel_configuration(self, channel_configuration: list[MidLevelChannelConfiguration]):
"""Setter for channel configuration"""
if len(channel_configuration) > 8:
raise ValueError(f"Mid level support 8 channels at max, given length {len(channel_configuration)}")
self._channel_configuration = channel_configuration


Expand All @@ -37,7 +39,7 @@ def get_data(self) -> bytes:

for x in range(8):
c: MidLevelChannelConfiguration | None = self._channel_configuration[x] if x < len(self._channel_configuration) else None
if c:
if c is not None and c.is_active:
bb.append_bytes(c.get_data())

return bb.get_bytes()
Expand Down