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 ofmcp2515Add those lines to
/boot/firmware/config.txtdtparam=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
can0in 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 ...