Skip to content

Commit

Permalink
Merge pull request #3 from supermete/improvement/refactoring
Browse files Browse the repository at this point in the history
Put CanMessage/CanSignal inherited classes in canmessage. Delete trav…
  • Loading branch information
supermete authored Apr 15, 2024
2 parents b4d1a16 + d73d13b commit 484404e
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 194 deletions.
15 changes: 0 additions & 15 deletions .travis.yml

This file was deleted.

31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

[![image](https://img.shields.io/pypi/v/caroa04.svg)](https://pypi.python.org/pypi/caroa04)

[![See Build Status on GitHub Actions](https://github.com/supermete/caroa04/actions/workflows/python-app.yml/badge.svg)](https://github.com/supermete/caroa04/actions/workflows/python-app.yml)
[![Python versions](https://img.shields.io/pypi/pyversions/caroa04.svg)](https://pypi.org/project/caroa04)

[![Documentation Status](https://readthedocs.org/projects/caroa04/badge/?version=latest)](https://caroa04.readthedocs.io/en/latest/?version=latest)

[![See Build Status on GitHub Actions](https://github.com/supermete/caroa04/actions/workflows/python-app.yml/badge.svg)](https://github.com/supermete/caroa04/actions/workflows/python-app.yml)


Library to control the CAROA04 CAN-IO expander device from eletechsup.

- Free software: MIT license
Expand All @@ -15,7 +18,7 @@ Library to control the CAROA04 CAN-IO expander device from eletechsup.

You can install *caroa04* via [pip]() from [PyPI]():

$ pip install caroao4
$ pip install caroa04

## Usage

Expand All @@ -40,7 +43,29 @@ caro.shutdown() # free the bus

## Features

- TODO
- This library uses the python-can library to communicate with the device.
- The device has 4 digital outputs and 4 digital inputs. Hence the signals
can be read/written by using the attributes of the CaroA04 class:
- do1, do2, do3, do4 : digital output 1 to digital output 4
- di1, di2, di3, di4 : digital input 1 to digital input 4
- bitrate, node_id : bitrate and address code of the device
- Each signal has a raw value and a physical value. For example, the device does
not understand a bitrate in bps. It expects an enumeration that it will interpret.
So it can either be set by writing its physical value (bitrate.phys = 250000) or by writing
its raw value (bitrate.raw) as follows:
- 0: 5 kbps
- 1: 10 kbps
- 2: 20 kbps
- 3: 50 kbps
- 4: 100 kbps
- 5: 120 kbps
- 6: 200 kbps
- 7: 250 kbps
- 8: 400 kbps
- 9: 500 kbps
- 10: 800 kbps
- 11: 1000 kbps


## Credits

Expand Down
27 changes: 24 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ caroa04
.. image:: https://img.shields.io/pypi/v/caroa04.svg
:target: https://pypi.python.org/pypi/caroa04

.. image:: https://img.shields.io/pypi/pyversions/caroa04.svg
:target: https://pypi.org/project/caroa04
:alt: Python versions

.. image:: https://github.com/supermete/caroa04/actions/workflows/python-app.yml/badge.svg
:target: https://github.com/supermete/caroa04/actions/workflows/python-app.yml
:alt: See Build Status on GitHub Actions
:target: https://github.com/supermete/caroa04/actions/workflows/python-app.yml
:alt: See Build Status on GitHub Actions

.. image:: https://readthedocs.org/projects/caroa04/badge/?version=latest
:target: https://caroa04.readthedocs.io/en/latest/?version=latest
Expand Down Expand Up @@ -59,7 +63,24 @@ Usage
Features
--------

* TODO
* This library uses the python-can library to communicate with the device.
* The device has 4 digital outputs and 4 digital inputs. Hence the signals can be read/written by using the attributes of the CaroA04 class:
* do1, do2, do3, do4 : digital output 1 to digital output 4
* di1, di2, di3, di4 : digital input 1 to digital input 4
* bitrate, node_id : bitrate and address code of the device
* Each signal has a raw value and a physical value. For example, the device does not understand a bitrate in bps. It expects an enumeration that it will interpret. So it can either be set by writing its physical value (bitrate.phys = 250000) or by writing its raw value (bitrate.raw) as follows:
* 0: 5 kbps
* 1: 10 kbps
* 2: 20 kbps
* 3: 50 kbps
* 4: 100 kbps
* 5: 120 kbps
* 6: 200 kbps
* 7: 250 kbps
* 8: 400 kbps
* 9: 500 kbps
* 10: 800 kbps
* 11: 1000 kbps

Credits
-------
Expand Down
10 changes: 10 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
site_name: caroa04
site_description: Library to control the CAROA04 CAN-IO expander device from eletechsup.
site_author: Rodolphe Mete Soyding

theme: readthedocs

repo_url: https://github.com/supermete/caroa04

pages:
- Home: index.md
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ maintainers = [
{name = "Rodolphe Mete Soyding", email = "r.soyding@gmail.com"}
]
classifiers = [

"Programming Language :: Python",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
]
license = {text = "MIT license"}
dependencies = [
Expand Down
174 changes: 173 additions & 1 deletion src/caroa04/canmessage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import numpy
import logging
import can

__author__ = "R. Soyding"

Expand Down Expand Up @@ -241,6 +243,176 @@ def set_parent(self, message):
self.parent = message


class XCanSignal(CanSignal):
"""
Overrides CanSignal class to send a message on the CAN when signal is being read or written.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def raw(self):
"""
Get raw value of the signal
:return: raw value of the signal
"""
if self.parent is not None:
self.parent.read()

return self.value

@property
def phys(self):
"""
Get physical value of the signal
:return: physical value of the signal
"""
if self.parent is not None:
self.parent.read()

value = self.value

if self.type == BOOL:
return bool(value)
elif self.type == ENUM:
if self.value in self.enum:
return self.enum[self.value]
else:
return self.value
else:
if self.signed:
value *= float(self.factor)
value += self.offset
value &= int(f"0b{'1' * self.length}", 2)

return value

@raw.setter
def raw(self, value):
"""
Set the raw value of the signal
:param value: raw value to be set
:return: None
"""
self.value = int(value + self.offset) & int(f"0b{'1'*self.length}", 2)

if self.parent is not None:
self.parent.write()

@phys.setter
def phys(self, value):
"""
Set the physical value of the signal (applies factor or enum depending on signal's type)
:param value: physical value to be set
:return: None
"""
if self.type == ENUM:
if value in self.enum.values():
for key in self.enum:
if self.enum[key] == value:
self.value = key
break
else:
return # don't send anything if value is not valid
else:
if self.signed:
if self.length <= 8:
value = numpy.uint8(round(value / float(self.factor)))
elif self.length <= 16:
value = numpy.uint16(round(value / float(self.factor)))
elif self.length <= 32:
value = numpy.uint32(round(value / float(self.factor)))
elif self.length <= 64:
value = numpy.uint64(round(value / float(self.factor)))
self.value = ((value + self.offset) & int(f"0b{'1' * self.length}", 2))
else:
self.value = round((int(value / float(self.factor)) + self.offset) & int(f"0b{'1' * self.length}", 2))

if self.parent is not None:
self.parent.write()


class CanMessageRW(CanMessage):
"""
Overrides CanMessage class to use a different arbitration ID for reading and writing and actually handling
read and writes using the provided bus instance.
Includes a command byte support, where the first byte of the payload can be set to a specific value everytime a
signal read or write operation is requested.
"""
def __init__(self, node_id, read_id, write_id, **kwargs):
self.bus = kwargs.pop('bus', None)
self.rx_cmd_byte = kwargs.pop('rx_cmd_byte', None)
self.tx_cmd_byte = kwargs.pop('tx_cmd_byte', None)
self.cmd_byte = None
self.read_id = read_id | node_id
self.write_id = write_id | node_id
self._node_id = node_id
super().__init__(0, **kwargs)

if self.rx_cmd_byte is not None and self.tx_cmd_byte is not None:
self.cmd_byte = CanSignal(startbit=0, length=8)
self.add(self.cmd_byte)

@property
def node_id(self):
return self._node_id

@node_id.setter
def node_id(self, value):
self.read_id = (self.read_id & 0xFFFFFF00) | value
self.write_id = (self.write_id & 0xFFFFFF00) | value
self._node_id = value

def write(self):
"""
Sends message with the write identifier.
:return: None
"""
if self.bus is not None:
logging.debug(f"Sending message {self.write_id:#x}")

if self.cmd_byte is not None:
self.cmd_byte.raw = self.tx_cmd_byte

message = can.Message(arbitration_id=self.write_id,
data=self.payload,
is_extended_id=self.is_extended)
logging.debug(message)
self.bus.send(message)

self.bus.set_filters([{"can_id": self.write_id, "can_mask": 0x7ff, "extended": False}])
sts = self.bus.recv(5)
if sts is not None and sts.arbitration_id == self.write_id and sts.dlc > 0:
self.update_payload(sts.data)
else:
logging.warning('Could not get a response from the device')
self.bus.set_filters()

def read(self):
"""
Sends message with the read identifier and updates the signals with the received response.
:return: None
"""
if self.bus is not None:
if self.cmd_byte is not None:
self.cmd_byte.raw = self.rx_cmd_byte

message = can.Message(arbitration_id=self.read_id,
data=self.payload,
is_extended_id=self.is_extended)
logging.debug(message)
self.bus.send(message)

self.bus.set_filters([{"can_id": self.read_id, "can_mask": 0x7ff, "extended": False}])
sts = self.bus.recv(5)
if sts is not None and sts.arbitration_id == self.read_id and sts.dlc > 0:
logging.debug(sts)
self.update_payload(sts.data)
else:
logging.warning('Could not get a response from the device')
self.bus.set_filters()


if __name__ == "__main__":
msg_3c2 = CanMessage(0x3c2)
csm_fail = CanSignal(startbit=8, length=1)
Expand All @@ -252,4 +424,4 @@ def set_parent(self, message):
sig1.raw = 0xff

print(csm_fail.raw)
print(msg_3c2.payload)
print(msg_3c2.payload)
Loading

0 comments on commit 484404e

Please sign in to comment.