Skip to content

Commit

Permalink
implement reset command
Browse files Browse the repository at this point in the history
  • Loading branch information
bleykauf committed Nov 24, 2023
1 parent bcbe63c commit b005284
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 14 deletions.
48 changes: 45 additions & 3 deletions meer_tec/mecom.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import random
import struct
from typing import Generic, Optional, Type, TypeVar
from typing import Generic, Literal, Optional, Type, TypeVar

from PyCRC.CRCCCITT import CRCCCITT as CRC

PARAM_CMDS = ["VS", "?VR"]
FloatOrInt = TypeVar("FloatOrInt", float, int)
ParamCmds = Literal["VS", "?VR"]


def calc_checksum(string: str) -> str:
"""Calculate CRC checksum."""
return f"{CRC().calculate(string):04X}"


def construct_mecom_cmd(
def construct_param_cmd(
device_addr: int,
cmd: str,
param_id: int,
Expand All @@ -36,10 +38,23 @@ def construct_mecom_cmd(
:return: MeCom command
"""
if seq_num is None:
# generate a random request number if none is given
seq_num = random.randint(0, 65535)

if seq_num < 0 or seq_num > 65535:
raise ValueError("seq_num must be between 0 and 65535")

if cmd not in PARAM_CMDS:
raise ValueError(f"cmd must be one of {PARAM_CMDS}")

if device_addr < 0 or device_addr > 255:
raise ValueError("device_addr must be between 0 and 255")

if cmd in ["VS", "?VR"] and param_id is None:
raise ValueError("param_id must be given for VS and ?VR commands")

if cmd == "VS":
if value is None:
raise ValueError("value must be given for VS command")
if value_type is float:
# convert float to hex of length 8, remove the leading '0X' and capitalize
val = hex(struct.unpack("<I", struct.pack("<f", value))[0])[2:].upper()
Expand All @@ -53,7 +68,34 @@ def construct_mecom_cmd(
return f"{cmd}{calc_checksum(cmd)}\r"


def construct_reset_cmd(device_addr: int, seq_num: Optional[int] = None) -> str:
"""
Construct a MeCom reset command.
:param device_addr: Device address (0 .. 255). Broadcast Device Address (0) will
send the command to all connected Meerstetter devices
:param seq_num: Sequence number (0 .. 65535). If not given, a random number will be
generated
:return: MeCom command
"""
if seq_num is None:
seq_num = random.randint(0, 65535)

if seq_num < 0 or seq_num > 65535:
raise ValueError("seq_num must be between 0 and 65535")

cmd = f"#{device_addr:02X}{seq_num:04X}RS"
return f"{cmd}{calc_checksum(cmd)}\r"


def verify_response(reponse: "Message", request: "Message") -> bool:
"""
Verify a MeCom response.
:param reponse: MeCom response
:param request: MeCom request
:return: True if response is valid, False otherwise
"""
checksum_correct = reponse.checksum == calc_checksum(reponse[0:-5])
request_match = reponse.seq_num == request.seq_num
return checksum_correct & request_match
Expand Down
16 changes: 10 additions & 6 deletions meer_tec/tec.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Optional, Type

from .interfaces import Interface, Message
from .mecom import FloatOrInt, construct_mecom_cmd
from .mecom import FloatOrInt, construct_param_cmd, construct_reset_cmd, verify_response


class TEC:
Expand All @@ -19,7 +19,7 @@ def get_parameter(
seq_num: Optional[int] = None,
param_inst: int = 1,
) -> FloatOrInt:
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=self.device_addr,
cmd="?VR",
param_id=param_id,
Expand All @@ -41,7 +41,7 @@ def set_parameter(
seq_num: Optional[int] = None,
param_inst: int = 1,
) -> None:
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=self.device_addr,
cmd="VS",
param_id=param_id,
Expand All @@ -52,12 +52,16 @@ def set_parameter(
)
request = Message(cmd, value_type)
reponse = Message(self.interface.query(request), value_type=value_type)
if not verify_response(reponse, request):
raise ValueError("Response does not match request")
print(reponse)

def reset(self) -> None:
# FIXME: Could not find this in the documentation
# self.set_parameter("?RS", value_type=int)
raise NotImplementedError
cmd = construct_reset_cmd(device_addr=self.device_addr)
request = Message(cmd, value_type=int)
reponse = Message(self.interface.query(request), value_type=int)
if not verify_response(reponse, request):
raise ValueError("Response does not match request")

# Common product parameters

Expand Down
16 changes: 11 additions & 5 deletions tests/test_mecom.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from meer_tec.mecom import construct_mecom_cmd
from meer_tec.mecom import construct_param_cmd, construct_reset_cmd


def test_vs_float() -> None:
# created with https://www.meerstetter.ch/MeCom/
CMD = "#7BEF32VS03E8E641C8CCCDE2C1\r"
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=123,
cmd="VS",
param_id=1000,
Expand All @@ -19,7 +19,7 @@ def test_vs_float() -> None:
def test_vr_float() -> None:
# created with https://www.meerstetter.ch/MeCom/
CMD = "#7BEF32?VR03E8E69AAD\r"
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=123,
cmd="?VR",
param_id=1000,
Expand All @@ -32,7 +32,7 @@ def test_vr_float() -> None:

def test_vs_int() -> None:
CMD = "#E6EA5FVS07E57B0000001958B0\r"
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=230,
cmd="VS",
param_id=2021,
Expand All @@ -46,7 +46,7 @@ def test_vs_int() -> None:

def test_vr_int() -> None:
CMD = "#E6EA5F?VR07E57BB3B5\r"
cmd = construct_mecom_cmd(
cmd = construct_param_cmd(
device_addr=230,
cmd="?VR",
param_id=2021,
Expand All @@ -55,3 +55,9 @@ def test_vr_int() -> None:
seq_num=59999,
)
assert cmd == CMD


def test_reset() -> None:
CMD = "#7B3039RSB5BB\r"
cmd = construct_reset_cmd(device_addr=123, seq_num=12345)
assert cmd == CMD

0 comments on commit b005284

Please sign in to comment.