Setting Up A Distributed Simulation (Sensors And Actors)

Software

Getting

$ git clone https://github.com/jfasch/FH-ENDLESS.git

Virtual Environment

  • Create virtual environment, and activate it

    $ python -m venv ~/My-Environments/endless
    $ . ~/My-Environments/endless/bin/activate
    (endless) $
    
  • Install requirements

    (endless) $ cd FH-ENDLESS/              # repo root
    (endless) $ python -m pip install -r requirements.txt
    

CAN Bus

MCSP2515 Hat, And Raspberry Bootloader Config

  • In /boot/firmware/overlays/README, search for mention of mcp2515

  • Add those lines to /boot/firmware/config.txt

    dtparam=spi=on
    dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=2000000
    
  • Reboot

  • Check

    $ ip link show can0
    3: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN mode DEFAULT group default qlen 10
        link/can
    

CAN Interface Configuration

You have configured a CAN controller into your system, be it a Pi Hat or a USB gadget. To communicate, you set at least the bitrate, and up the interface.

  • Determine interface name (we’ll use can0 in the following)

    $ ip link show
    ... check for a CAN type interface ...
    
  • Set speed and buffer size

    # ip link set can0 type can bitrate 500000
    # ip link set can0 txqueuelen 1000
    
  • Up the interface

    # ip link set can0 up
    

All-Simulated CAN: vcan

# modprobe vcan
# ip link add dev mein-test-can type vcan
# ip link set mein-test-can up

Raspi/: The Raspberry Pi Application

Note

On a physical CAN bus, do not collocate the Pi application and the microcontroller mockups on the same machine.

Unless they are in loopback mode, CAN controllers do not see what they send. A receiving application will not see what the sender is sending.

Configuration File

A configuration file defines components and their interconnections. That network is subject to frequent change. Check if the configuration fits your situation; for example, the CAN_IFACE and MQTT_ADDR variables probably need tuning, as well as CAN IDs, probably.

# -*- python -*-

from endless.project_1.can_switch import CANSwitch
from endless.project_1.can_temp_sensor import CAN_TemperatureSensor
from endless.project_1.can_hum_temp_sensor import CAN_HumidityTemperatureSensor
from endless.project_1.hum_temp_control import HumidityTemperature2Control
from endless.project_1.mqtt_helper import transform_hum_temp_to_json, transform_temp_to_json
from endless.project_1.dbus_server import DBusServer

from endless.framework.can_reader import CANReader
from endless.framework.can_writer import CANWriter
from endless.framework.mqtt import MQTTClient, MQTT_PublishSampleTagToTopic
from endless.framework.sample_broadcaster import SampleBroadcaster
from endless.framework.sample_filter import SampleFilter
from endless.framework.sample_history import SampleHistory
from endless.framework.hysteresis import Hysteresis
from endless.framework.switch_stdout import StdoutSwitch
from endless.framework.sink_stdout import StdoutSink


# CAN_IFACE = 'can0'
CAN_IFACE = 'mein-test-can'

# MQTT_ADDR = '192.168.220.142'
MQTT_ADDR = '127.0.0.1'


COMPONENTS = []

can_reader = CANReader(CAN_IFACE)
humtemp_sensor_0x33 = CAN_HumidityTemperatureSensor(can_id=0x33, tag='CAN@0x33')
humtemp_sensor_0x34 = CAN_HumidityTemperatureSensor(can_id=0x34, tag='CAN@0x34')
temp_sensor_0x42 = CAN_TemperatureSensor(can_id=0x42, tag='CAN@0x42')
COMPONENTS.extend([can_reader, humtemp_sensor_0x33, humtemp_sensor_0x34, temp_sensor_0x42])

can_reader.frame_out.connect(humtemp_sensor_0x33.can_in)
can_reader.frame_out.connect(humtemp_sensor_0x34.can_in)

humtemp_sensor_0x33.sample_out.connect(history_0x33.sample_in)
humtemp_sensor_0x34.sample_out.connect(history_0x34.sample_in)

broadcaster = SampleBroadcaster()
COMPONENTS.append(broadcaster)

history_0x33.sample_out.connect(broadcaster.sample_in)
history_0x34.sample_out.connect(broadcaster.sample_in)

# broadcast_debug = StdoutSink(prefix='AFTER BROADCAST')
# COMPONENTS.append(broadcast_debug)
# broadcaster.sample_out.connect(broadcast_debug.sample_in)

can33_filter = SampleFilter(lambda sample: sample if sample.tag == 'CAN@0x33' else None)
COMPONENTS.append(can33_filter)
broadcaster.sample_out.connect(can33_filter.sample_in)

humtemp2ctl = HumidityTemperature2Control()
COMPONENTS.append(humtemp2ctl)
can33_filter.sample_out.connect(humtemp2ctl.sample_in)

