README.md

# Cantastic

Cantastic is an Elixir library to interact with CAN/Bus via lib_socket_can (Linux only).
It does all the heavy lifting of parsing the incoming frames and sending the outgoing ones at the right frequencies.

RAW and ISOTP modes are currently supported, BCM (Broadcast Manager) support is planned.

## Installation

in the `mix.exs` file:

```elixir
def deps do
  [{:cantastic, "~> 1.0.0"}]
end
```

## OTP App Configuration

### Example

```elixir
config :cantastic,
  can_network_mappings: "ovcs:vcan0,leaf_drive:vcan1,polo_drive:vcan2",
  setup_can_interfaces: true,
  otp_app: :vms_core,
  priv_can_config_path: "polo_2007.yml",
  enable_socketcand: true,
  socketcand_ip_interface: "wlan0"
```

## Description

Cantastic supports the following configuration options:

| Key | Description | Default value |
|-----|-------------|---------------|
| `:can_network_mappings` | A comma separated list of can network names and related interfaces. |  |
| `:setup_can_interfaces`| Wheher Cantastic should setup the CAN interfaces. It requires the Elixir user to have the approriate rights (usually the case for Nerves hosts). | `false` |
| `:otp_app` | The name of the OTP app owning the priv directory where the can config file is stored |  |
| `:priv_can_config_path` | The relative path where the Yaml config file is located |  |
| `:enable_socketcand` | Wheter Cantastic should start the socketcand server on all configured interfaces. This allows to remotely access the CAN interfaces for debugging.| `false` |
| `:socketcand_ip_interface` | The IP interface on which socketcand should listen to. | `"eth0"` |

## YAML configuration file

Cantastic requires you to define a YAML file describing the frames to be sent and received and how to interpret them.
This allows you to declaratively define your CAN networks in a clear and maintainable format.

### Example

```yaml
---
can_networks:
  ovcs:
    bitrate: 500000
    emitted_frames:
      - name: contactors_status_request
        id: 0x100
        frequency: 20
        signals:
          - name: main_negative_contactor_enabled
            kind: enum
            value_start: 0
            value_length: 8
            mapping:
              0x00: false
              0x01: true
          - name: main_positive_contactor_enabled
            kind: enum
            value_start: 8
            value_length: 8
            mapping:
              0x00: false
              0x01: true
    received_frames:
      - name: car_controls_status
        id: 0x200
        frequency: 10
        signals:
          - name: raw_max_throttle
            kind: integer
            value_start: 0
            value_length: 16
          - name: raw_throttle
            kind: integer
            value_start: 16
            value_length: 16
          - name: requested_gear
            kind: enum
            value_start: 48
            value_length: 8
            mapping:
              0x00: drive
              0x01: neutral
              0x02: reverse
              0x03: parking
```

### Detailed YAML file structure:

#### Top level property

| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:can_networks` | a map of can networks to connect to in the form `network_name: {...network definitions...}` | True | |

#### Network properties

| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:bitrate` | The CAN network speed in bits per seconds. | True |  |
| `:emitted_frames` | An array of frame definitions. | False | [] |
| `:received_frames` | An array of frame definitions.  | False | [] |
| `:obd2_requests` | An array of OBD2 request definitions. | False | [] |

##### Example

```YAML
#  my_vehicle.yml
---
can_networks:
  my_network:
    bitrate: 500000
    emitted_frames:
      - name: frame1
      - .....
    received_frames:
      - name: frame2
        ....
    obd2_requests:
      - name: request1
      - ....
```

#### Frame definitions

| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:id` | The CAN Frame ID | True | |
| `:name` | The CAN Frame name, will be used in your own code to reference it | True |  |
| `:frequency` | The frequency is milliseconds at which the frame should be emitted/is expected to be received | True for emitted frames, False for received frames |  |
| `:allowed_frequency_leeway` | The tolerance in milliseconds to be added to the frequency by the `Cantastic.ReceivedFrameWatcher` when monitoring the frame frequency | False | 10 |
| `:allowed_missing_frames` | The number of missed frames before `Cantastic.ReceivedFrameWatcher` should send `handle_missing_frame` messages to subscribers | False | 5 |
| `:allowed_missing_frames_period` | Timeframe in milliseconds during which `Cantastic.ReceivedFrameWatcher` is counting the number of missing frames | False | 5_000 |
| `:required_on_time_frames` | The number of frames received at the expected frequency to consider a frame back to 'normal' | false | 5 |
| `:signals` | An array of signals to be interpreted in this frame | False | [] |


##### Example

```YAML
---
can_networks:
  my_network:
    bitrate: 500000
    received_frames:
      - name: frame1
        id: 0x100
        frequency: 20
        signals:
          - name: signal1
            ....
          - name: signal1
            ....