hysteresis = Hysteresis(20, 40)
COMPONENTS.append(hysteresis)
humtemp2ctl.control.connect(hysteresis.control)

# fixme: SwitchBroadcaster (send switch notifications over mqtt)

# switch = StdoutSwitch('Hi Switch:')
# COMPONENTS.append(switch)
# hysteresis.switch.connect(switch.switch)

switch = CANSwitch(can_id=0x40, number=3)
COMPONENTS.append(switch)
hysteresis.switch.connect(switch.switch)

can_writer = CANWriter(can_iface=CAN_IFACE)
COMPONENTS.append(can_writer)
switch.frame_out.connect(can_writer.frame_in)

humtemp2json = SampleFilter(transform_hum_temp_to_json)
COMPONENTS.append(humtemp2json)
broadcaster.sample_out.connect(humtemp2json.sample_in)

temp2json = SampleFilter(transform_temp_to_json)
COMPONENTS.append(temp2json)
temp_sensor_0x42.sample_out.connect(temp2json.sample_in)

publish_by_tag = MQTT_PublishSampleTagToTopic({
    'CAN@0x33': 'can-0x33',
    'CAN@0x34': 'can-0x34',
    'CAN@0x42': 'can-0x42',
})
COMPONENTS.append(publish_by_tag)
humtemp2json.sample_out.connect(publish_by_tag.sample_in)
temp2json.sample_out.connect(publish_by_tag.sample_in)

mqtt = MQTTClient(host=MQTT_ADDR)
COMPONENTS.append(mqtt)
publish_by_tag.publisher.connect(mqtt.publisher)

dbus_server = DBusServer(busname='org.project_1.Things')
COMPONENTS.append(dbus_server)
dbus_server.switch_counter.connect(switch.counter)
dbus_server.hysteresis_config.connect(hysteresis.config)
dbus_server.measurements_controllerA.connect(history_0x33.sample_list)
dbus_server.measurements_controllerB.connect(history_0x34.sample_list)

Run Application

$ cd <FH-ENDLESS>/Raspi                   # subdir Raspi/ of repository root
$ export PYTHONPATH=$(pwd)/src:$PYTHONPATH
$ bin/run-components.py --help
... read ...
$ bin/run-components.py --configfile conf/egon.conf

MC-HumTempSensor/: Sensors On CAN IDs 0x33 and 0x34

In lack of a real sensor microcontroller (Egon has one), we start a Python program. It simulates the measurements, and outputs on CAN two configurable sine curves instead.

$ cd <FH-ENDLESS>/MC-HumTempSensor/       # subdir MC-HumTempSensor/ of repository root
$ ./can-hum-temp-sensor.py --help
... read ...

Note that the CAN interface, and the controller’s CAN ID must match that of the config file. Take also into respect that the hysteresis (see config file) has as its input the temperature measurements from 0x33.

Sensor On 0x33

$ ./can-hum-temp-sensor.py --can-interface can0 --can-id 0x33 \
       --interval 1500 \
       --amplitude-temperature 15 \
       --hz-temperature 0.2 \
       --phase-shift-temperature '0.3*pi' \
       --vertical-shift-temperature 30 \
       --amplitude-humidity 5 \
       --hz-humidity 0.5 \
       --phase-shift-humidity pi/2 \
       --vertical-shift-humidity 80

Optional: Sensor On 0x34

No sensor outage detection is implemented, so it is safe to not start a second sensor. However, we use to at least publish values from 0x34 on a dedicated MQTT topic. If you want to see samples there, start a second instance of the sensor. For example,

$ ./can-hum-temp-sensor.py --can-interface can0 --can-id 0x34 \
       --interval 10 \
       --amplitude-temperature 1500 \
       --hz-temperature 100 \
       --phase-shift-temperature 0 \
       --vertical-shift-temperature 30000 \
       --amplitude-humidity 5 \
       --hz-humidity 0.5 \
       --phase-shift-humidity pi/2 \
       --vertical-shift-humidity 80

Note that a 10 millisecond sample interval could be a bit too optimistic - it is a performance test for the application and connected components like MQTT and databases.

MC-Switches/: Actor On CAN ID 0x40

$ cd <FH-ENDLESS>/MC-Switches/            # subdir MC-Switches/ of repository root
$ ./can-switches.py --help
... read ...
$ ./can-switches.py --can-interface can0 --can-id 0x40
16 switches available:
  0: OFF
  1: OFF
  2: OFF
  3: OFF
  4: OFF
  5: OFF
  6: OFF
  7: OFF
  8: OFF
  9: OFF
  10: OFF
  11: OFF
  12: OFF
  13: OFF
  14: OFF
  15: OFF
... prints switch actions as they occur ...