```

#### Signal definitions

| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:name` | The signal name, will be used in your own code to reference it | True |  |
| `:value_start` | The bit number where the raw signal starts | True | |
| `:value_length` | The number of bits to use for this signal | True | |
| `:kind` | The type of value to be returned, one of: `"decimal"`, `"integer"`, `"static"`, `"enum"` | False | `"decimal"` |
| `:precision` | The precision to which a decimal signal should be rounded to | False | 2 |
| `:sign` | Wheter the signal should be interpreted as a signed or unsigned integer | False | `"unsigned"` |
| `:endianness` | The endianness to be used to interpret the signal | False | `"little"` |
| `:mapping` | For `"enum"` values, a map for each integer value | False | {} |
| `:unit` | An informational unit related to the signal's value | False | |
| `:scale` | A decimal scale to be applied on the raw value, defined as a string in YAML | False | "1" |
| `:offset` | A decimal offset to be applied on the raw value, defined as a string in YAML  | False | "0" |
| `:value` | For `"static"` values, the integer raw representation to be used  | False | |

##### Example

```YAML
---
can_networks:
  my_network:
    bitrate: 500000
    received_frames:
      - name: frame1
        id: 0x100
        frequency: 20
        signals:
          - name: decimal_signal
            value_start: 0
            value_length: 8
            kind: decimal
            precision: 3
            sign: signed
            endianness: big
            scale: "0.3444"
            offset: "30"
          - name: boolean_signal
            value_start: 8
            value_length: 1
            kind: mapping
            mapping:
              0x00: false
              0x01: true
          - name: static_signal
            value_start: 9
            value_length: 8
            kind: static
            value: 0xAB

```

#### OBD2 request definitions


| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:name` | The OBD2 Request name, will be used in your own code to reference it | True |  |
| `:request_frame_id` | The CAN Frame ID to be used for the OBD2 request | True | |
| `:response_frame_id` | The CAN Frame ID of the frame used for the response | True | |
| `:frequency` | The frequency is milliseconds at which the request should be emitted | True |  |
| `:mode` | The OBD2 mode to be used | True |  |
| `:parameters` | An array of parameters to be interpreted in this request | False | [] |

##### Example

```YAML
---
can_networks:
  my_network:
    bitrate: 500000
    obd2_requests:
      - name: obd2_request1
        request_frame_id: 0x7DF
        response_frame_id: 0x7E8
        frequency: 20
        mode: 0x01
        parameters:
          - name: parameter1
            ....
```

#### OBD2 parameters definitions

| Key | Description | Required | Default value |
|-----|-------------|----------|---------------|
| `:name` | The parameter name, will be used in your own code to reference it | True |  |
| `:kind` | The type of value to be returned, one of: `"decimal"`, `"integer"` | False | `"decimal"` |
| `:precision` | The precision to which a decimal parameter should be rounded to | False | 2 |
| `:sign` | Wheter the parameter should be interpreted as a signed or unsigned integer | False | `"unsigned"` |
| `:value_length` | The number of bits to use for this parameter | True | |
| `:endianness` | The endianness to be used to interpret the parameter | False | `"little"` |
| `:unit` | An informational unit related to the parameter's value | False | |
| `:scale` | A decimal scale to be applied on the raw value, defined as a string in YAML | False | "1" |
| `:offset` | A decimal offset to be applied on the raw value, defined as a string in YAML  | False | "0" |

##### Example

```YAML
---
can_networks:
  my_network:
    bitrate: 500000
    obd2_requests:
      - name: obd2_request1
        request_frame_id: 0x7DF
        response_frame_id: 0x7E8
        frequency: 20
        mode: 0x01
        parameters:
          - name: speed
            id: 0x0D
            value_length: 8
          - name: rotation_per_minute
            id: 0x0C
            value_length: 16
            scale: "0.25"
```

#### Utilities

In order to keep your YAML file maintainable, Cantastic allows you to split it in multiple files and to import them using the following syntax:

`import!:ovcs_mini/generic_controller/0x701_main_controller_alive.yml`

##### Example:

```YAML
#  my_vehicle.yml
---
can_networks:
  ovcs:
    bitrate: 500000
    emitted_frames:
      - import!:./frames/frame1.yml
      - ...
    received_frames:
      - import!:./frames/frame2.yml
      - ...
    obd2_requests:
      - import!:./obd2_requests/frame2.yml
      - ....
```

```YAML
#  frames/frame1.yml
TODO
```

## Real world example

Cantastic is used in the Open Vehicle Control System, you will find concrete usage example in this [repository](https://github.com/open-vehicle-control-system/ovcs)

More concretely:

* A YAML [configuration file](https://github.com/open-vehicle-control-system/ovcs/blob/main/vms/core/priv/can/vehicles/ovcs1.yml)
* An [emitter](https://github.com/open-vehicle-control-system/ovcs/blob/main/vms/core/lib/vms_core/components/nissan/leaf_aze0/inverter.ex#L41)
* A [receiver](https://github.com/open-vehicle-control-system/ovcs/blob/main/vms/core/lib/vms_core/components/nissan/leaf_aze0/inverter.ex#L55)
* A [frame reception handler](https://github.com/open-vehicle-control-system/ovcs/blob/main/vms/core/lib/vms_core/components/nissan/leaf_aze0/inverter.ex#L124)
* An [OBD2 Request](https://github.com/open-vehicle-control-system/ovcs/blob/main/vms/core/lib/vms_core/vehicles/obd2.ex#L29)