├── RailuinoApp
├── res
│ ├── drawable-hdpi
│ │ ├── up.png
│ │ ├── back.png
│ │ ├── down.png
│ │ ├── help.png
│ │ ├── loco.png
│ │ ├── mic_on.png
│ │ ├── v200.png
│ │ ├── forward.png
│ │ ├── mic_off.png
│ │ ├── mic_on_2.png
│ │ ├── power_on.png
│ │ ├── settings.png
│ │ ├── ic_launcher.png
│ │ ├── mic_off_2.png
│ │ ├── power_off.png
│ │ ├── power_off_2.png
│ │ ├── power_on_2.png
│ │ ├── turn_round.png
│ │ ├── turn_straight.png
│ │ ├── ic_action_search.png
│ │ ├── bluetooth_connected.png
│ │ ├── bluetooth_searching.png
│ │ └── bluetooth_disconnected.png
│ ├── values
│ │ ├── styles.xml
│ │ ├── dimens.xml
│ │ └── strings.xml
│ ├── drawable-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_action_search.png
│ ├── values-v11
│ │ └── styles.xml
│ ├── values-v14
│ │ └── styles.xml
│ ├── values-large
│ │ └── dimens.xml
│ ├── layout
│ │ ├── spinner.xml
│ │ ├── loco_new.xml
│ │ ├── turn_new.xml
│ │ ├── loco.xml
│ │ ├── test2.xml
│ │ ├── turn.xml
│ │ ├── test.xml
│ │ ├── activity_main.xml
│ │ └── main.xml
│ ├── menu
│ │ └── activity_main.xml
│ └── values-de
│ │ └── strings.xml
├── libs
│ └── android-support-v4.jar
├── src
│ └── de
│ │ └── pleumann
│ │ └── railuinoapp
│ │ ├── Configuration.java
│ │ ├── Accessory.java
│ │ ├── PageIndicator.java
│ │ ├── Locomotive.java
│ │ └── Railuino.java
├── .classpath
├── project.properties
├── proguard-project.txt
├── .project
└── AndroidManifest.xml
└── Railuino
└── src
├── can
├── defaults.h
├── global.h
├── mcp2515.h
├── mcp2515_defs.h
└── mcp2515.c
├── examples
├── 01.Controller
│ ├── Power
│ │ └── Power.ino
│ ├── Turnout
│ │ └── Turnout.ino
│ ├── Headlight
│ │ └── Headlight.ino
│ ├── CV
│ │ └── CV.ino
│ ├── Function
│ │ └── Function.ino
│ ├── Direction
│ │ └── Direction.ino
│ └── Speed
│ │ └── Speed.ino
├── 05.Misc
│ ├── Sniffer
│ │ └── Sniffer.ino
│ ├── Address
│ │ └── Address.ino
│ ├── Joystick
│ │ └── Joystick.ino
│ ├── Console
│ │ └── Console.ino
│ └── Tests
│ │ └── Tests.ino
├── 02.Reporter
│ ├── Monitor
│ │ └── Monitor.ino
│ └── PingPong
│ │ └── PingPong.ino
├── 04.Gateway
│ ├── LowLevel
│ │ └── LowLevel.ino
│ ├── Serial
│ │ └── Serial.ino
│ └── Bluetooth
│ │ └── Bluetooth.ino
└── 03.Automation
│ ├── Commuter
│ └── Commuter.ino
│ └── CommuterWithTurnout
│ └── CommuterWithTurnout.ino
├── README
├── ir
├── infrared2.c
└── infrared.c
├── Railuino.h
└── Railuino.cpp
/RailuinoApp/res/drawable-hdpi/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/up.png
--------------------------------------------------------------------------------
/RailuinoApp/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/RailuinoApp/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/back.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/down.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/help.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/loco.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/loco.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/mic_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/mic_on.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/v200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/v200.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/forward.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/forward.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/mic_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/mic_off.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/mic_on_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/mic_on_2.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/power_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/power_on.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/settings.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/mic_off_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/mic_off_2.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/power_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/power_off.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/power_off_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/power_off_2.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/power_on_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/power_on_2.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/turn_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/turn_round.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/RailuinoApp/res/values-v11/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/turn_straight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/turn_straight.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/ic_action_search.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-mdpi/ic_action_search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-mdpi/ic_action_search.png
--------------------------------------------------------------------------------
/RailuinoApp/res/values-v14/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/bluetooth_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/bluetooth_connected.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/bluetooth_searching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/bluetooth_searching.png
--------------------------------------------------------------------------------
/RailuinoApp/res/drawable-hdpi/bluetooth_disconnected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MBuratto/railuino/HEAD/RailuinoApp/res/drawable-hdpi/bluetooth_disconnected.png
--------------------------------------------------------------------------------
/RailuinoApp/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 8dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/RailuinoApp/res/values-large/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 16dp
5 | 16dp
6 |
7 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/RailuinoApp/src/de/pleumann/railuinoapp/Configuration.java:
--------------------------------------------------------------------------------
1 | package de.pleumann.railuinoapp;
2 |
3 | import java.io.IOException;
4 |
5 | public class Configuration {
6 |
7 | public void save() throws IOException {
8 |
9 | }
10 |
11 | public void restore() throws IOException {
12 |
13 | }
14 |
15 | // public LocomotiveAdapter getLocomotives() {
16 | //
17 | // }
18 | //
19 | // public AccessoryAdapter getAccessories() {
20 | //
21 | // }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/RailuinoApp/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/RailuinoApp/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=Google Inc.:Google APIs:17
15 |
--------------------------------------------------------------------------------
/RailuinoApp/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/RailuinoApp/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | RailuinoApp
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Railuino/src/can/defaults.h:
--------------------------------------------------------------------------------
1 | #ifndef DEFAULTS_H
2 | #define DEFAULTS_H
3 |
4 | #if defined(__UNO__)
5 |
6 | #define P_MOSI B,3
7 | #define P_MISO B,4
8 | #define P_SCK B,5
9 |
10 | //#define MCP2515_CS D,3 // Rev A
11 | #define MCP2515_CS B,2 // was 2 Rev B
12 | #define MCP2515_INT D,2 // was 2
13 | #define LED2_HIGH B,0
14 | #define LED2_LOW B,0
15 |
16 | #define CAN_INT 0
17 |
18 | #elif defined(__LEONARDO__)
19 |
20 | #define P_MOSI B,2
21 | #define P_MISO B,3
22 | #define P_SCK B,1
23 |
24 | #define MCP2515_CS B,6 // was 2 Rev B
25 | #define MCP2515_INT D,1 // was 2
26 | #define LED2_HIGH B,0
27 | #define LED2_LOW B,0
28 |
29 | #define CAN_INT 1
30 |
31 | #elif defined(__MEGA__)
32 |
33 | #define P_MOSI B,2
34 | #define P_MISO B,3
35 | #define P_SCK B,1
36 |
37 | //#define MCP2515_CS D,3 // Rev A
38 | #define MCP2515_CS B,4 // was 2 Rev B
39 | #define MCP2515_INT E,4 // was 2
40 | #define LED2_HIGH B,0
41 | #define LED2_LOW B,0
42 |
43 | #define CAN_INT 0
44 |
45 | #endif
46 |
47 | #endif // DEFAULTS_H
48 |
--------------------------------------------------------------------------------
/RailuinoApp/res/menu/activity_main.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/loco_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/turn_new.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Railuino/src/can/global.h:
--------------------------------------------------------------------------------
1 | #ifndef GLOBAL_H
2 | #define GLOBAL_H
3 |
4 | // ----------------------------------------------------------------------------
5 |
6 | //#define true 1
7 | //#define false 0
8 |
9 | #define True 1
10 | #define False 0
11 |
12 | //typedef _Bool bool;
13 | //typedef boolean Bool;
14 |
15 | // ----------------------------------------------------------------------------
16 |
17 | #define RESET(x) _XRS(x)
18 | #define SET(x) _XS(x)
19 | #define TOGGLE(x) _XT(x)
20 | #define SET_OUTPUT(x) _XSO(x)
21 | #define SET_INPUT(x) _XSI(x)
22 | #define IS_SET(x) _XR(x)
23 |
24 | #define PORT(x) _port2(x)
25 | #define DDR(x) _ddr2(x)
26 | #define PIN(x) _pin2(x)
27 |
28 | #define _XRS(x,y) PORT(x) &= ~(1<
5 |
6 |
9 |
10 |
11 |
12 |
13 |
17 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Power/Power.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word TIME = 2000;
20 | const boolean DEBUG = true;
21 |
22 | TrackController ctrl(0xdf24, DEBUG);
23 |
24 | void setup() {
25 | Serial.begin(115200);
26 | while (!Serial);
27 |
28 | ctrl.begin();
29 | }
30 |
31 | void loop() {
32 | Serial.println("Power on");
33 | ctrl.setPower(true);
34 |
35 | delay(TIME);
36 |
37 | Serial.println("Power off");
38 | ctrl.setPower(false);
39 |
40 | delay(TIME);
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/Railuino/src/examples/05.Misc/Sniffer/Sniffer.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const boolean DEBUG = true;
20 |
21 | TrackController ctrl(0xdf24, DEBUG);
22 |
23 | TrackMessage message;
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | Serial.println();
30 | Serial.println();
31 | Serial.println("DIR CMND R HASH HASH LNGT DAT0 DAT1 DAT2 DAT3 DAT4 DAT5 DAT6 DAT7");
32 | Serial.println("--- ---- - ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----");
33 | ctrl.begin();
34 | }
35 |
36 | void loop() {
37 | ctrl.receiveMessage(message);
38 | delay(20);
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Railuino/src/examples/02.Reporter/Monitor/Monitor.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word TIME = 1000;
20 |
21 | TrackReporterIOX rprt(1); // Replace IOX by S88 when using S88.
22 |
23 | void setup() {
24 | Serial.begin(115200);
25 | while (!Serial);
26 |
27 | Serial.println("01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16");
28 | Serial.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --");
29 | }
30 |
31 | void loop() {
32 | rprt.refresh();
33 |
34 | for (int i = 1; i <= 16;i++) {
35 | Serial.print(rprt.getValue(i) ? "XX " : " ");
36 | }
37 | Serial.println();
38 |
39 | delay(TIME);
40 | }
41 |
42 |
--------------------------------------------------------------------------------
/Railuino/src/examples/02.Reporter/PingPong/PingPong.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word PING = 1;
20 | const word PONG = 2;
21 |
22 | TrackReporterIOX rprt(1); // Replace IOX by S88 when using S88.
23 |
24 | void setup() {
25 | Serial.begin(115200);
26 | while (!Serial);
27 | }
28 |
29 | void waitForContact(int i) {
30 | rprt.refresh();
31 |
32 | while (!rprt.getValue(i)) {
33 | rprt.refresh();
34 | }
35 |
36 | while (rprt.getValue(i)) {
37 | rprt.refresh();
38 | }
39 | }
40 |
41 | void loop() {
42 | waitForContact(PING);
43 | Serial.println("Ping");
44 |
45 | waitForContact(PONG);
46 | Serial.println("Pong");
47 | }
48 |
49 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Turnout/Turnout.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word TURN = ADDR_ACC_MM2 + 1;
20 | const word TIME = 5000;
21 | const boolean DEBUG = true;
22 |
23 | TrackController ctrl(0xdf24, DEBUG);
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | ctrl.begin();
30 | Serial.println("Power on");
31 | ctrl.setPower(true);
32 | }
33 |
34 |
35 | void loop() {
36 | Serial.println("Set turnout straight");
37 | ctrl.setTurnout(TURN, true);
38 |
39 | delay(TIME);
40 |
41 | Serial.println("Set turnout round");
42 | ctrl.setTurnout(TURN, false);
43 |
44 | delay(TIME);
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/loco.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
25 |
26 |
35 |
36 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/test2.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
20 |
21 |
22 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/Railuino/src/examples/04.Gateway/LowLevel/LowLevel.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 | #include
19 |
20 | TrackController ctrl(0xdf24, false);
21 |
22 | SoftwareSerial blue(10, 11);
23 |
24 | TrackMessage message;
25 |
26 | char buffer[32];
27 |
28 | int length;
29 |
30 | void setup() {
31 | ctrl.begin();
32 | }
33 |
34 | void loop() {
35 | if (ctrl.receiveMessage(message)) {
36 | blue.println(message);
37 | }
38 |
39 | if (blue.available()) {
40 | char c = blue.read();
41 |
42 | if (c == 10 || length == sizeof(buffer)) {
43 | String s = String(buffer);
44 | length = 0;
45 | if (message.parseFrom(s)) {
46 | ctrl.sendMessage(message);
47 | }
48 | } else if (c >= 32) {
49 | buffer[length++] = c;
50 | }
51 | }
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/RailuinoApp/src/de/pleumann/railuinoapp/Accessory.java:
--------------------------------------------------------------------------------
1 | package de.pleumann.railuinoapp;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | public class Accessory {
6 |
7 | public static final int TYPE_NEW = -1;
8 |
9 | public static final int TYPE_MM2 = 0;
10 |
11 | public static final int TYPE_DCC = 1;
12 |
13 | private String name = "";
14 |
15 | private int type = 0;
16 |
17 | private int address = 0;
18 |
19 | private int states;
20 |
21 | static String untitled;
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public void setName(String name) {
28 | this.name = name;
29 | }
30 |
31 | public int getType() {
32 | return type;
33 | }
34 |
35 | public void setType(int type) {
36 | this.type = type;
37 | }
38 |
39 | public int getAddress() {
40 | return address;
41 | }
42 |
43 | public void setAddress(int address) {
44 | this.address = address;
45 | }
46 |
47 | public boolean getState(int index) {
48 | int bit = 1 << index;
49 | return (states & bit) != 0;
50 | }
51 |
52 | public void setState(int index, boolean value) {
53 | int bit = 1 << index;
54 | states = value ? states | bit : states & ~bit;
55 | }
56 |
57 | public int getFullAddress() {
58 | return type == 0 ? 0x2fff + address : 0x3800 + address;
59 | }
60 |
61 | public String toString() {
62 | return name.equals("") ? untitled : name;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Headlight/Headlight.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word TIME = 2000;
21 | const boolean DEBUG = true;
22 |
23 | TrackController ctrl(0xdf24, DEBUG);
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | ctrl.begin();
30 | Serial.println("Power on");
31 | ctrl.setPower(true);
32 | }
33 |
34 | void loop() {
35 | byte b;
36 |
37 | Serial.println("Lights on");
38 | ctrl.setLocoFunction(LOCO, 0, 1);
39 | if (ctrl.getLocoFunction(LOCO, 0, &b)) {
40 | Serial.print("(Lights are ");
41 | Serial.println(b ? "on)" : "off)");
42 | }
43 |
44 | delay(TIME);
45 |
46 | Serial.println("Lights off");
47 | ctrl.setLocoFunction(LOCO, 0, 0);
48 | if (ctrl.getLocoFunction(LOCO, 0, &b)) {
49 | Serial.print("(Lights are ");
50 | Serial.println(b ? "on)" : "off)");
51 | }
52 |
53 | delay(TIME);
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/CV/CV.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_DCC + 21; // Reading CVs requires DCC to work
20 | const boolean DEBUG = true;
21 |
22 | TrackController ctrl(0xdf24, DEBUG);
23 |
24 | void setup() {
25 | Serial.begin(115200);
26 | while (!Serial);
27 |
28 | ctrl.begin();
29 | Serial.println("Power on");
30 | ctrl.setPower(true);
31 | }
32 |
33 | void showRegister(int i, String label) {
34 | byte b;
35 |
36 | if (ctrl.readConfig(LOCO, i, &b)) {
37 | Serial.print("Register ");
38 | Serial.print(i, DEC);
39 | Serial.print(" - ");
40 | Serial.print(label);
41 | Serial.print(": ");
42 | Serial.println(b, DEC);
43 | }
44 | }
45 |
46 |
47 | void loop() {
48 | showRegister(1, "Address");
49 | showRegister(2, "Min. Voltage");
50 | showRegister(3, "Accel. time");
51 | showRegister(4, "Decel. time");
52 | showRegister(5, "Max. speed");
53 | showRegister(6, "Avg. speed");
54 | showRegister(7, "Version");
55 | showRegister(8, "Manufacturer");
56 |
57 | for (;;);
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Function/Function.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word TIME = 2000;
21 | const boolean DEBUG = true;
22 |
23 | TrackController ctrl(0xdf24, DEBUG);
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | ctrl.begin();
30 | Serial.println("Power on");
31 | ctrl.setPower(true);
32 | }
33 |
34 | void loop() {
35 | byte b;
36 |
37 | for (int i = 0; i <= 4; i++) {
38 | Serial.print("Function ");
39 | Serial.print(i, DEC);
40 |
41 | ctrl.setLocoFunction(LOCO, i, 1);
42 | if (ctrl.getLocoFunction(LOCO, i, &b)) {
43 | Serial.print("(Function ");
44 | Serial.print(i, DEC);
45 | Serial.println(b ? " is on)" : " is off)");
46 | }
47 |
48 | delay(TIME);
49 |
50 | ctrl.setLocoFunction(LOCO, i, 0);
51 | if (ctrl.getLocoFunction(LOCO, i, &b)) {
52 | Serial.print("(Function ");
53 | Serial.print(i, DEC);
54 | Serial.println(b ? " is on)" : " is off)");
55 | }
56 |
57 | delay(TIME);
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Direction/Direction.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word TIME = 2000;
21 | const boolean DEBUG = true;
22 |
23 | TrackController ctrl(0xdf24, DEBUG);
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | ctrl.begin();
30 | Serial.println("Power on");
31 | ctrl.setPower(true);
32 | Serial.println("Headlights on");
33 | ctrl.setLocoFunction(LOCO, 0, 1);
34 | }
35 |
36 | void loop() {
37 | byte b;
38 |
39 | Serial.println("Direction forward");
40 | ctrl.setLocoDirection(LOCO, DIR_FORWARD);
41 | if (ctrl.getLocoDirection(LOCO, &b)) {
42 | Serial.print("(Direction is ");
43 | Serial.println(b == DIR_FORWARD ? "forward)" : "reverse)");
44 | }
45 |
46 | delay(TIME);
47 |
48 | Serial.println("Direction reverse");
49 | ctrl.setLocoDirection(LOCO, DIR_REVERSE);
50 | if (ctrl.getLocoDirection(LOCO, &b)) {
51 | Serial.print("(Direction is ");
52 | Serial.println(b == DIR_FORWARD ? "forward)" : "reverse)");
53 | }
54 |
55 | delay(TIME);
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/RailuinoApp/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Railuino
4 | Hello world!
5 | Settings
6 | Railuino
7 |
8 | MM2 protocol
9 | DCC protocol
10 | MFX protocol
11 |
12 | Label
13 | Address
14 |
15 | (New locomotive)
16 | (New switchboard)
17 |
18 | Edit locomotive
19 | Edit switchboard
20 |
21 | Delete locomotive
22 | Are you sure? This cannot be undone.
23 |
24 | Delete accessory
25 | Are you sure? This cannot be undone.
26 |
27 | Untitled
28 |
29 | direction
30 | forward
31 | backward
32 |
33 | stop
34 | slower
35 | faster
36 | low speed
37 | medium speed
38 | high speed
39 | full speed
40 |
41 | lights
42 | function
43 | turnout
44 |
45 |
--------------------------------------------------------------------------------
/Railuino/src/examples/01.Controller/Speed/Speed.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word SPEED = 100;
21 | const word TIME = 5000;
22 | const boolean DEBUG = true;
23 |
24 | TrackController ctrl(0xdf24, DEBUG);
25 |
26 | void setup() {
27 | Serial.begin(115200);
28 | while (!Serial);
29 |
30 | ctrl.begin();
31 |
32 | Serial.println("Power on");
33 | ctrl.setPower(true);
34 | Serial.println("Headlights on");
35 | ctrl.setLocoFunction(LOCO, 0, 1);
36 | Serial.println("Direction forward");
37 | ctrl.setLocoDirection(LOCO, DIR_FORWARD);
38 | }
39 |
40 | void loop() {
41 | word speed;
42 |
43 | Serial.print("Setting speed to ");
44 | Serial.println(SPEED);
45 | ctrl.setLocoSpeed(LOCO, SPEED);
46 | if (ctrl.getLocoSpeed(LOCO, &speed)) {
47 | Serial.print("(Speed is ");
48 | Serial.print(speed, DEC);
49 | Serial.println(")");
50 | }
51 |
52 | delay(TIME);
53 |
54 | Serial.println("Stop");
55 | ctrl.setLocoSpeed(LOCO, 0);
56 | if (ctrl.getLocoSpeed(LOCO, &speed)) {
57 | Serial.print("(Speed is ");
58 | Serial.print(speed, DEC);
59 | Serial.println(")");
60 | }
61 |
62 | Serial.println("System stopped. Need to reset.");
63 |
64 | for (;;);
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/Railuino/src/examples/03.Automation/Commuter/Commuter.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word SPEED = 100;
21 | const word TIME = 2000;
22 | const boolean DEBUG = true;
23 |
24 | TrackController ctrl(0xdf24, DEBUG);
25 |
26 | TrackReporterIOX rprt(1); // Replace IOX by S88 when using S88.
27 |
28 | word track = 1;
29 |
30 | void setup() {
31 | Serial.begin(115200);
32 | while (!Serial);
33 |
34 | ctrl.begin();
35 | ctrl.setPower(true);
36 | ctrl.setLocoFunction(LOCO, 0, 1);
37 | ctrl.setLocoSpeed(LOCO, 0);
38 | }
39 |
40 | void waitForContact(word index) {
41 | Serial.print("Waiting for contact ");
42 | Serial.println(index);
43 |
44 | rprt.refresh();
45 | while (!rprt.getValue(index)) {
46 | rprt.refresh();
47 | }
48 |
49 | Serial.println("Ok");
50 | }
51 |
52 | void drivePastContact(int dir, int contact) {
53 | Serial.println("V200 go");
54 | ctrl.setLocoDirection(LOCO, dir);
55 | ctrl.setLocoSpeed(LOCO, SPEED);
56 | waitForContact(contact);
57 |
58 | Serial.println("V200 stop");
59 | ctrl.setLocoSpeed(LOCO, 0);
60 | }
61 |
62 | void loop() {
63 | drivePastContact(DIR_FORWARD, 1);
64 |
65 | delay(TIME);
66 |
67 | drivePastContact(DIR_REVERSE, 2);
68 |
69 | delay(TIME);
70 | }
71 |
72 |
--------------------------------------------------------------------------------
/RailuinoApp/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Railuino
4 | Hello world!
5 | Settings
6 | Railuino
7 |
8 | MM2-Protokoll
9 | DCC-Protokoll
10 | MFX-Protokoll
11 |
12 | Bezeichnung
13 | Adresse
14 |
15 | (Neue Lokomotive)
16 | (Neues Schaltpult)
17 |
18 | Lokomotive bearbeiten
19 | Schaltpult bearbeiten
20 |
21 | Lokomotive löschen
22 | Sind Sie sicher? Dies kann nicht rückgängig gemacht werden.
23 |
24 | Schaltpult löschen
25 | Sind Sie sicher? Dies kann nicht rückgängig gemacht werden.
26 |
27 | Unbenannt
28 |
29 | Richtung
30 | Vorwärts
31 | Rückwärts
32 |
33 | Anhalten
34 | Schneller
35 | Langsamer
36 | Langsame Fahrt
37 | Halbe Fahrt
38 | Schnelle Fahrt
39 | Volle Fahrt
40 |
41 | Licht
42 | Funktion
43 | Weiche
44 |
45 |
--------------------------------------------------------------------------------
/Railuino/src/examples/05.Misc/Address/Address.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const boolean DEBUG = false;
20 |
21 | TrackController ctrl(0xdf24, DEBUG);
22 |
23 | TrackMessage message;
24 |
25 | void setup() {
26 | Serial.begin(115200);
27 | while (!Serial);
28 |
29 | Serial.println();
30 | Serial.println();
31 | Serial.println("Control locomotives using your MS2 to detect their address...");
32 | Serial.println();
33 | ctrl.begin();
34 | }
35 |
36 | void loop() {
37 | if (ctrl.receiveMessage(message)) {
38 | if (message.response && message.command >= 0x05 && message.command <= 0x07) {
39 | word full = makeWord(message.data[2], message.data[3]);
40 |
41 | String type = "unknown";
42 | word addr = 0;
43 |
44 | if (full >= ADDR_DCC) {
45 | type = "DCC";
46 | addr = full - ADDR_DCC;
47 | } else if (full >= ADDR_SX2) {
48 | type = "SX2";
49 | addr = full - ADDR_SX2;
50 | } else if (full >= ADDR_MFX) {
51 | type = "MFX";
52 | addr = full - ADDR_MFX;
53 | } else if (full >= ADDR_SX1) {
54 | type = "SX1";
55 | addr = full - ADDR_SX1;
56 | } else /* must be MM2 */ {
57 | type = "MM2";
58 | addr = full - ADDR_MM2;
59 | }
60 |
61 | Serial.println("Type is " + type + ", address is " + addr);
62 | }
63 | }
64 |
65 | delay(20);
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Railuino/src/examples/03.Automation/CommuterWithTurnout/CommuterWithTurnout.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word TURN = ADDR_ACC_MM2 + 1;
21 | const word SPEED = 100;
22 | const word TIME = 2000;
23 | const boolean DEBUG = true;
24 |
25 | TrackController ctrl(0xdf24, DEBUG);
26 |
27 | TrackReporterIOX rprt(1); // Replace IOX by S88 when using S88.
28 |
29 | word track = 1;
30 |
31 | void setup() {
32 | Serial.begin(115200);
33 | while (!Serial);
34 |
35 | ctrl.begin();
36 | ctrl.setPower(true);
37 | ctrl.setLocoFunction(LOCO, 0, 1);
38 | ctrl.setLocoSpeed(LOCO, 0);
39 | }
40 |
41 | void waitForContact(word index) {
42 | Serial.print("Waiting for contact ");
43 | Serial.println(index);
44 |
45 | rprt.refresh();
46 | while (!rprt.getValue(index)) {
47 | rprt.refresh();
48 | }
49 |
50 | Serial.println("Ok");
51 | }
52 |
53 | void drivePastContact(int dir, int contact) {
54 | Serial.println("V200 go");
55 | ctrl.setLocoDirection(LOCO, dir);
56 | ctrl.setLocoSpeed(LOCO, SPEED);
57 | waitForContact(contact);
58 |
59 | Serial.println("V200 stop");
60 | ctrl.setLocoSpeed(LOCO, 0);
61 | }
62 |
63 | void loop() {
64 | drivePastContact(DIR_FORWARD, 3);
65 |
66 | delay(TIME);
67 |
68 | Serial.print("Setting turnout for track " + track);
69 | ctrl.setAccessory(TURN, track == 1 ? ACC_RED : ACC_GREEN, 1, 50);
70 | track = 3 - track;
71 |
72 | delay(TIME);
73 |
74 | drivePastContact(DIR_REVERSE, track);
75 |
76 | delay(TIME);
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/RailuinoApp/src/de/pleumann/railuinoapp/PageIndicator.java:
--------------------------------------------------------------------------------
1 | package de.pleumann.railuinoapp;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.support.v4.view.ViewPager;
8 | import android.support.v4.view.ViewPager.OnPageChangeListener;
9 | import android.util.AttributeSet;
10 | import android.view.View;
11 |
12 | public class PageIndicator extends View implements OnPageChangeListener {
13 |
14 | private int count;
15 |
16 | private int index;
17 |
18 | private float step;
19 |
20 | private ViewPager pager;
21 |
22 | public PageIndicator(Context context, AttributeSet attrs) {
23 | super(context, attrs);
24 | }
25 |
26 | public PageIndicator(Context context) {
27 | super(context);
28 | }
29 |
30 | public void setPager(ViewPager pager) {
31 | this.pager = pager;
32 | onPageSelected(pager.getCurrentItem());
33 | }
34 |
35 | @Override
36 | public void onPageScrolled(int arg0, float arg1, int arg2) {
37 | }
38 |
39 | @Override
40 | public void onPageScrollStateChanged(int arg0) {
41 | }
42 |
43 | @Override
44 | public void onPageSelected(int arg0) {
45 | count = pager.getAdapter().getCount();
46 | index = arg0;
47 |
48 | step = (float)(getWidth() - getPaddingLeft() - getPaddingRight()) / (float)(count);
49 |
50 | invalidate();
51 | }
52 |
53 | @Override
54 | protected void onDraw(Canvas canvas) {
55 | super.onDraw(canvas);
56 |
57 | count = pager.getAdapter().getCount();
58 | index = pager.getCurrentItem();
59 |
60 | step = (float)(getWidth() - getPaddingLeft() - getPaddingRight()) / (float)(count);
61 |
62 | Paint paint = new Paint();
63 |
64 | paint.setColor(Color.LTGRAY);
65 | canvas.drawRect(getPaddingLeft(), getPaddingBottom(), getWidth() - getPaddingRight(), getHeight() - getPaddingTop(), paint);
66 | paint.setColor(Color.CYAN);
67 | canvas.drawRect(getPaddingLeft() + index * step, getPaddingBottom(), getPaddingLeft() + (index + 1) * step, getHeight() - getPaddingTop(), paint);
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/RailuinoApp/src/de/pleumann/railuinoapp/Locomotive.java:
--------------------------------------------------------------------------------
1 | package de.pleumann.railuinoapp;
2 |
3 | import java.io.File;
4 |
5 | public class Locomotive {
6 |
7 | public static final int TYPE_NEW = -1;
8 |
9 | public static final int TYPE_MM2 = 0;
10 |
11 | public static final int TYPE_DCC = 1;
12 |
13 | public static final int TYPE_MFX = 2;
14 |
15 | private String name = "";
16 |
17 | private int type = 0;
18 |
19 | private int address = 0;
20 |
21 | private File photo;
22 |
23 | private int direction;
24 |
25 | private int speed;
26 |
27 | private int functions;
28 |
29 | static String untitled;
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public void setName(String name) {
36 | this.name = name;
37 | }
38 |
39 | public int getType() {
40 | return type;
41 | }
42 |
43 | public void setType(int type) {
44 | this.type = type;
45 | }
46 |
47 | public int getAddress() {
48 | return address;
49 | }
50 |
51 | public void setAddress(int address) {
52 | this.address = address;
53 | }
54 |
55 | public File getPhoto() {
56 | return photo;
57 | }
58 |
59 | public void setPhoto(File photo) {
60 | this.photo = photo;
61 | }
62 |
63 | public int getDirection() {
64 | return direction;
65 | }
66 |
67 | public void setDirection(int direction) {
68 | this.direction = direction;
69 | }
70 |
71 | public int getSpeed() {
72 | return speed;
73 | }
74 |
75 | public void setSpeed(int speed) {
76 | this.speed = speed;
77 | }
78 |
79 | public boolean getFunction(int index) {
80 | int bit = 1 << index;
81 | return (functions & bit) != 0;
82 | }
83 |
84 | public void setFunction(int index, boolean value) {
85 | int bit = 1 << index;
86 | functions = value ? functions | bit : functions & ~bit;
87 | }
88 |
89 | public int getFullAddress() {
90 | return type == 0 ? address : type == 1 ? 0xc000 + address : 0x4000 + address;
91 | }
92 |
93 | public String toString() {
94 | return name.equals("") ? untitled : name;
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/Railuino/src/examples/05.Misc/Joystick/Joystick.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | const word LOCO = ADDR_MM2 + 78;
20 | const word TURN = ADDR_ACC_MM2 + 1;
21 | const boolean DEBUG = true;
22 |
23 | TrackController ctrl(0xdf24, DEBUG);
24 |
25 | const int UP = A1;
26 | const int DOWN = A3;
27 | const int LEFT = A5;
28 | const int RIGHT = A2;
29 | const int FIRE = A4;
30 |
31 | void setup() {
32 | Serial.begin(115200);
33 | while (!Serial);
34 |
35 | ctrl.begin();
36 |
37 | digitalWrite(A1, HIGH);
38 | digitalWrite(A2, HIGH);
39 | digitalWrite(A3, HIGH);
40 | digitalWrite(A4, HIGH);
41 | digitalWrite(A5, HIGH);
42 |
43 | ctrl.setPower(true);
44 | ctrl.setLocoDirection(LOCO, DIR_FORWARD);
45 | ctrl.setLocoFunction(LOCO, 0, 1);
46 | }
47 |
48 | int getJoystick() {
49 | if (digitalRead(UP) == 0) {
50 | return UP;
51 | } else if (digitalRead(RIGHT) == 0) {
52 | return RIGHT;
53 | } else if (digitalRead(DOWN) == 0) {
54 | return DOWN;
55 | } else if (digitalRead(FIRE) == 0) {
56 | return FIRE;
57 | } else if (digitalRead(LEFT) == 0) {
58 | return LEFT;
59 | } else {
60 | return 0;
61 | }
62 | }
63 |
64 | int getJoystickWait() {
65 | int result = 0;
66 |
67 | while (result == 0) {
68 | result = getJoystick();
69 | }
70 |
71 | while (getJoystick() != 0) {
72 | };
73 |
74 | return result;
75 | }
76 |
77 | void loop() {
78 | int event = getJoystickWait();
79 |
80 | switch (event) {
81 | case UP: {
82 | ctrl.setTurnout(TURN, false);
83 | break;
84 | }
85 | case DOWN: {
86 | ctrl.setTurnout(TURN, true);
87 | break;
88 | }
89 | case RIGHT: {
90 | ctrl.accelerateLoco(LOCO);
91 | break;
92 | }
93 | case LEFT: {
94 | ctrl.decelerateLoco(LOCO);
95 | break;
96 | }
97 | case FIRE: {
98 | ctrl.setLocoDirection(LOCO, DIR_CHANGE);
99 | break;
100 | }
101 | }
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/Railuino/src/README:
--------------------------------------------------------------------------------
1 | Railuino - Hacking your Maerklin
2 |
3 | This library allows you to control your digital Maerklin railway
4 | using Arduino. It can be used in two flavors:
5 |
6 | (1) As a Mobile Station 2. You can use a Sparkfun CAN shield and a
7 | cable to connect your Arduino to the Digital Connector Box of
8 | a Mobile Station 2 and probably (untested) also to a Central
9 | Station 2. This allows you to control locomotives, functions
10 | and turnouts using MM2, DCC and other protocols. You can even
11 | read and write decoder CVs, as long as the decoder's protocol
12 | supports it (DCC does both, MM2 only write, others vary).
13 |
14 | (2) As an infrared controller. You can use a simple infrared LED
15 | and a resistor to have your Arduino talk to the very basic IR
16 | receiver box that comes with various starter packages. This
17 | allows you to control four MM2 locomotives on fixed addresses
18 | (the classic Delta addresses). You can also control sixteen
19 | turnouts (this is an undocumented feature of the IR box, and
20 | the original Marklin IR controller doesn't support it).
21 |
22 | In addition to controlling things on and connected to the track
23 | the library allows you to receive reports about track usage using
24 | the standard S88 bus.
25 |
26 | Installation is easy: Just get the latest release from the
27 | downloads page and place the contents of the "src" directory in a
28 | "Railuino" directory under your Arduino "libraries" directory.
29 | Then restart Arduino. You should now see a bunch of new examples
30 | that teach you how to use Railuino. The "Misc/Tests" example is
31 | a good way of validating your setup.
32 |
33 | For documentation on the functions I currently recommend to read
34 | the comments in the "Railuino.h" header file. There are also
35 | several sets of slides on the downloads page that describe the
36 | overall approach and the hardware. Finally, there is a video from
37 | LinuxTag and another one from DroidCon NL on YouTube.
38 |
39 | The library itself is made available under the GNU Lesser General
40 | Public License (LGPL). See the LICENSE file for details. All
41 | examples except the test suite are licensed under the Creative
42 | Commons Zero license, which effectively makes them public domain.
43 | The test suite is also licensed under the LGPL.
44 |
45 | Railuino uses parts of two other libraries, both of which are
46 | very much recommended:
47 |
48 | 1) The CAN controller code is based on Fabian Greif's CANlib,
49 | which is (c) 2007-2012 Fabian Greif and licensed under a
50 | BSD-style license.
51 |
52 | 2) The IR sender code is taken from Ken Shirriff's IR library,
53 | which is (c) 2009-2012 Ken Shirriff and licensed under the
54 | GNU Lesser General Public Library.
55 |
56 | Have fun!
57 |
--------------------------------------------------------------------------------
/Railuino/src/examples/05.Misc/Console/Console.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | word ADDRESS[] = { DELTA1, DELTA2, DELTA3, DELTA4 };
20 |
21 | word loco = ADDRESS[0];
22 |
23 | TrackController ctrl(0xdf24, true);
24 |
25 | int input(String message, int low, int high) {
26 | while(true) {
27 | int result = 0;
28 |
29 | Serial.println();
30 | Serial.print(message);
31 | Serial.print(" (");
32 | Serial.print(low, DEC);
33 | Serial.print(" - ");
34 | Serial.print(high, DEC);
35 | Serial.println(")");
36 |
37 | while (true) {
38 | if (Serial.available() > 0) {
39 | int c = Serial.read();
40 |
41 | if ((c == 10 || c == 13) && result > 0) {
42 | if (result >= low && result <= high) {
43 | Serial.println();
44 | Serial.print(">> ");
45 | Serial.println(result, DEC);
46 | return result;
47 | } else {
48 | break;
49 | }
50 | } else if (c >= '0' && c <= '9') {
51 | result = result * 10 + (c - '0');
52 | } else {
53 | result = 0;
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | void setup() {
61 | Serial.begin(115200);
62 | while (!Serial);
63 |
64 | ctrl.begin();
65 |
66 | for (int i = 0; i < 20; i++) {
67 | Serial.println();
68 | }
69 |
70 | Serial.println("Railuino serial console");
71 | }
72 |
73 | void loop() {
74 | Serial.println();
75 | Serial.println("1 ... Choose loco");
76 | Serial.println("2 ... Accelerate loco");
77 | Serial.println("3 ... Decelerate loco");
78 | Serial.println("4 ... Toggle direction");
79 | Serial.println("5 ... Toggle headlights");
80 | Serial.println("6 ... Switch turnout");
81 | Serial.println("7 ... Set power on");
82 | Serial.println("8 ... Set power off");
83 |
84 | int cmd = input("Choose command", 1, 8);
85 |
86 | if (cmd == 1) {
87 | loco = ADDRESS[input("Choose loco", 1, 4) - 1];
88 | } else if (cmd == 2) {
89 | ctrl.accelerateLoco(loco);
90 | } else if (cmd == 3) {
91 | ctrl.decelerateLoco(loco);
92 | } else if (cmd == 4) {
93 | ctrl.toggleLocoDirection(loco);
94 | } else if (cmd == 5) {
95 | ctrl.toggleLocoFunction(loco, 0);
96 | } else if (cmd == 6) {
97 | int turnout = input("Choose turnout", 1, 16);
98 | int value = input("Choose direction [1=straight, 2=round]", 1, 2);
99 | ctrl.setTurnout(ADDR_ACC_MM2 + turnout, value == 1);
100 | } else if (cmd == 7) {
101 | ctrl.setPower(true);
102 | } else if (cmd == 8) {
103 | ctrl.setPower(false);
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/Railuino/src/ir/infrared2.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // ===================================================================
4 | // === Low-level IR stuff ============================================
5 | // ===================================================================
6 |
7 | /**
8 | * This is taken more or less verbatim from Ken Shirriff's awesome IR
9 | * library.
10 | *
11 | * http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
12 | *
13 | * I stripped it down to what is needed here and made it work with
14 | * RC5 extended code. I decided against using his full code because
15 | * of Arduino's inability to deal with libraries that use other
16 | * libraries. I decided in favor of "repackaging" so people can use
17 | * his full library in parallel with Railuino.
18 | */
19 |
20 | #define SYSCLOCK 16000000 // main Arduino clock
21 | #define RC5_T1 889
22 | #define TOPBIT 0x80000000
23 |
24 | void mark(int time) {
25 | // Sends an IR mark for the specified number of microseconds.
26 | // The mark output is modulated at the PWM frequency.
27 | TCCR3A |= _BV(COM3A1); // Enable pin 3 PWM output
28 | delayMicroseconds(time);
29 | }
30 |
31 | /* Leave pin off for time (given in microseconds) */
32 | void space(int time) {
33 | // Sends an IR space for the specified number of microseconds.
34 | // A space is no output, so the PWM output is disabled.
35 | TCCR3A &= ~(_BV(COM3A1)); // Disable pin 3 PWM output
36 | delayMicroseconds(time);
37 | }
38 |
39 | void enableIROut(int khz) {
40 | // Enables IR output. The khz value controls the modulation frequency in kilohertz.
41 | // The IR output will be on pin 3 (OC2B).
42 | // This routine is designed for 36-40KHz; if you use it for other values, it's up to you
43 | // to make sure it gives reasonable results. (Watch out for overflow / underflow / rounding.)
44 | // TIMER2 is used in phase-correct PWM mode, with OCR2A controlling the frequency and OCR2B
45 | // controlling the duty cycle.
46 | // There is no prescaling, so the output frequency is 16MHz / (2 * OCR2A)
47 | // To turn the output on and off, we leave the PWM running, but connect and disconnect the output pin.
48 | // A few hours staring at the ATmega documentation and this will all make sense.
49 | // See my Secrets of Arduino PWM at http://arcfn.com/2009/07/secrets-of-arduino-pwm.html for details.
50 |
51 |
52 | // Disable the Timer2 Interrupt (which is used for receiving IR)
53 | TIMSK3 = _BV(OCIE3A); //Timer2 Overflow Interrupt
54 |
55 | pinMode(3, OUTPUT);
56 | digitalWrite(3, LOW); // When not sending PWM, we want it low
57 |
58 | // COM2A = 00: disconnect OC2A
59 | // COM2B = 00: disconnect OC2B; to send signal set to 10: OC2B non-inverted
60 | // WGM2 = 101: phase-correct PWM with OCRA as top
61 | // CS2 = 000: no prescaling
62 | TCCR3A = _BV(WGM31);
63 | TCCR3B = _BV(WGM33) | _BV(CS30);
64 |
65 | // The top value for the timer. The modulation frequency will be SYSCLOCK / 2 / OCR2A.
66 | ICR3 = SYSCLOCK / 2 / khz/ 1000;
67 | OCR3A = ICR3 / 3;
68 | }
69 |
70 | // Note: first bit must be a one (start bit)
71 | void sendRC5(unsigned long data, int nbits, bool extended)
72 | {
73 | enableIROut(36);
74 | data = data << (32 - nbits);
75 | mark(RC5_T1); // First start bit
76 | if (extended) {
77 | mark(RC5_T1); // Second start bit
78 | space(RC5_T1); // Second start bit
79 | } else {
80 | space(RC5_T1); // Second start bit
81 | mark(RC5_T1); // Second start bit
82 | }
83 | for (int i = 0; i < nbits; i++) {
84 | if (data & TOPBIT) {
85 | space(RC5_T1); // 1 is space, then mark
86 | mark(RC5_T1);
87 | }
88 | else {
89 | mark(RC5_T1);
90 | space(RC5_T1);
91 | }
92 | data <<= 1;
93 | }
94 | space(0); // Turn off at end
95 | }
96 |
--------------------------------------------------------------------------------
/Railuino/src/ir/infrared.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // ===================================================================
4 | // === Low-level IR stuff ============================================
5 | // ===================================================================
6 |
7 | /**
8 | * This is taken more or less verbatim from Ken Shirriff's awesome IR
9 | * library.
10 | *
11 | * http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html
12 | *
13 | * I stripped it down to what is needed here and made it work with
14 | * RC5 extended code. I decided against using his full code because
15 | * of Arduino's inability to deal with libraries that use other
16 | * libraries. I decided in favor of "repackaging" so people can use
17 | * his full library in parallel with Railuino.
18 | */
19 |
20 | #define SYSCLOCK 16000000 // main Arduino clock
21 | #define RC5_T1 889
22 | #define TOPBIT 0x80000000
23 |
24 | void mark(int time) {
25 | // Sends an IR mark for the specified number of microseconds.
26 | // The mark output is modulated at the PWM frequency.
27 | TCCR2A |= _BV(COM2B1); // Enable pin 3 PWM output
28 | delayMicroseconds(time);
29 | }
30 |
31 | /* Leave pin off for time (given in microseconds) */
32 | void space(int time) {
33 | // Sends an IR space for the specified number of microseconds.
34 | // A space is no output, so the PWM output is disabled.
35 | TCCR2A &= ~(_BV(COM2B1)); // Disable pin 3 PWM output
36 | delayMicroseconds(time);
37 | }
38 |
39 | void enableIROut(int khz) {
40 | // Enables IR output. The khz value controls the modulation frequency in kilohertz.
41 | // The IR output will be on pin 3 (OC2B).
42 | // This routine is designed for 36-40KHz; if you use it for other values, it's up to you
43 | // to make sure it gives reasonable results. (Watch out for overflow / underflow / rounding.)
44 | // TIMER2 is used in phase-correct PWM mode, with OCR2A controlling the frequency and OCR2B
45 | // controlling the duty cycle.
46 | // There is no prescaling, so the output frequency is 16MHz / (2 * OCR2A)
47 | // To turn the output on and off, we leave the PWM running, but connect and disconnect the output pin.
48 | // A few hours staring at the ATmega documentation and this will all make sense.
49 | // See my Secrets of Arduino PWM at http://arcfn.com/2009/07/secrets-of-arduino-pwm.html for details.
50 |
51 |
52 | // Disable the Timer2 Interrupt (which is used for receiving IR)
53 | TIMSK2 &= ~_BV(TOIE2); //Timer2 Overflow Interrupt
54 |
55 | pinMode(3, OUTPUT);
56 | digitalWrite(3, LOW); // When not sending PWM, we want it low
57 |
58 | // COM2A = 00: disconnect OC2A
59 | // COM2B = 00: disconnect OC2B; to send signal set to 10: OC2B non-inverted
60 | // WGM2 = 101: phase-correct PWM with OCRA as top
61 | // CS2 = 000: no prescaling
62 | TCCR2A = _BV(WGM20);
63 | TCCR2B = _BV(WGM22) | _BV(CS20);
64 |
65 | // The top value for the timer. The modulation frequency will be SYSCLOCK / 2 / OCR2A.
66 | OCR2A = SYSCLOCK / 2 / khz / 1000;
67 | OCR2B = OCR2A / 3; // 33% duty cycle
68 | }
69 |
70 | // Note: first bit must be a one (start bit)
71 | void sendRC5(unsigned long data, int nbits, bool extended)
72 | {
73 | enableIROut(36);
74 | data = data << (32 - nbits);
75 | mark(RC5_T1); // First start bit
76 | if (extended) {
77 | mark(RC5_T1); // Second start bit
78 | space(RC5_T1); // Second start bit
79 | } else {
80 | space(RC5_T1); // Second start bit
81 | mark(RC5_T1); // Second start bit
82 | }
83 | for (int i = 0; i < nbits; i++) {
84 | if (data & TOPBIT) {
85 | space(RC5_T1); // 1 is space, then mark
86 | mark(RC5_T1);
87 | }
88 | else {
89 | mark(RC5_T1);
90 | space(RC5_T1);
91 | }
92 | data <<= 1;
93 | }
94 | space(0); // Turn off at end
95 | }
96 |
--------------------------------------------------------------------------------
/Railuino/src/can/mcp2515.h:
--------------------------------------------------------------------------------
1 | #ifndef MCP2515_H
2 | #define MCP2515_H
3 |
4 | // ----------------------------------------------------------------------------
5 | /* Copyright (c) 2007 Fabian Greif
6 | * All rights reserved.
7 | *
8 | * Redistribution and use in source and binary forms, with or without
9 | * modification, are permitted provided that the following conditions
10 | * are met:
11 | *
12 | * 1. Redistributions of source code must retain the above copyright
13 | * notice, this list of conditions and the following disclaimer.
14 | * 2. Redistributions in binary form must reproduce the above copyright
15 | * notice, this list of conditions and the following disclaimer in the
16 | * documentation and/or other materials provided with the distribution.
17 | *
18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 | * SUCH DAMAGE.
29 | */
30 | // ----------------------------------------------------------------------------
31 |
32 | #include
33 |
34 | #include "mcp2515_defs.h"
35 | #include "global.h"
36 | #ifdef __cplusplus
37 |
38 | extern "C"
39 | {
40 |
41 |
42 | #endif
43 | // ----------------------------------------------------------------------------
44 | typedef struct
45 | {
46 | uint32_t id; //!< ID der Nachricht (11 oder 29 Bit)
47 | struct {
48 | int rtr : 1; //!< Remote-Transmit-Request-Frame?
49 | int extended : 1; //!< extended ID?
50 | } flags;
51 | uint8_t length; //!< Anzahl der Datenbytes
52 | uint8_t data[8]; //!< Die Daten der CAN Nachricht
53 | } tCAN;
54 |
55 | typedef tCAN can_t;
56 |
57 | // ----------------------------------------------------------------------------
58 | uint8_t spi_putc( uint8_t data );
59 |
60 | // ----------------------------------------------------------------------------
61 | void can_write_register( uint8_t adress, uint8_t data );
62 |
63 | // ----------------------------------------------------------------------------
64 | uint8_t can_read_register(uint8_t adress);
65 |
66 | // ----------------------------------------------------------------------------
67 | void can_bit_modify(uint8_t adress, uint8_t mask, uint8_t data);
68 |
69 | // ----------------------------------------------------------------------------
70 | uint8_t can_read_status(uint8_t type);
71 |
72 | // ----------------------------------------------------------------------------
73 |
74 | uint8_t can_init(uint8_t speed, bool loopback);
75 |
76 | // ----------------------------------------------------------------------------
77 | // check if there are any new messages waiting
78 | uint8_t can_check_message(void);
79 |
80 | // ----------------------------------------------------------------------------
81 | // check if there is a free buffer to send messages
82 | uint8_t can_check_free_buffer(void);
83 |
84 | // ----------------------------------------------------------------------------
85 | uint8_t can_get_message(tCAN *message);
86 |
87 | // ----------------------------------------------------------------------------
88 | uint8_t can_send_message(tCAN *message);
89 |
90 |
91 | #ifdef __cplusplus
92 | }
93 | #endif
94 |
95 | #endif // MCP2515_H
96 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/turn.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
20 |
21 |
26 |
27 |
28 |
33 |
34 |
39 |
40 |
45 |
46 |
51 |
52 |
57 |
58 |
63 |
64 |
65 |
69 |
70 |
75 |
76 |
81 |
82 |
87 |
88 |
93 |
94 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Railuino/src/examples/04.Gateway/Serial/Serial.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | TrackController ctrl(0xdf24, true);
20 |
21 | String request;
22 |
23 | String function;
24 |
25 | word arguments[8];
26 |
27 | word numOfArguments;
28 |
29 | boolean result;
30 |
31 | void setup() {
32 | Serial.begin(115200);
33 | while (!Serial);
34 |
35 | ctrl.begin();
36 | Serial.println("100 Ready");
37 | }
38 |
39 | void receiveRequest() {
40 | char buffer[256];
41 | int length = 0;
42 | boolean newline = false;
43 |
44 | while (!newline && length < sizeof(buffer)) {
45 | if (Serial.available() > 0) {
46 | int c = Serial.read();
47 |
48 | if (c == 10) {
49 | buffer[length++] = 0;
50 | newline = true;
51 | } else if (c == 13) {
52 | // Ignore
53 | } else {
54 | buffer[length++] = c;
55 | }
56 | }
57 | }
58 |
59 | request = String(buffer);
60 | }
61 |
62 | word stringToWord(String s) {
63 | word result = 0;
64 |
65 | for (int i = 0; i < s.length(); i++) {
66 | result = 10 * result + (s.charAt(i) - '0');
67 | }
68 |
69 | return result;
70 | }
71 |
72 | boolean parse() {
73 | int lpar = request.indexOf('(');
74 | if (lpar == -1) {
75 | return false;
76 | }
77 |
78 | function = String(request.substring(0, lpar));
79 | function.trim();
80 |
81 | int offset = lpar + 1;
82 | int comma = request.indexOf(',', offset);
83 | numOfArguments = 0;
84 | while (comma != -1) {
85 | String tmp = request.substring(offset, comma);
86 | tmp.trim();
87 | arguments[numOfArguments++] = stringToWord(tmp);
88 | offset = comma + 1;
89 | comma = request.indexOf(',', offset);
90 | }
91 |
92 | int rpar = request.indexOf(')', offset);
93 | while (rpar == -1) {
94 | return false;
95 | }
96 |
97 | if (rpar > offset) {
98 | String tmp = request.substring(offset, rpar);
99 | tmp.trim();
100 | arguments[numOfArguments++] = stringToWord(tmp);
101 | }
102 |
103 | return true;
104 | }
105 |
106 | boolean dispatch() {
107 | if (function == "accelerateLoco") {
108 | return ctrl.accelerateLoco(arguments[0]);
109 | } else if (function == "decelerateLoco") {
110 | return ctrl.decelerateLoco(arguments[0]);
111 | } else if (function == "toggleLocoDirection") {
112 | return ctrl.toggleLocoDirection(arguments[0]);
113 | } else if (function == "setLocoDirection") {
114 | return ctrl.setLocoDirection(arguments[0], arguments[1]);
115 | } else if (function == "toggleLocoFunction") {
116 | return ctrl.toggleLocoFunction(arguments[0], arguments[1]);
117 | } else if (function == "setTurnout") {
118 | return ctrl.setTurnout(arguments[0], arguments[1]);
119 | } else if (function == "setPower") {
120 | return ctrl.setPower(arguments[0]);
121 | } else if (function == "setLocoSpeed") {
122 | return ctrl.setLocoSpeed(arguments[0], arguments[1]);
123 | } else {
124 | return false;
125 | }
126 |
127 | }
128 |
129 | void loop() {
130 | receiveRequest();
131 | if (parse()) {
132 | if (dispatch()) {
133 | Serial.println("200 Ok");
134 | } else {
135 | Serial.println("300 Command error");
136 | }
137 | } else {
138 | Serial.println("301 Syntax error");
139 | }
140 | }
141 |
142 |
--------------------------------------------------------------------------------
/Railuino/src/examples/04.Gateway/Bluetooth/Bluetooth.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This example is free software; you can redistribute it and/or
7 | * modify it under the terms of the Creative Commons Zero License,
8 | * version 1.0, as published by the Creative Commons Organisation.
9 | * This effectively puts the file into the public domain.
10 | *
11 | * This example is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 | #include
19 |
20 | TrackController ctrl(0xdf24, true);
21 |
22 | SoftwareSerial blue(4, 5);
23 |
24 | String request;
25 |
26 | String function;
27 |
28 | word arguments[8];
29 |
30 | word numOfArguments;
31 |
32 | boolean result;
33 |
34 | void setup() {
35 | Serial.begin(115200);
36 | while (!Serial);
37 |
38 | ctrl.begin();
39 | blue.begin(9600);
40 | // Serial.println("100 Ready");
41 | }
42 |
43 | void receiveRequest() {
44 | char buffer[256];
45 | int length = 0;
46 | boolean newline = false;
47 |
48 | while (!newline && length < sizeof(buffer)) {
49 | if (blue.available() > 0) {
50 | int c = blue.read();
51 |
52 | if (c == 10) {
53 | buffer[length++] = 0;
54 | newline = true;
55 | } else if (c == 13) {
56 | // Ignore
57 | } else {
58 | buffer[length++] = c;
59 | }
60 | }
61 | }
62 |
63 | request = String(buffer);
64 |
65 | Serial.println(request);
66 | }
67 |
68 | word stringToWord(String s) {
69 | word result = 0;
70 |
71 | for (int i = 0; i < s.length(); i++) {
72 | result = 10 * result + (s.charAt(i) - '0');
73 | }
74 |
75 | return result;
76 | }
77 |
78 | boolean parse() {
79 | int lpar = request.indexOf('(');
80 | if (lpar == -1) {
81 | return false;
82 | }
83 |
84 | function = String(request.substring(0, lpar));
85 | function.trim();
86 |
87 | int offset = lpar + 1;
88 | int comma = request.indexOf(',', offset);
89 | numOfArguments = 0;
90 | while (comma != -1) {
91 | String tmp = request.substring(offset, comma);
92 | tmp.trim();
93 | arguments[numOfArguments++] = stringToWord(tmp);
94 | offset = comma + 1;
95 | comma = request.indexOf(',', offset);
96 | }
97 |
98 | int rpar = request.indexOf(')', offset);
99 | while (rpar == -1) {
100 | return false;
101 | }
102 |
103 | if (rpar > offset) {
104 | String tmp = request.substring(offset, rpar);
105 | tmp.trim();
106 | arguments[numOfArguments++] = stringToWord(tmp);
107 | }
108 |
109 | return true;
110 | }
111 |
112 | boolean dispatch() {
113 | if (function == "accelerateLoco") {
114 | return ctrl.accelerateLoco(arguments[0]);
115 | } else if (function == "decelerateLoco") {
116 | return ctrl.decelerateLoco(arguments[0]);
117 | } else if (function == "toggleLocoDirection") {
118 | return ctrl.toggleLocoDirection(arguments[0]);
119 | } else if (function == "setLocoDirection") {
120 | return ctrl.setLocoDirection(arguments[0], arguments[1]);
121 | } else if (function == "toggleLocoFunction") {
122 | return ctrl.toggleLocoFunction(arguments[0], arguments[1]);
123 | } else if (function == "setLocoFunction") {
124 | return ctrl.setLocoFunction(arguments[0], arguments[1], arguments[2]);
125 | } else if (function == "setTurnout") {
126 | return ctrl.setTurnout(arguments[0], arguments[1]);
127 | } else if (function == "setPower") {
128 | return ctrl.setPower(arguments[0]);
129 | } else if (function == "setLocoSpeed") {
130 | return ctrl.setLocoSpeed(arguments[0], arguments[1]);
131 | } else {
132 | return false;
133 | }
134 | }
135 |
136 | void loop() {
137 | receiveRequest();
138 | if (parse()) {
139 | if (dispatch()) {
140 | blue.println("200 Ok");
141 | } else {
142 | blue.println("300 Command error");
143 | }
144 | } else {
145 | blue.println("301 Syntax error");
146 | }
147 | }
148 |
149 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
20 |
21 |
28 |
29 |
36 |
37 |
44 |
45 |
52 |
53 |
60 |
61 |
68 |
69 |
76 |
77 |
84 |
85 |
92 |
93 |
100 |
101 |
108 |
109 |
116 |
117 |
124 |
125 |
132 |
133 |
140 |
141 |
147 |
148 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/RailuinoApp/src/de/pleumann/railuinoapp/Railuino.java:
--------------------------------------------------------------------------------
1 | package de.pleumann.railuinoapp;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.lang.reflect.Method;
7 | import java.util.UUID;
8 |
9 | import android.bluetooth.BluetoothDevice;
10 | import android.bluetooth.BluetoothSocket;
11 | import android.util.Log;
12 |
13 | public class Railuino {
14 |
15 | private BluetoothDevice device;
16 |
17 | private BluetoothSocket socket;
18 |
19 | private InputStream input;
20 |
21 | private OutputStream output;
22 |
23 | private int messageID;
24 |
25 | public Railuino(BluetoothDevice device) throws IOException {
26 | this.device = device;
27 |
28 | socket = getSocket(device);
29 | socket.connect();
30 |
31 | input = socket.getInputStream();
32 | output = socket.getOutputStream();
33 |
34 | Log.d("XXX", "Connected to " + device.getName());
35 | }
36 |
37 | /**
38 | * Sends the given message, assigning a new message ID on the fly.
39 | */
40 | public void send(String s) throws IOException {
41 | output.write(s.getBytes());
42 | output.write(13);
43 | output.write(10);
44 |
45 | Log.d("XXX", "Sent: " + s);
46 | }
47 |
48 | /**
49 | * Receives the next message and returns it.
50 | */
51 | public String receive() throws IOException {
52 | StringBuilder builder = new StringBuilder(256);
53 | int i = input.read();
54 | while (i != 13) {
55 | if (i >= ' ') {
56 | builder.append((char)i);
57 | }
58 | i = input.read();
59 | }
60 |
61 | Log.d("XXX", "Received: " + builder.toString());
62 |
63 | return builder.toString();
64 | }
65 |
66 | /**
67 | * Sends the given message, assigning a new message ID on the fly, and
68 | * receives and returns the response message. This also handles the
69 | * necessary ACK messages in both directions. Synchronous. No error handling
70 | * is being done. The messages are not checked for correctness.
71 | */
72 | public boolean sendAndReceive(final String s) {
73 | new Thread(new Runnable() {
74 | @Override
75 | public void run() {
76 | try {
77 | send(s);
78 | String response = receive();
79 |
80 | // return response.startsWith("OK");
81 | } catch (IOException e) {
82 | e.printStackTrace();
83 | // return false;
84 | }
85 | }
86 | }).start();
87 |
88 | return true;
89 | }
90 |
91 | /**
92 | * Closes the connection to the DiOBD880, ignoring all problems that might
93 | * occur. Should be called when the object is not needed anymore.
94 | */
95 | public void close() {
96 | try {
97 | if (input != null) {
98 | input.close();
99 | input = null;
100 | }
101 | } catch (Exception ignored) {
102 | }
103 |
104 | try {
105 | if (output != null) {
106 | output.close();
107 | output = null;
108 | }
109 | } catch (Exception ignored) {
110 | }
111 |
112 | try {
113 | if (socket != null) {
114 | socket.close();
115 | socket = null;
116 | }
117 | } catch (Exception ignored) {
118 | }
119 |
120 | if (device != null) {
121 | device = null;
122 | }
123 |
124 | Log.d("XXX", "Connection closed");
125 | }
126 |
127 | /**
128 | * Creates an RFCOMM Bluetooth socket. It we are paired with the box, we can
129 | * use a secure socket. Otherwise we have to fallback to an insecure one. We
130 | * use reflection to access the method, because it is at least
131 | * hidden/undocumented in older releases. It might even be missing
132 | * completely.
133 | */
134 | private BluetoothSocket getSocket(BluetoothDevice device) {
135 | try {
136 | if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
137 | return device.createInsecureRfcommSocketToServiceRecord(UUID
138 | .fromString("00001101-0000-1000-8000-00805F9B34FB"));
139 | } else {
140 | // This also works on Android 2.2, where insecure RFCOMM is
141 | // undocumented. Normally, starting from Android 2.3, we would
142 | // simply do this:
143 | //
144 | // device.createInsecureRfcommSocketToServiceRecord(UUID
145 | // .fromString("00001101-0000-1000-8000-00805F9B34FB"));
146 |
147 | Method method = BluetoothDevice.class.getMethod(
148 | "createInsecureRfcommSocket", int.class);
149 | return (BluetoothSocket) method.invoke(device, 1);
150 | }
151 | } catch (Exception e) {
152 | Log.e("XXX", "Error getting socket", e);
153 | return null;
154 | }
155 | }
156 |
157 | /**
158 | * Returns the readable name of the Bluetooth device we are connected to.
159 | */
160 | public String getName() {
161 | return device.getName();
162 | }
163 |
164 | public boolean setPower(boolean value) {
165 | return sendAndReceive("setPower(" + (value ? 1 : 0) + ")");
166 | }
167 |
168 | public boolean setLocoDirection(int address, int dir) {
169 | return sendAndReceive("setLocoDirection(" + address + ", " + ( 1 + dir) + ")");
170 | }
171 |
172 | public boolean setLocoSpeed(int address, int speed) {
173 | return sendAndReceive("setLocoSpeed(" + address + ", " + speed + ")");
174 | }
175 |
176 | public boolean setLocoFunction(int address, int index, boolean value) {
177 | return sendAndReceive("setLocoFunction(" + address + ", " + index + ", " + (value ? 1 : 0) + ")");
178 | }
179 |
180 | public boolean setTurnout(int address, boolean value) {
181 | return sendAndReceive("setTurnout(" + address + ", " + (value ? 1 : 0) + ")");
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
14 |
15 |
21 |
22 |
26 |
27 |
30 |
31 |
35 |
36 |
41 |
42 |
47 |
48 |
53 |
54 |
59 |
60 |
61 |
62 |
66 |
67 |
72 |
73 |
78 |
79 |
84 |
85 |
90 |
91 |
92 |
93 |
97 |
98 |
103 |
104 |
109 |
110 |
115 |
116 |
121 |
122 |
123 |
124 |
128 |
129 |
134 |
135 |
140 |
141 |
146 |
147 |
152 |
153 |
154 |
155 |
156 |
160 |
161 |
162 |
163 |
--------------------------------------------------------------------------------
/Railuino/src/can/mcp2515_defs.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef MCP2515_DEFS_H
3 | #define MCP2515_DEFS_H
4 |
5 | /** \name SPI Kommandos */
6 | /*@{*/
7 | #define SPI_RESET 0xC0
8 | #define SPI_READ 0x03
9 | #define SPI_READ_RX 0x90
10 | #define SPI_WRITE 0x02
11 | #define SPI_WRITE_TX 0x40
12 | #define SPI_RTS 0x80
13 | #define SPI_READ_STATUS 0xA0
14 | #define SPI_RX_STATUS 0xB0
15 | #define SPI_BIT_MODIFY 0x05
16 | /*@}*/
17 |
18 | /** \name Adressen der Register des MCP2515
19 | *
20 | * Die Redundanten Adressen von z.B. dem Register CANSTAT
21 | * (0x0E, 0x1E, 0x2E, ...) wurden dabei nicht mit aufgelistet.
22 | */
23 | /*@{*/
24 | #define RXF0SIDH 0x00
25 | #define RXF0SIDL 0x01
26 | #define RXF0EID8 0x02
27 | #define RXF0EID0 0x03
28 | #define RXF1SIDH 0x04
29 | #define RXF1SIDL 0x05
30 | #define RXF1EID8 0x06
31 | #define RXF1EID0 0x07
32 | #define RXF2SIDH 0x08
33 | #define RXF2SIDL 0x09
34 | #define RXF2EID8 0x0A
35 | #define RXF2EID0 0x0B
36 | #define BFPCTRL 0x0C
37 | #define TXRTSCTRL 0x0D
38 | #define CANSTAT 0x0E
39 | #define CANCTRL 0x0F
40 |
41 | #define RXF3SIDH 0x10
42 | #define RXF3SIDL 0x11
43 | #define RXF3EID8 0x12
44 | #define RXF3EID0 0x13
45 | #define RXF4SIDH 0x14
46 | #define RXF4SIDL 0x15
47 | #define RXF4EID8 0x16
48 | #define RXF4EID0 0x17
49 | #define RXF5SIDH 0x18
50 | #define RXF5SIDL 0x19
51 | #define RXF5EID8 0x1A
52 | #define RXF5EID0 0x1B
53 | #define TEC 0x1C
54 | #define REC 0x1D
55 |
56 | #define RXM0SIDH 0x20
57 | #define RXM0SIDL 0x21
58 | #define RXM0EID8 0x22
59 | #define RXM0EID0 0x23
60 | #define RXM1SIDH 0x24
61 | #define RXM1SIDL 0x25
62 | #define RXM1EID8 0x26
63 | #define RXM1EID0 0x27
64 | #define CNF3 0x28
65 | #define CNF2 0x29
66 | #define CNF1 0x2A
67 | #define CANINTE 0x2B
68 | #define CANINTF 0x2C
69 | #define EFLG 0x2D
70 |
71 | #define TXB0CTRL 0x30
72 | #define TXB0SIDH 0x31
73 | #define TXB0SIDL 0x32
74 | #define TXB0EID8 0x33
75 | #define TXB0EID0 0x34
76 | #define TXB0DLC 0x35
77 | #define TXB0D0 0x36
78 | #define TXB0D1 0x37
79 | #define TXB0D2 0x38
80 | #define TXB0D3 0x39
81 | #define TXB0D4 0x3A
82 | #define TXB0D5 0x3B
83 | #define TXB0D6 0x3C
84 | #define TXB0D7 0x3D
85 |
86 | #define TXB1CTRL 0x40
87 | #define TXB1SIDH 0x41
88 | #define TXB1SIDL 0x42
89 | #define TXB1EID8 0x43
90 | #define TXB1EID0 0x44
91 | #define TXB1DLC 0x45
92 | #define TXB1D0 0x46
93 | #define TXB1D1 0x47
94 | #define TXB1D2 0x48
95 | #define TXB1D3 0x49
96 | #define TXB1D4 0x4A
97 | #define TXB1D5 0x4B
98 | #define TXB1D6 0x4C
99 | #define TXB1D7 0x4D
100 |
101 | #define TXB2CTRL 0x50
102 | #define TXB2SIDH 0x51
103 | #define TXB2SIDL 0x52
104 | #define TXB2EID8 0x53
105 | #define TXB2EID0 0x54
106 | #define TXB2DLC 0x55
107 | #define TXB2D0 0x56
108 | #define TXB2D1 0x57
109 | #define TXB2D2 0x58
110 | #define TXB2D3 0x59
111 | #define TXB2D4 0x5A
112 | #define TXB2D5 0x5B
113 | #define TXB2D6 0x5C
114 | #define TXB2D7 0x5D
115 |
116 | #define RXB0CTRL 0x60
117 | #define RXB0SIDH 0x61
118 | #define RXB0SIDL 0x62
119 | #define RXB0EID8 0x63
120 | #define RXB0EID0 0x64
121 | #define RXB0DLC 0x65
122 | #define RXB0D0 0x66
123 | #define RXB0D1 0x67
124 | #define RXB0D2 0x68
125 | #define RXB0D3 0x69
126 | #define RXB0D4 0x6A
127 | #define RXB0D5 0x6B
128 | #define RXB0D6 0x6C
129 | #define RXB0D7 0x6D
130 |
131 | #define RXB1CTRL 0x70
132 | #define RXB1SIDH 0x71
133 | #define RXB1SIDL 0x72
134 | #define RXB1EID8 0x73
135 | #define RXB1EID0 0x74
136 | #define RXB1DLC 0x75
137 | #define RXB1D0 0x76
138 | #define RXB1D1 0x77
139 | #define RXB1D2 0x78
140 | #define RXB1D3 0x79
141 | #define RXB1D4 0x7A
142 | #define RXB1D5 0x7B
143 | #define RXB1D6 0x7C
144 | #define RXB1D7 0x7D
145 | /*@}*/
146 |
147 | /** \name Bitdefinition der verschiedenen Register */
148 | /*@{*/
149 |
150 | /** \brief Bitdefinition von BFPCTRL */
151 | #define B1BFS 5
152 | #define B0BFS 4
153 | #define B1BFE 3
154 | #define B0BFE 2
155 | #define B1BFM 1
156 | #define B0BFM 0
157 |
158 | /** \brief Bitdefinition von TXRTSCTRL */
159 | #define B2RTS 5
160 | #define B1RTS 4
161 | #define B0RTS 3
162 | #define B2RTSM 2
163 | #define B1RTSM 1
164 | #define B0RTSM 0
165 |
166 | /** \brief Bitdefinition von CANSTAT */
167 | #define OPMOD2 7
168 | #define OPMOD1 6
169 | #define OPMOD0 5
170 | #define ICOD2 3
171 | #define ICOD1 2
172 | #define ICOD0 1
173 |
174 | /** \brief Bitdefinition von CANCTRL */
175 | #define REQOP2 7
176 | #define REQOP1 6
177 | #define REQOP0 5
178 | #define ABAT 4
179 | #define CLKEN 2
180 | #define CLKPRE1 1
181 | #define CLKPRE0 0
182 |
183 | /** \brief Bitdefinition von CNF3 */
184 | #define WAKFIL 6
185 | #define PHSEG22 2
186 | #define PHSEG21 1
187 | #define PHSEG20 0
188 |
189 | /** \brief Bitdefinition von CNF2 */
190 | #define BTLMODE 7
191 | #define SAM 6
192 | #define PHSEG12 5
193 | #define PHSEG11 4
194 | #define PHSEG10 3
195 | #define PHSEG2 2
196 | #define PHSEG1 1
197 | #define PHSEG0 0
198 |
199 | /** \brief Bitdefinition von CNF1 */
200 | #define SJW1 7
201 | #define SJW0 6
202 | #define BRP5 5
203 | #define BRP4 4
204 | #define BRP3 3
205 | #define BRP2 2
206 | #define BRP1 1
207 | #define BRP0 0
208 |
209 | /** \brief Bitdefinition von CANINTE */
210 | #define MERRE 7
211 | #define WAKIE 6
212 | #define ERRIE 5
213 | #define TX2IE 4
214 | #define TX1IE 3
215 | #define TX0IE 2
216 | #define RX1IE 1
217 | #define RX0IE 0
218 |
219 | /** \brief Bitdefinition von CANINTF */
220 | #define MERRF 7
221 | #define WAKIF 6
222 | #define ERRIF 5
223 | #define TX2IF 4
224 | #define TX1IF 3
225 | #define TX0IF 2
226 | #define RX1IF 1
227 | #define RX0IF 0
228 |
229 | /** \brief Bitdefinition von EFLG */
230 | #define RX1OVR 7
231 | #define RX0OVR 6
232 | #define TXB0 5
233 | #define TXEP 4
234 | #define RXEP 3
235 | #define TXWAR 2
236 | #define RXWAR 1
237 | #define EWARN 0
238 |
239 | /** \brief Bitdefinition von TXBnCTRL (n = 0, 1, 2) */
240 | #define ABTF 6
241 | #define MLOA 5
242 | #define TXERR 4
243 | #define TXREQ 3
244 | #define TXP1 1
245 | #define TXP0 0
246 |
247 | /** \brief Bitdefinition von RXB0CTRL */
248 | #define RXM1 6
249 | #define RXM0 5
250 | #define RXRTR 3
251 | #define BUKT 2
252 | #define BUKT1 1
253 | #define FILHIT0 0
254 |
255 | /** \brief Bitdefinition von TXBnSIDL (n = 0, 1) */
256 | #define EXIDE 3
257 |
258 | /**
259 | * \brief Bitdefinition von RXB1CTRL
260 | * \see RXM1, RXM0, RXRTR und FILHIT0 sind schon fuer RXB0CTRL definiert
261 | */
262 | #define FILHIT2 2
263 | #define FILHIT1 1
264 |
265 | /** \brief Bitdefinition von RXBnSIDL (n = 0, 1) */
266 | #define SRR 4
267 | #define IDE 3
268 |
269 | /**
270 | * \brief Bitdefinition von RXBnDLC (n = 0, 1)
271 | * \see TXBnDLC (gleiche Bits)
272 | */
273 | #define RTR 6
274 | #define DLC3 3
275 | #define DLC2 2
276 | #define DLC1 1
277 | #define DLC0 0
278 |
279 | /*@}*/
280 | #endif // MCP2515_DEFS_H
281 |
--------------------------------------------------------------------------------
/Railuino/src/can/mcp2515.c:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2007 Fabian Greif
2 | * All rights reserved.
3 | *
4 | * Redistribution and use in source and binary forms, with or without
5 | * modification, are permitted provided that the following conditions
6 | * are met:
7 | *
8 | * 1. Redistributions of source code must retain the above copyright
9 | * notice, this list of conditions and the following disclaimer.
10 | * 2. Redistributions in binary form must reproduce the above copyright
11 | * notice, this list of conditions and the following disclaimer in the
12 | * documentation and/or other materials provided with the distribution.
13 | *
14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 | * SUCH DAMAGE.
25 | */
26 | // ----------------------------------------------------------------------------
27 |
28 |
29 | #include
30 | #include
31 | #include "Arduino.h"
32 | #include "global.h"
33 | #include "mcp2515.h"
34 | #include "mcp2515_defs.h"
35 |
36 |
37 | #include "defaults.h"
38 |
39 | // -------------------------------------------------------------------------
40 | // Schreibt/liest ein Byte ueber den Hardware SPI Bus
41 |
42 | uint8_t spi_putc( uint8_t data )
43 | {
44 | // put byte in send-buffer
45 | SPDR = data;
46 |
47 | // wait until byte was send
48 | while( !( SPSR & (1< is the chip accessible?
174 | if (can_read_register(CNF1) != 3) {
175 |
176 | Serial.println(can_read_register(CNF1), HEX);
177 |
178 | SET(LED2_HIGH);
179 |
180 | return false;
181 | }
182 |
183 | // deaktivate the RXnBF Pins (High Impedance State)
184 | can_write_register(BFPCTRL, 0);
185 |
186 | // set TXnRTS as inputs
187 | can_write_register(TXRTSCTRL, 0);
188 |
189 | // turn off filters => receive any message
190 | can_write_register(RXB0CTRL, (1<flags.extended = bit_is_set(id2, 3) ? 1 : 0;
261 |
262 | // read id
263 | message->id = id1 << 21;
264 | message->id |= (((id2 & 0xE0) >> 3) | (id2 & 0x03)) << 16;
265 | message->id |= id3 << 8;
266 | message->id |= id4;
267 |
268 | // message->id = (id1 << 24) | (id2 << 16 | id3 << 8 | id4;
269 |
270 |
271 | // read DLC
272 | uint8_t length = spi_putc(0xff) & 0x0f;
273 |
274 | message->length = length;
275 | message->flags.rtr = (bit_is_set(status, 3)) ? 1 : 0;
276 |
277 | // read data
278 | for (t=0;tdata[t] = spi_putc(0xff);
280 | }
281 | SET(MCP2515_CS);
282 |
283 | // clear interrupt flag
284 | if (bit_is_set(status, 6)) {
285 | can_bit_modify(CANINTF, (1< could not send message
320 | return 0;
321 | }
322 |
323 | RESET(MCP2515_CS);
324 | spi_putc(SPI_WRITE_TX | address);
325 |
326 | uint32_t id1 = message->id >> 21;
327 |
328 | uint32_t id2 = ((message->id >> 13) & 0xE0) | ((message->id >> 16) & 0x03);
329 |
330 | if (message->flags.extended) {
331 | id2 |= 0x08;
332 | }
333 |
334 | uint32_t id3 = message->id >> 8;
335 | uint32_t id4 = message->id;
336 |
337 | spi_putc(id1); // 3
338 | spi_putc(id2); // 5
339 |
340 | spi_putc(id3);
341 | spi_putc(id4);
342 |
343 | uint8_t length = message->length & 0x0f;
344 |
345 | if (message->flags.rtr) {
346 | // a rtr-frame has a length, but contains no data
347 | spi_putc((1<data[t]);
356 | }
357 | }
358 | SET(MCP2515_CS);
359 |
360 | _delay_us(1);
361 |
362 | // send message
363 | RESET(MCP2515_CS);
364 | address = (address == 0) ? 1 : address;
365 | spi_putc(SPI_RTS | address);
366 | SET(MCP2515_CS);
367 |
368 | return address;
369 | }
370 |
--------------------------------------------------------------------------------
/RailuinoApp/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
18 |
19 |
24 |
25 |
26 |
32 |
33 |
41 |
42 |
43 |
47 |
48 |
56 |
57 |
58 |
62 |
63 |
68 |
69 |
75 |
76 |
81 |
82 |
83 |
87 |
88 |
96 |
97 |
105 |
106 |
114 |
115 |
123 |
124 |
132 |
133 |
134 |
138 |
139 |
147 |
148 |
156 |
157 |
165 |
166 |
174 |
175 |
183 |
184 |
185 |
190 |
191 |
196 |
197 |
202 |
203 |
204 |
209 |
210 |
215 |
216 |
221 |
222 |
227 |
228 |
233 |
234 |
239 |
240 |
241 |
245 |
246 |
251 |
252 |
257 |
258 |
263 |
264 |
269 |
270 |
275 |
276 |
277 |
--------------------------------------------------------------------------------
/Railuino/src/examples/05.Misc/Tests/Tests.ino:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #include
18 |
19 | // Controls whether all messages are being shown
20 | #define DEBUG true
21 |
22 | // Controls whether stress tests are included
23 | #define STRESS true
24 |
25 | // Locomotive that is being used during the tests (DCC required for reading CVs)
26 | #define LOCO ADDR_MM2 + 78
27 |
28 | // Turnout that is being used during the tests
29 | #define TURN ADDR_ACC_MM2 + 1
30 |
31 | // Signal that is being used during the tests
32 | #define SIGN ADDR_ACC_MM2 + 2
33 |
34 | // Number of passed and failed tests
35 | int pass = 0;
36 | int fail = 0;
37 |
38 | // Assertion facility, macros and helper functions
39 | #define TEST __TEST__(__FUNCTION__);
40 | #define PASS pass++; return;
41 | #define FAIL fail++; return;
42 | #define ASSERT(x, y) if(!__ASSERT__(x, y, __FUNCTION__)) { FAIL; }
43 |
44 | void __TEST__(const char funcName[]) {
45 | Serial.println();
46 | Serial.print("***** ");
47 | Serial.print(funcName);
48 | Serial.println(" *****");
49 | Serial.println();
50 | }
51 |
52 | boolean __ASSERT__(int assertNo, boolean exprValue, const char funcName[]) {
53 | if (!(exprValue)) {
54 | Serial.print(F("Assertion "));
55 | Serial.print(assertNo);
56 | Serial.print(F(" in function "));
57 | Serial.print(funcName);
58 | Serial.println(F("() failed"));
59 |
60 | return false;
61 | }
62 |
63 | return true;
64 | }
65 |
66 | // One-time initializer actually does all the work
67 | void setup() {
68 | Serial.begin(115200);
69 |
70 | while (!Serial);
71 |
72 | Serial.println();
73 | Serial.println();
74 | Serial.println();
75 | Serial.print(F("Press to start Railuino "));
76 | Serial.print(RAILUINO_VERSION >> 8);
77 | Serial.print(F("."));
78 | Serial.print(RAILUINO_VERSION & 0xff);
79 | Serial.println(F(" test suite..."));
80 | while(true) {
81 | int c = Serial.read();
82 | if (c == 10 || c == 13) {
83 | break;
84 | }
85 | }
86 |
87 | testMessageClear();
88 | testMessagePrintTo();
89 | testMessageParseFrom();
90 |
91 | testController();
92 | testInitController();
93 | testBeginEnd();
94 | testSendReceiveMessage();
95 | testExchangeMessage();
96 |
97 | testVersion();
98 | testPower();
99 | testGetSetDirection();
100 | testToggleDirection();
101 | testGetSetSpeed();
102 | testAccelerate();
103 | testDecelerate();
104 | testGetSetFunction();
105 | testToggleFunction();
106 | testSetAccessory();
107 | testSetTurnout();
108 | testReadWriteConfig();
109 |
110 | if (STRESS) {
111 | testSendReceiveMessageStress1();
112 | testSendReceiveMessageStress2();
113 | testExchangeMessageStress();
114 | }
115 |
116 | Serial.println();
117 | Serial.print(F("Total tests : ")); Serial.println(pass + fail);
118 | Serial.print(F("Passed tests : ")); Serial.println(pass);
119 | Serial.print(F("Failed tests : ")); Serial.println(fail);
120 | Serial.println();
121 | Serial.println(fail == 0 ? ":-)" : ":-(");
122 | Serial.flush();
123 | }
124 |
125 | // Loop stays empty
126 | void loop() {
127 | }
128 |
129 | // Tests clearing a message
130 | void testMessageClear() {
131 | TEST;
132 |
133 | TrackMessage message;
134 |
135 | message.command = 0xff;
136 | message.response = true;
137 | message.hash = 0xffff;
138 | message.length = 0x08;
139 |
140 | for (int i = 0; i < 8; i++) {
141 | message.data[i] = 0xff;
142 | }
143 |
144 | message.clear();
145 |
146 | ASSERT(0, message.command == 0);
147 | ASSERT(1, message.response == false);
148 | ASSERT(2, message.hash == 0);
149 | ASSERT(3, message.length == 0);
150 |
151 | for (int i = 0; i < 8; i++) {
152 | ASSERT(4 + i, message.data[i] == 0);
153 | }
154 |
155 | PASS;
156 | }
157 |
158 | // Tests printing a message to a Serial
159 | void testMessagePrintTo() {
160 | TEST;
161 |
162 | TrackMessage message;
163 |
164 | message.command = 0xff;
165 | message.response = true;
166 | message.hash = 0xdead;
167 | message.length = 0x08;
168 |
169 | for (int i = 0; i < 8; i++) {
170 | message.data[i] = i;
171 | }
172 |
173 | size_t bytesPrinted = Serial.println(message);
174 |
175 | ASSERT(0, bytesPrinted == 37);
176 |
177 | PASS;
178 | }
179 |
180 | // Tests parsing a message from a String
181 | void testMessageParseFrom() {
182 | TEST;
183 |
184 | TrackMessage message;
185 |
186 | String good1 = "dead ff 8 00 01 02 03 04 05 06 07";
187 | String good2 = "babe R 00 0";
188 | String good3 = "abba R 88 2 00 01 02 03 IGNORE THIS";
189 |
190 | String bad1 = "";
191 | String bad2 = "cafe 00 8";
192 | String bad3 = "dada 00 9 00 01 02 03 04 05 06 07 08";
193 | String bad4 = "The quick brown fox jumped over the lazy dog.";
194 |
195 | ASSERT(0, message.parseFrom(good1));
196 |
197 | ASSERT(1, message.command == 0xff);
198 | ASSERT(2, message.response == false);
199 | ASSERT(3, message.hash == 0xdead);
200 | ASSERT(4, message.length == 0x08);
201 |
202 | for (int i = 0; i < 8; i++) {
203 | ASSERT(5 + i, message.data[i] == i);
204 | }
205 |
206 | ASSERT(13, message.parseFrom(good2));
207 |
208 | ASSERT(14, message.command == 0x00);
209 | ASSERT(15, message.response == true);
210 | ASSERT(16, message.hash == 0xbabe);
211 | ASSERT(17, message.length == 0x00);
212 |
213 | ASSERT(18, message.parseFrom(good3));
214 |
215 | ASSERT(19, message.command == 0x88);
216 | ASSERT(20, message.response == true);
217 | ASSERT(21, message.hash == 0xabba);
218 | ASSERT(21, message.length == 0x02);
219 |
220 | ASSERT(22, message.data[0] == 0x00);
221 | ASSERT(23, message.data[1] == 0x01);
222 |
223 | for (int i = 2; i < 8; i++) {
224 | ASSERT(24 + i - 2, message.data[i] == 0);
225 | }
226 |
227 | ASSERT(30, !message.parseFrom(bad1));
228 | ASSERT(31, !message.parseFrom(bad2));
229 | ASSERT(32, !message.parseFrom(bad3));
230 | ASSERT(33, !message.parseFrom(bad4));
231 |
232 | PASS;
233 | }
234 |
235 | // Tests creating the controller
236 | void testController() {
237 | TEST;
238 |
239 | TrackController ctrl1;
240 |
241 | ASSERT(0, ctrl1.getHash() == 0);
242 | ASSERT(1, !ctrl1.isDebug());
243 | ASSERT(2, !ctrl1.isLoopback());
244 |
245 | TrackController ctrl2(0x1234, false);
246 |
247 | ASSERT(3, ctrl2.getHash() == 0x1234);
248 | ASSERT(4, !ctrl2.isDebug());
249 | ASSERT(5, !ctrl2.isLoopback());
250 |
251 | TrackController ctrl3(0x1234, true);
252 |
253 | ASSERT(6, ctrl3.getHash() == 0x1234);
254 | ASSERT(7, ctrl3.isDebug());
255 | ASSERT(8, !ctrl3.isLoopback());
256 |
257 | PASS;
258 | }
259 |
260 | // Tests initializing the controller
261 | void testInitController() {
262 | TEST;
263 |
264 | TrackController ctrl;
265 |
266 | ctrl.init(0x1234, false, false); // Must not use DEBUG constant!
267 | ASSERT(0, ctrl.getHash() == 0x1234);
268 | ASSERT(1, !ctrl.isDebug());
269 | ASSERT(2, !ctrl.isLoopback());
270 |
271 | ctrl.init(0x5678, true, true); // Must not use DEBUG constant!
272 | ASSERT(3, ctrl.getHash() == 0x5678);
273 | ASSERT(4, ctrl.isDebug());
274 | ASSERT(5, ctrl.isLoopback());
275 |
276 | PASS;
277 | }
278 |
279 | // Tests controller start/stop including hash generation
280 | void testBeginEnd() {
281 | TEST;
282 |
283 | TrackController ctrl;
284 |
285 | ctrl.init(0x7f7f, DEBUG, false);
286 | ctrl.begin();
287 | ASSERT(0, ctrl.getHash() == 0x7f7f);
288 | ctrl.end();
289 |
290 | ctrl.init(0, DEBUG, false);
291 | ctrl.begin();
292 | ASSERT(1, ctrl.getHash() != 0);
293 | ASSERT(2, ctrl.getHash() & 0x0300 == 0x0300);
294 | ASSERT(3, ctrl.getHash() | 0xff7f == 0xff7f);
295 | ctrl.end();
296 |
297 | PASS;
298 | }
299 |
300 | // Tests sending and receiving messages
301 | void testSendReceiveMessage() {
302 | TEST;
303 |
304 | TrackController ctrl;
305 | TrackMessage out;
306 | TrackMessage in;
307 |
308 | String text = "dead ff 8 00 01 02 03 04 05 06 07";
309 |
310 | ctrl.init(0x7f7f, DEBUG, true);
311 | ctrl.begin();
312 |
313 | out.parseFrom(text);
314 |
315 | // No response without a sent message
316 | ASSERT(0, !ctrl.receiveMessage(in));
317 |
318 | // Single message is reflected
319 | ASSERT(1, ctrl.sendMessage(out));
320 |
321 | delay(20);
322 |
323 | ASSERT(2, ctrl.receiveMessage(in));
324 | ASSERT(3, in.hash == out.hash);
325 | ASSERT(4, in.response);
326 | ASSERT(5, in.command == out.command);
327 | ASSERT(6, in.length == out.length);
328 |
329 | for (int i = 0; i < in.length; i++) {
330 | ASSERT(7 + i, in.data[i] == out.data[i]);
331 | }
332 |
333 | ASSERT(19, !ctrl.receiveMessage(in));
334 |
335 | PASS;
336 | }
337 |
338 | // Tests sending/receiving many messages one-by-one
339 | void testSendReceiveMessageStress1() {
340 | TEST;
341 |
342 | TrackController ctrl;
343 | TrackMessage out;
344 | TrackMessage in;
345 |
346 | ctrl.init(0x7f7f, DEBUG, true);
347 | ctrl.begin();
348 |
349 | // Bunch of send/receive calls
350 | for (int i = 0; i < 1000; i++) {
351 | int a = 20 * i;
352 |
353 | out.clear();
354 | out.hash = i;
355 | out.command = random(256);
356 | out.length = random(9);
357 |
358 | for (int j = 0; j < out.length; j++) {
359 | out.data[j] = random(256);
360 | }
361 |
362 | ASSERT(a + 0, !ctrl.receiveMessage(in));
363 | ASSERT(a + 1, ctrl.sendMessage(out));
364 |
365 | delay(20);
366 |
367 | ASSERT(a + 2, ctrl.receiveMessage(in));
368 |
369 | ASSERT(a + 3, in.response);
370 |
371 | ASSERT(a + 4, in.hash == out.hash);
372 | ASSERT(a + 5, in.command == out.command);
373 | ASSERT(a + 6, in.length == out.length);
374 |
375 | for (int k = 0; k < in.length; k++) {
376 | ASSERT(a + 10 + k, in.data[k] == out.data[k]);
377 | }
378 | }
379 |
380 | PASS;
381 | }
382 |
383 | // Tests sending/receiving many messages in bulk
384 | void testSendReceiveMessageStress2() {
385 | TEST;
386 |
387 | TrackController ctrl;
388 | TrackMessage out;
389 | TrackMessage in;
390 |
391 | ctrl.init(0x7f7f, DEBUG, true);
392 | ctrl.begin();
393 |
394 | int a = 0;
395 |
396 | // Bunch of "fill buffer, then empty it"
397 | for (int i = 0; i < 32; i++) {
398 | ASSERT(a++, !ctrl.receiveMessage(in));
399 |
400 | for (int j = 0; j < 32; j++) {
401 | out.clear();
402 | out.command = j;
403 | out.length = j % 9;
404 |
405 | for (int k = 0; k < out.length; k++) {
406 | out.data[k] = k;
407 | }
408 |
409 | ASSERT(a++, ctrl.sendMessage(out));
410 |
411 | delay(10);
412 | }
413 |
414 | for (int j = 0; j < 32; j++) {
415 | ASSERT(a++, ctrl.receiveMessage(in));
416 |
417 | ASSERT(a++, in.response);
418 |
419 | ASSERT(a++, in.hash == 0x7f7f);
420 | ASSERT(a++, in.command == j);
421 | ASSERT(a++, in.length == j % 9);
422 |
423 | for (int k = 0; k < in.length; k++) {
424 | ASSERT(a++, in.data[k] == k);
425 | }
426 | }
427 | }
428 |
429 | PASS;
430 | }
431 |
432 | // Tests exchanging messages
433 | void testExchangeMessage() {
434 | TEST;
435 |
436 | TrackController ctrl;
437 | TrackMessage out;
438 | TrackMessage in;
439 |
440 | String text = "dead ff 8 00 01 02 03 04 05 06 07";
441 |
442 | ctrl.init(0x7f7f, DEBUG, true);
443 | ctrl.begin();
444 |
445 | ASSERT(0, !ctrl.receiveMessage(in));
446 |
447 | // Single message is reflected
448 | out.parseFrom(text);
449 |
450 | ASSERT(1, ctrl.exchangeMessage(out, out, 1000));
451 | ASSERT(2, out.response);
452 | ASSERT(3, out.command == 0xff);
453 | ASSERT(4, out.length == 8);
454 |
455 | for (int i = 0; i < out.length; i++) {
456 | ASSERT(5 + i, out.data[i] == i);
457 | }
458 |
459 | ASSERT(19, !ctrl.receiveMessage(in));
460 |
461 | // When using separate 'in' message 'out' stays untouched
462 | out.parseFrom(text);
463 |
464 | ASSERT(1, ctrl.exchangeMessage(out, in, 1000));
465 | ASSERT(2, !out.response);
466 | ASSERT(3, in.response);
467 | ASSERT(4, in.command == 0xff);
468 | ASSERT(5, in.length == 8);
469 |
470 | for (int i = 0; i < in.length; i++) {
471 | ASSERT(6 + i, in.data[i] == i);
472 | }
473 |
474 | ASSERT(19, !ctrl.receiveMessage(in));
475 |
476 | // Non-matching responses are consumed
477 | out.command = 0x00;
478 | ASSERT(20, ctrl.sendMessage(out));
479 |
480 | out.parseFrom(text);
481 |
482 | ASSERT(21, ctrl.exchangeMessage(out, in, 1000));
483 | ASSERT(22, in.response);
484 | ASSERT(23, in.command == 0xff);
485 | ASSERT(24, in.length == 8);
486 |
487 | for (int i = 0; i < in.length; i++) {
488 | ASSERT(25 + i, in.data[i] == i);
489 | }
490 |
491 | ASSERT(29, !ctrl.receiveMessage(in));
492 |
493 | PASS;
494 | }
495 |
496 | // Tests exchanging many messages one-by-one
497 | void testExchangeMessageStress() {
498 | TEST;
499 |
500 | TrackController ctrl;
501 | TrackMessage out;
502 | TrackMessage in;
503 |
504 | ctrl.init(0x7f7f, DEBUG, true);
505 | ctrl.begin();
506 |
507 | // Bunch of execute calls
508 | for (int i = 0; i < 1000; i++) {
509 | int a = 20 * i;
510 |
511 | ASSERT(a + 0, !ctrl.receiveMessage(in));
512 |
513 | out.clear();
514 | out.command = random(256);
515 | out.length = random(9);
516 |
517 | for (int j = 0; j < out.length; j++) {
518 | out.data[j] = random(256);
519 | }
520 |
521 | ASSERT(a + 1, ctrl.exchangeMessage(out, in, 1000));
522 |
523 | ASSERT(a + 2, in.response);
524 |
525 | ASSERT(a + 3, in.hash == 0x7f7f);
526 | ASSERT(a + 4, in.command == out.command);
527 | ASSERT(a + 5, in.length == out.length);
528 |
529 | for (int k = 0; k < in.length; k++) {
530 | ASSERT(a + 10 + k, in.data[k] == out.data[k]);
531 | }
532 | }
533 |
534 | PASS;
535 | }
536 |
537 | // Tests the version
538 | void testVersion() {
539 | TEST;
540 |
541 | TrackController ctrl;
542 |
543 | ctrl.init(0, DEBUG, false);
544 | ctrl.begin();
545 |
546 | byte high, low;
547 |
548 | ASSERT(0, ctrl.getVersion(&high, &low));
549 |
550 | Serial.print("### Trackbox SW version is ");
551 | Serial.print(high, DEC);
552 | Serial.print(".");
553 | Serial.println(low, DEC);
554 |
555 | ASSERT(1, (word)(high << 8 | low) >= (word)TRACKBOX_VERSION);
556 |
557 | ctrl.end();
558 |
559 | PASS;
560 | }
561 |
562 | // Tests controlling the power
563 | void testPower() {
564 | TEST;
565 |
566 | TrackController ctrl;
567 |
568 | ctrl.init(0, DEBUG, false);
569 | ctrl.begin();
570 |
571 | ASSERT(0, ctrl.setPower(true));
572 |
573 | delay(1000);
574 |
575 | ASSERT(1, ctrl.setPower(false));
576 |
577 | delay(1000);
578 |
579 | ctrl.end();
580 |
581 | PASS;
582 | }
583 |
584 | // Tests getting/setting the direction of a loco
585 | void testGetSetDirection() {
586 | TEST;
587 |
588 | TrackController ctrl;
589 |
590 | ctrl.init(0, DEBUG, false);
591 | ctrl.begin();
592 |
593 | byte dir = 0;
594 |
595 | ASSERT(0, ctrl.setLocoDirection(LOCO, DIR_FORWARD));
596 | ASSERT(1, ctrl.getLocoDirection(LOCO, &dir));
597 | ASSERT(2, dir == DIR_FORWARD);
598 |
599 | delay(500);
600 |
601 | ASSERT(3, ctrl.setLocoDirection(LOCO, DIR_REVERSE));
602 | ASSERT(4, ctrl.getLocoDirection(LOCO, &dir));
603 | ASSERT(5, dir == DIR_REVERSE);
604 |
605 | delay(500);
606 |
607 | ASSERT(6, ctrl.setLocoDirection(LOCO, DIR_CHANGE));
608 | ASSERT(7, ctrl.getLocoDirection(LOCO, &dir));
609 | ASSERT(8, dir == DIR_FORWARD);
610 |
611 | delay(500);
612 |
613 | ASSERT(9, ctrl.setLocoDirection(LOCO, DIR_CURRENT));
614 | ASSERT(10, ctrl.getLocoDirection(LOCO, &dir));
615 | ASSERT(11, dir == DIR_FORWARD);
616 |
617 | delay(500);
618 |
619 | ctrl.end();
620 |
621 | PASS;
622 | }
623 |
624 | // Tests toggling the direction of a loco
625 | void testToggleDirection() {
626 | TEST;
627 |
628 | TrackController ctrl;
629 |
630 | ctrl.init(0, DEBUG, false);
631 | ctrl.begin();
632 |
633 | byte dir = 0;
634 |
635 | ASSERT(0, ctrl.setLocoDirection(LOCO, DIR_FORWARD));
636 |
637 | delay(500);
638 |
639 | ASSERT(1, ctrl.toggleLocoDirection(LOCO));
640 | ASSERT(2, ctrl.getLocoDirection(LOCO, &dir));
641 | ASSERT(3, dir == DIR_REVERSE);
642 |
643 | delay(500);
644 |
645 | ASSERT(4, ctrl.toggleLocoDirection(LOCO));
646 | ASSERT(5, ctrl.getLocoDirection(LOCO, &dir));
647 | ASSERT(6, dir == DIR_FORWARD);
648 |
649 | delay(500);
650 |
651 | ctrl.end();
652 |
653 | PASS;
654 | }
655 |
656 | // Tests getting/setting the speed of a loco
657 | void testGetSetSpeed() {
658 | TEST;
659 |
660 | TrackController ctrl;
661 |
662 | word speed = 0;
663 |
664 | ctrl.init(0, DEBUG, false);
665 | ctrl.begin();
666 |
667 | ASSERT(0, ctrl.setLocoSpeed(LOCO, 0));
668 | ASSERT(1, ctrl.getLocoSpeed(LOCO, &speed));
669 | ASSERT(2, speed == 0);
670 |
671 | delay(500);
672 |
673 | ASSERT(3, ctrl.setLocoSpeed(LOCO, 500));
674 | ASSERT(4, ctrl.getLocoSpeed(LOCO, &speed));
675 | ASSERT(5, speed == 500);
676 |
677 | delay(500);
678 |
679 |
680 | ASSERT(6, ctrl.setLocoSpeed(LOCO, 1000));
681 | ASSERT(7, ctrl.getLocoSpeed(LOCO, &speed));
682 | ASSERT(8, speed == 1000);
683 |
684 | delay(500);
685 |
686 | ASSERT(9, ctrl.setLocoSpeed(LOCO, 1001));
687 | ASSERT(10, ctrl.getLocoSpeed(LOCO, &speed));
688 | ASSERT(11, speed == 1000);
689 |
690 | delay(500);
691 |
692 | PASS;
693 | }
694 |
695 | // Tests accelerating a loco
696 | void testAccelerate() {
697 | TEST;
698 |
699 | TrackController ctrl;
700 |
701 | word speed = 0;
702 |
703 | ctrl.init(0, DEBUG, false);
704 | ctrl.begin();
705 |
706 | ASSERT(0, ctrl.setLocoSpeed(LOCO, 0));
707 | ctrl.accelerateLoco(LOCO);
708 | ASSERT(1, ctrl.getLocoSpeed(LOCO, &speed));
709 | ASSERT(2, speed == 77);
710 |
711 | delay(500);
712 |
713 | ASSERT(3, ctrl.setLocoSpeed(LOCO, 999));
714 | ctrl.accelerateLoco(LOCO);
715 | ASSERT(4, ctrl.getLocoSpeed(LOCO, &speed));
716 | ASSERT(5, speed == 1000);
717 |
718 | delay(500);
719 |
720 | ctrl.end();
721 |
722 | PASS;
723 | }
724 |
725 | // Tests decelerating a loco
726 | void testDecelerate() {
727 | TEST;
728 |
729 | TrackController ctrl;
730 |
731 | word speed = 0;
732 |
733 | ctrl.init(0, DEBUG, false);
734 | ctrl.begin();
735 |
736 | ASSERT(0, ctrl.setLocoSpeed(LOCO, 1000));
737 | ctrl.decelerateLoco(LOCO);
738 | ASSERT(1, ctrl.getLocoSpeed(LOCO, &speed));
739 | ASSERT(2, speed == 923);
740 |
741 | delay(500);
742 |
743 | ASSERT(3, ctrl.setLocoSpeed(LOCO, 1));
744 | ctrl.decelerateLoco(LOCO);
745 | ASSERT(4, ctrl.getLocoSpeed(LOCO, &speed));
746 | ASSERT(5, speed == 0);
747 |
748 | delay(500);
749 |
750 | ctrl.end();
751 |
752 | PASS;
753 | }
754 |
755 | // Tests getting/setting the functions of a loco
756 | void testGetSetFunction() {
757 | TEST;
758 |
759 | TrackController ctrl;
760 |
761 | byte value = 0;
762 |
763 | ctrl.init(0, DEBUG, false);
764 | ctrl.begin();
765 |
766 | for (int i = 0; i < 4; i++) {
767 | ASSERT(0, ctrl.setLocoFunction(LOCO, i, 1));
768 | ASSERT(1, ctrl.getLocoFunction(LOCO, i, &value));
769 | ASSERT(2, value != 0);
770 |
771 | delay(500);
772 |
773 | ASSERT(3, ctrl.setLocoFunction(LOCO, i, 0));
774 | ASSERT(4, ctrl.getLocoFunction(LOCO, i, &value));
775 | ASSERT(5, value == 0);
776 |
777 | delay(500);
778 | }
779 |
780 | ctrl.end();
781 |
782 | PASS;
783 | }
784 |
785 | // Tests toggling the functions of a loco
786 | void testToggleFunction() {
787 | TEST;
788 |
789 | TrackController ctrl;
790 |
791 | byte value = 0;
792 |
793 | ctrl.init(0, DEBUG, false);
794 | ctrl.begin();
795 |
796 | for (int i = 0; i < 4; i++) {
797 | ASSERT(0, ctrl.setLocoFunction(LOCO, i, 0));
798 |
799 | ASSERT(1, ctrl.toggleLocoFunction(LOCO, i));
800 | ASSERT(2, ctrl.getLocoFunction(LOCO, i, &value));
801 | ASSERT(3, value != 0);
802 |
803 | delay(500);
804 |
805 | ASSERT(4, ctrl.toggleLocoFunction(LOCO, i));
806 | ASSERT(5, ctrl.getLocoFunction(LOCO, i, &value));
807 | ASSERT(6, value == 0);
808 |
809 | delay(500);
810 | }
811 |
812 | ctrl.end();
813 |
814 | PASS;
815 | }
816 |
817 | // Tests controlling a (magnetic) accessory
818 | void testSetAccessory() {
819 | TEST;
820 |
821 | TrackController ctrl;
822 |
823 | byte state = 0;
824 | byte power = 0;
825 |
826 | ctrl.init(0, DEBUG, false);
827 | ctrl.begin();
828 | ctrl.setLocoSpeed(LOCO, 0);
829 | ctrl.setPower(true);
830 |
831 | byte states[4] = { ACC_HP0, ACC_HP1, ACC_HP2, ACC_SH0 };
832 |
833 | for (int i = 0 ; i < 4; i++) {
834 | ASSERT(0, ctrl.setAccessory(SIGN, states[i], 1, 0));
835 | delay(500);
836 | }
837 |
838 | ctrl.end();
839 |
840 | PASS;
841 | }
842 |
843 | // Tests controlling a turnout
844 | void testSetTurnout() {
845 | TEST;
846 |
847 | TrackController ctrl;
848 |
849 | ctrl.init(0, DEBUG, false);
850 | ctrl.begin();
851 | ctrl.setLocoSpeed(LOCO, 0);
852 | ctrl.setPower(true);
853 |
854 | ASSERT(0, ctrl.setTurnout(TURN, true));
855 | delay(500);
856 | ASSERT(1, ctrl.setTurnout(TURN, false));
857 | delay(500);
858 |
859 | ctrl.end();
860 |
861 | PASS;
862 | }
863 |
864 | // Tests reading/writing config values
865 | void testReadWriteConfig() {
866 | TEST;
867 |
868 | TrackController ctrl;
869 |
870 | ctrl.init(0, DEBUG, false);
871 | ctrl.begin();
872 | ctrl.setLocoSpeed(LOCO, 0);
873 | ctrl.setPower(true);
874 |
875 | byte saved = 3;
876 | byte value = 252;
877 |
878 | // Can only read for DCC, so assume true for other protocols
879 | ASSERT(0, LOCO < ADDR_DCC || ctrl.readConfig(LOCO, 2, &saved));
880 |
881 | delay(500);
882 |
883 | ASSERT(1, ctrl.writeConfig(LOCO, 2, 255 - saved));
884 |
885 | delay(500);
886 |
887 | // Can only read for DCC, so assume true for other protocols
888 | ASSERT(3, LOCO < ADDR_DCC || ctrl.readConfig(LOCO, 2, &value));
889 | ASSERT(4, value == 255 - saved);
890 |
891 | delay(500);
892 |
893 | ASSERT(5, ctrl.writeConfig(LOCO, 2, saved));
894 |
895 | delay(500);
896 |
897 | ctrl.end();
898 |
899 | PASS;
900 | }
901 |
--------------------------------------------------------------------------------
/Railuino/src/Railuino.h:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | #ifndef railuino__h
18 | #define railuino__h
19 |
20 | #include
21 | #include
22 |
23 | // ===================================================================
24 | // === Board detection ===============================================
25 | // ===================================================================
26 |
27 | #if defined(__AVR_ATmega328P__)
28 | #define __UNO__ 1
29 | #define __BOARD__ "Arduino Uno"
30 | #elif defined(__AVR_ATmega32U4__)
31 | #define __LEONARDO__ 1
32 | #define __BOARD__ "Arduino Leonardo"
33 | #elif defined(__AVR_ATmega2560__)
34 | #define __MEGA__ 1
35 | #define __BOARD__ "Arduino Mega"
36 | #else
37 | #error Unsupported board. Please adjust library.
38 | #endif
39 |
40 | // ===================================================================
41 | // === Common definitions ============================================
42 | // ===================================================================
43 |
44 | /**
45 | * Version of Railuino library and required connection box software.
46 | */
47 | #define RAILUINO_VERSION 0x005A // 0.90
48 | #define TRACKBOX_VERSION 0x0127 // 1.39
49 |
50 | /**
51 | * Constants for protocol base addresses.
52 | */
53 | #define ADDR_MM2 0x0000 // MM2 locomotive
54 | #define ADDR_SX1 0x0800 // Selectrix (old) locomotive
55 | #define ADDR_MFX 0x4000 // MFX locomotive
56 | #define ADDR_SX2 0x8000 // Selectrix (new) locomotive
57 | #define ADDR_DCC 0xC000 // DCC locomotive
58 | #define ADDR_ACC_SX1 0x2000 // Selectrix (old) magnetic accessory
59 | #define ADDR_ACC_MM2 0x2FFF // MM2 magnetic accessory
60 | #define ADDR_ACC_DCC 0x3800 // DCC magnetic accessory
61 |
62 | /**
63 | * Constants for classic MM2 Delta addresses.
64 | */
65 | #define DELTA1 78
66 | #define DELTA2 72
67 | #define DELTA3 60
68 | #define DELTA4 24
69 |
70 | /**
71 | * Constants for locomotive directions.
72 | */
73 | #define DIR_CURRENT 0
74 | #define DIR_FORWARD 1
75 | #define DIR_REVERSE 2
76 | #define DIR_CHANGE 3
77 |
78 | /**
79 | * Constants for accessory states including some aliases.
80 | */
81 | #define ACC_OFF 0
82 | #define ACC_ROUND 0
83 | #define ACC_RED 0
84 | #define ACC_RIGHT 0
85 | #define ACC_HP0 0
86 |
87 | #define ACC_ON 1
88 | #define ACC_GREEN 1
89 | #define ACC_STRAIGHT 1
90 | #define ACC_HP1 1
91 |
92 | #define ACC_YELLOW 2
93 | #define ACC_LEFT 2
94 | #define ACC_HP2 2
95 |
96 | #define ACC_WHITE 3
97 | #define ACC_SH0 3
98 |
99 | /**
100 | * Represents a message going through the Marklin CAN bus. More or
101 | * less a beautified version of the real CAN message. You normally
102 | * don't need to use this unless you want to experiment with the
103 | * protocol or extend the library. See the Marklin protocol
104 | * documentation for details. The TrackMessage is a Printable, so
105 | * it can be directly used in Serial.println(), for instance. It
106 | * can also be converted from a String.
107 | */
108 | class TrackMessage : public Printable {
109 |
110 | public:
111 |
112 | /**
113 | * The command number.
114 | */
115 | byte command;
116 |
117 | /**
118 | * The hash that is used for avoiding device/message collisions.
119 | */
120 | word hash;
121 |
122 | /**
123 | * Whether this is a response to a request.
124 | */
125 | boolean response;
126 |
127 | /**
128 | * The number of data bytes in the payload.
129 | */
130 | byte length;
131 |
132 | /**
133 | * The actual message data bytes.
134 | */
135 | byte data[8];
136 |
137 | /**
138 | * Clears the message, setting all values to zero. Provides for
139 | * easy recycling of TrackMessage objects.
140 | */
141 | void clear();
142 |
143 | /**
144 | * Prints the message to the given Print object, which could be a
145 | * Serial object, for instance. The message format looks like this
146 | *
147 | * HHHH R CC L DD DD DD DD DD DD DD DD
148 | *
149 | * with all numbers being hexadecimals and the data bytes being
150 | * optional beyond what the message length specifies. Exactly one
151 | * whitespace is inserted between different fields as a separator.
152 | */
153 | virtual size_t printTo(Print &p) const;
154 |
155 | /**
156 | * Parses the message from the given String. Returns true on
157 | * success, false otherwise. The message must have exactly the
158 | * format that printTo creates. This includes each and every
159 | * whitespace. If the parsing fails the state of the object is
160 | * undefined afterwards, and a clear() is recommended.
161 | */
162 | boolean parseFrom(String &s);
163 |
164 | };
165 |
166 | // ===================================================================
167 | // === TrackController ===============================================
168 | // ===================================================================
169 |
170 | /**
171 | * Controls things on and connected to the track: locomotives,
172 | * turnouts and other accessories. While there are some low-level
173 | * methods for dealing with messages, you will normally want to use
174 | * the high-level methods that wrap most of the the nasty protocol
175 | * details. When addressing something, you have to tell the system the
176 | * type of address (or decoder) you are using by adding the proper
177 | * protocol base address. For instance, DCC locomotive 42 is properly
178 | * addressed as ADDR_DCC + 42.
179 | */
180 | class TrackController {
181 |
182 | private:
183 |
184 | /**
185 | * Stores the hash of our controller. This must not conflict with
186 | * hashes of other devices in the setup (most likely the MS2 and
187 | * the connector box).
188 | */
189 | word mHash;
190 |
191 | /**
192 | * Stores the debug flag. When debugging is on, all outgoing and
193 | * incoming messages are printed to the Serial console.
194 | */
195 | boolean mDebug;
196 |
197 | /**
198 | * Holds the loopback flag. When loopback is on, messages are
199 | * reflected by the CAN controller. No external communication
200 | * takes place. This is helpful for some test cases.
201 | */
202 | boolean mLoopback;
203 |
204 | /**
205 | * Generates a new hash and makes sure it does not conflict
206 | * with those of other devices in the setup.
207 | */
208 | void generateHash();
209 |
210 | public:
211 |
212 | /**
213 | * Creates a new TrackController with default values. This should
214 | * be fine for most use cases. Further configuration can be done
215 | * by using the init() method.
216 | */
217 | TrackController();
218 |
219 | /**
220 | * Creates a new TrackController with the given hash and debugging
221 | * flag. A zero hash will result in a unique hash begin generated.
222 | */
223 | TrackController(word hash, boolean debug);
224 |
225 | /**
226 | * Is called when a TrackController is being destroyed. Does the
227 | * necessary cleanup. No need to call this manually.
228 | */
229 | ~TrackController();
230 |
231 | /**
232 | * Initializes the TrackController with the given values. This
233 | * should be called before begin, otherwise it will not take
234 | * effect. A zero hash will result in a unique hash begin
235 | * generated.
236 | */
237 | void init(word hash, boolean debug, boolean loopback);
238 |
239 | /**
240 | * Queries the hash used by the TrackController.
241 | */
242 | word getHash();
243 |
244 | /**
245 | * Reflects whether the TrackController is in debug mode,
246 | * where all messages are dumped to the Serial console.
247 | */
248 | boolean isDebug();
249 |
250 | /**
251 | * Reflects whether the TrackController is in debug mode,
252 | * where all messages are reflected by the CAN controller.
253 | */
254 | boolean isLoopback();
255 |
256 | /**
257 | * Sends a message and reports true on success. Internal method.
258 | * Normally you don't want to use this, but the more convenient
259 | * methods below instead.
260 | */
261 | boolean sendMessage(TrackMessage &message);
262 |
263 | /**
264 | * Receives an arbitrary message, if available, and reports true
265 | * on success. Does not block. Internal method. Normally you
266 | * don't want to use this, but the more convenient methods below
267 | * instead.
268 | */
269 | boolean receiveMessage(TrackMessage &message);
270 |
271 | /**
272 | * Sends a message and waits for the corresponding response,
273 | * returning true on success. Blocks until either a message with
274 | * the same command ID and the response marker arrives or the
275 | * timeout (in ms) expires. All non-matching messages are
276 | * skipped. Internal method. Normally you don't want to use this,
277 | * but the more convenient methods below instead. 'out' and 'in'
278 | * may be the same object.
279 | */
280 | boolean exchangeMessage(TrackMessage &out, TrackMessage &in, word timeout);
281 |
282 | /**
283 | * Initializes the CAN hardware and starts receiving CAN
284 | * messages. CAN messages are put into an internal buffer of
285 | * limited size, so they don't get lost, but you have to take
286 | * care of them in time. Otherwise the buffer might overflow.
287 | */
288 | void begin();
289 |
290 | /**
291 | * Stops receiving messages from the CAN hardware. Clears
292 | * the internal buffer.
293 | */
294 | void end();
295 |
296 | /**
297 | * Controls power on the track. When passing false, all
298 | * locomotives will stop, but remember their previous directions
299 | * and speeds. When passing true, all locomotives will regain
300 | * their old directions and speeds. The system starts in
301 | * stopped mode in order to avoid accidents. The return value
302 | * reflects whether the call was successful.
303 | */
304 | boolean setPower(boolean power);
305 |
306 | /**
307 | * Sets the direction of the given locomotive. Valid directions
308 | * are those specified by the DIR_* constants. The return value
309 | * reflects whether the call was successful.
310 | */
311 | boolean setLocoDirection(word address, byte direction);
312 |
313 | /**
314 | * Toggles the direction of the given locomotive. This normally
315 | * includes a full stop.
316 | */
317 | boolean toggleLocoDirection(word address);
318 |
319 | /**
320 | * Sets the speed of the given locomotive. Valid speeds are
321 | * 0 to 1023 (inclusive), though the connector box will limit
322 | * all speeds beyond 1000 to 1000. The return value reflects
323 | * whether the call was successful.
324 | */
325 | boolean setLocoSpeed(word address, word speed);
326 |
327 | /**
328 | * Increases the speed of the given locomotive by 1/14th
329 | * of the maximum speed.
330 | */
331 | boolean accelerateLoco(word address);
332 |
333 | /**
334 | * Decreases the speed of the given locomotive by 1/14th
335 | * of the maximum speed.
336 | */
337 | boolean decelerateLoco(word address);
338 |
339 | /**
340 | * Sets the given function of the given locomotive (or simply a
341 | * function decoder). Valid functions are 0 to 31, with 0
342 | * normally denoting the head/backlight. Valid values are, again,
343 | * 0 ("off") to 31, although not all protocols support values
344 | * beyond 1 (which then means "on"). The return value reflects
345 | * whether the call was successful.
346 | */
347 | boolean setLocoFunction(word address, byte function, byte power);
348 |
349 | /**
350 | * Toggles the given function of the given locomotive. Valid
351 | * functions are 0 to 31, with 0 normally denoting the
352 | * head/backlight.
353 | */
354 | boolean toggleLocoFunction(word address, byte function);
355 |
356 | /**
357 | * Switches the given magnetic accessory. Valid position values
358 | * are those denoted by the ACC_* constants. Valid power values
359 | * are 0 ("off") to 31 (inclusive) although not all protocols
360 | * support values beyond 1 (which then means "on"). The final
361 | * parameter specifies the time (in ms) for which the accessory
362 | * will be active. A time of 0 means the accessory will only be
363 | * switched on. Some magnetic accessories must not be active for
364 | * too long, because they might burn out. A good timeout for
365 | * Marklin turnouts seems to be 20 ms. The return value reflects
366 | * whether the call was successful.
367 | */
368 | boolean setAccessory(word address, byte position, byte power, word time);
369 |
370 | /**
371 | * Switches a turnout. This is actually a convenience function
372 | * around setAccessory() that uses default values for some
373 | * parameters. The return value reflects whether the call was
374 | * successful.
375 | */
376 | boolean setTurnout(word address, boolean straight);
377 |
378 | /**
379 | * Queries the direction of the given locomotive and writes it
380 | * into the referenced byte. The return value indicates whether
381 | * the call was successful and the direction is valid.
382 | */
383 | boolean getLocoDirection(word address, byte *direction);
384 |
385 | /**
386 | * Queries the speed of the given locomotive and writes it
387 | * into the referenced byte. The return value indicates whether
388 | * the call was successful and the speed is valid.
389 | */
390 | boolean getLocoSpeed(word address, word *speed);
391 |
392 | /**
393 | * Queries the given function of the given locomotive and writes
394 | * it into the referenced byte. The return value indicates
395 | * whether the call was successful and the power is valid. Note
396 | * that the call will not reflect the original power value sent
397 | * to the function, but only 0 ("off") or 1 ("on").
398 | */
399 | boolean getLocoFunction(word address, byte function, byte *power);
400 |
401 | /**
402 | * Queries the given magnetic accessory's state and and writes
403 | * it into the referenced bytes. The return value indicates
404 | * whether the call was successful and the bytes are valid. Note
405 | * that the call will not reflect the original power value sent
406 | * to the function, but only 0 ("off") or 1 ("on").
407 | */
408 | boolean getAccessory(word address, byte *position, byte *power);
409 |
410 | /**
411 | * Queries a turnout state. This is actually a convenience
412 | * function around getAccessory() that uses default values for
413 | * some parameters.
414 | */
415 | boolean getTurnout(word address, boolean *straight);
416 |
417 | /**
418 | * Writes the given value to the given config number of the given
419 | * locomotive. The return value reflects whether the call was
420 | * successful.
421 | */
422 | boolean writeConfig(word address, word number, byte value);
423 |
424 | /**
425 | * Reads the given config number of the given locomotive into the
426 | * given value.
427 | */
428 | boolean readConfig(word address, word number, byte *value);
429 |
430 | /**
431 | * Queries the software version of the track format processor.
432 | */
433 | boolean getVersion(byte *high, byte *low);
434 |
435 | };
436 |
437 | /**
438 | * The little brother of TrackController. This class talks to an IR
439 | * connector box. The interface is more or less a subset of the one
440 | * that TrackController offers. There are exactly four locomotives,
441 | * numbered 1 to 4, and sixteen turnouts, numbered 1 to 16. The IR
442 | * controller does not care about protocols, although in reality
443 | * it's all MM2. Also, no object-oriented mumbo-jumbo here to
444 | * accomodate for the limited Arduino resources. Note that with
445 | * the exception of remembering power this class is stateless,
446 | * so the caller has to keep track of things, if necessary.
447 | */
448 | class TrackControllerInfrared {
449 |
450 | private:
451 |
452 | /**
453 | * Whether the power is on.
454 | */
455 | boolean mPower;
456 |
457 | /**
458 | * The current value of the toggle bit (needed for RC5).
459 | */
460 | word mToggle;
461 |
462 | public:
463 |
464 | /**
465 | * Creates a new TrackControllerInfrared and does some
466 | * initializing. Assumes the IR LED is on pin 9.
467 | */
468 | TrackControllerInfrared();
469 |
470 | /**
471 | * Sends a message consisting of address and command (in the
472 | * sense of RC5). Takes care of the extension and alternating
473 | * bits. Internal method. Normally you don't want to use this,
474 | * but the more convenient methods below instead.
475 | */
476 | boolean sendMessage(word address, word command);
477 |
478 | /**
479 | * Controls power on the track. When passing false, all
480 | * locomotives will stop, but remember their previous directions
481 | * and speeds. When passing true, all locomotives will regain
482 | * their old directions and speeds. The system starts in
483 | * stopped mode in order to avoid accidents. The return value
484 | * reflects whether the call was successful.
485 | */
486 | boolean setPower(boolean power);
487 |
488 | /**
489 | * Toogles the direction of the given locomotive. This includes
490 | * a full stop of the locomotive. The return value reflects whether
491 | * the call was successful.
492 | */
493 | boolean toggleLocoDirection(int loco);
494 |
495 | /**
496 | * Accelerates the given locomotive by 1 level on a scale assuming
497 | * a total of 14 speed levels. Does not have any effect if the
498 | * maximum speed has already been reached.
499 | */
500 | boolean accelerateLoco(int loco);
501 |
502 | /**
503 | * Accelerates the given locomotive by 1 level on a scale assuming
504 | * a total of 14 speed levels. Does not have any effect if the
505 | * maximum speed has already been reached.
506 | */
507 | boolean decelerateLoco(int loco);
508 |
509 | /**
510 | * Toggles the given function of the given locomotive (or simply a
511 | * function decoder). Valid functions are 0 to 4, with 0 normally
512 | * denoting the head/backlight. The return value reflects whether
513 | * the call was successful.
514 | */
515 | boolean toggleLocoFunction(int loco, int function);
516 |
517 | /**
518 | * Switches a turnout. The return value reflects whether the call
519 | * was successful.
520 | */
521 | boolean setTurnout(int turnout, boolean straight);
522 |
523 | };
524 |
525 | // ===================================================================
526 | // === TrackReporterS88 ==============================================
527 | // ===================================================================
528 |
529 | /**
530 | * Implements the S88 bus protocol for reporting the state of tracks.
531 | * S88 is basically a long shift register where each bit corresponds
532 | * to a single contact on the track. Flip-flops on each S88 board make
533 | * sure activations are stored, so it is not necessary to query a
534 | * contact at the exact time it is activated. This implementation
535 | * allows a maximum of 512 bits or 32 full-width (16 bit) S88 boards.
536 | * The S88 standard recommends a maximum of 30 boards, so we should be
537 | * on the safe side.
538 | */
539 | class TrackReporterS88 {
540 |
541 | private:
542 |
543 | /**
544 | * The number of contacts available.
545 | */
546 | int mSize;
547 |
548 | /**
549 | * The most recent contact values we know.
550 | */
551 | byte mSwitches[64];
552 |
553 | public:
554 |
555 | /**
556 | * Creates a new TrackReporter with the given number of modules
557 | * being attached. While this value can be safely set to the
558 | * maximum of 32, it makes sense to specify the actual number,
559 | * since this speeds up reporting. The method assumes 16 bit
560 | * modules. If you use 8 bit modules instead (or both) you need
561 | * to do the math yourself.
562 | */
563 | TrackReporterS88(int modules);
564 |
565 | /**
566 | * Reads the current state of all contacts into the TrackReporter
567 | * and clears the flip-flops on all S88 boards. Call this method
568 | * periodically to have up-to-date values.
569 | */
570 | void refresh();
571 |
572 | /**
573 | * Returns the state of an individual contact. Valid index values
574 | * are 1 to 512.
575 | */
576 | boolean getValue(int index);
577 |
578 | };
579 |
580 | // ===================================================================
581 | // === TrackReporterIOX ==============================================
582 | // ===================================================================
583 |
584 | /**
585 | * Implements a low-cost track reporting mechanism based on I/O
586 | * expanders. Currently the MCP 23S08 and MCP 23S17 are supported,
587 | * with the MCP 23S08 being treaded just like the 16 bit module, so
588 | * the upper 8 bits are undefined. The IO expanders are connected via
589 | * SPI. Pin x is being used for chip select. The interrupt line must
590 | * be connected to Arduino pin 3 (interrupt 1) and pulled up via a
591 | * resistor. Multiple expanders can be combined, assuming they are
592 | * configured to different addresses via the hardware pins. All need
593 | * to share the interrupt line, which is configured for open drain.
594 | */
595 | class TrackReporterIOX {
596 |
597 | private:
598 |
599 | /**
600 | * The number of IO expanders being used.
601 | */
602 | int mCount;
603 |
604 | /**
605 | * The most recent contact values we know.
606 | */
607 | byte mSwitches[16];
608 |
609 | public:
610 |
611 | /**
612 | * Creates a new TrackReporter with the given number of expanders.
613 | */
614 | TrackReporterIOX(int modules);
615 |
616 | /**
617 | * Is called when a TrackReporter is being destroyed. Does the
618 | * necessary cleanup. No need to call this manually.
619 | */
620 | ~TrackReporterIOX();
621 |
622 | /**
623 | * Reads the current state of all expanders into the TrackReporter.
624 | * Call this method periodically to have up-to-date values.
625 | */
626 | void refresh();
627 |
628 | /**
629 | * Returns the state of an individual contact. Note counting starts
630 | * from 1 in order to stay compatible with the S88 reporter.
631 | */
632 | boolean getValue(int index);
633 |
634 | };
635 |
636 | #endif
637 |
--------------------------------------------------------------------------------
/Railuino/src/Railuino.cpp:
--------------------------------------------------------------------------------
1 | /*********************************************************************
2 | * Railuino - Hacking your Märklin
3 | *
4 | * Copyright (C) 2012 Joerg Pleumann
5 | *
6 | * This library is free software; you can redistribute it and/or
7 | * modify it under the terms of the GNU Lesser General Public
8 | * License as published by the Free Software Foundation; either
9 | * version 2.1 of the License, or (at your option) any later version.
10 | *
11 | * This library is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * LICENSE file for more details.
15 | */
16 |
17 | // #include
18 | #include "Railuino.h"
19 | // #include "canbus/canbus.c"
20 | #include "can/mcp2515.h"
21 | #include "can/mcp2515.c"
22 |
23 | #if defined(__LEONARDO__)
24 | #include "ir/infrared2.c"
25 | #else
26 | #include "ir/infrared.c"
27 | #endif
28 |
29 | size_t printHex(Print &p, unsigned long hex, int digits) {
30 | size_t size = 0;
31 |
32 | String s = String(hex, HEX);
33 |
34 | for (int i = s.length(); i < digits; i++) {
35 | size += p.print("0");
36 | }
37 |
38 | size += p.print(s);
39 |
40 | return size;
41 | }
42 |
43 | int parseHex(String &s, int start, int end, boolean *ok) {
44 | int value = 0;
45 |
46 | for (int i = start; i < end; i++) {
47 | char c = s.charAt(i);
48 |
49 | if (c >= '0' && c <= '9') {
50 | value = 16 * value + c - '0';
51 | } else if (c >= 'a' && c <= 'f') {
52 | value = 16 * value + 10 + c - 'a';
53 | } else if (c >= 'A' && c <= 'F') {
54 | value = 16 * value + 10 + c - 'A';
55 | } else {
56 | ok = false;
57 | return -1;
58 | }
59 | }
60 |
61 | return value;
62 | }
63 |
64 | #define SIZE 32
65 |
66 | #define ulong unsigned long
67 |
68 | can_t _buffer[SIZE];
69 |
70 | volatile int posRead = 0;
71 |
72 | volatile int posWrite = 0;
73 |
74 | volatile boolean lastOpWasWrite = false;
75 |
76 | void enqueue() {
77 | if (posWrite == posRead && lastOpWasWrite) {
78 | // Serial.println("!!! Buffer full");
79 | return;
80 | }
81 |
82 | if (can_get_message(&_buffer[posWrite])) {
83 | posWrite = (posWrite + 1) % SIZE;
84 | } else {
85 | // Serial.println("!!! No message");
86 | }
87 |
88 | lastOpWasWrite = true;
89 | }
90 |
91 | boolean dequeue(can_t *p) {
92 | noInterrupts();
93 |
94 | if (posWrite == posRead && !lastOpWasWrite) {
95 | interrupts();
96 | return false;
97 | }
98 |
99 | memcpy(p, &_buffer[posRead], sizeof(can_t));
100 | /*
101 | p->id=_buffer[posRead].id;
102 | p->length=_buffer[posRead].length;
103 |
104 | for (int i = 0; i < p->length; i++) {
105 | p->data[i] = _buffer[posRead].data[i];
106 | }
107 | */
108 | //*p = _buffer[posRead];
109 |
110 | posRead = (posRead + 1) % SIZE;
111 | lastOpWasWrite = false;
112 |
113 | interrupts();
114 |
115 | return true;
116 | }
117 |
118 | // ===================================================================
119 | // === TrackMessage ==================================================
120 | // ===================================================================
121 |
122 | void TrackMessage::clear() {
123 | command = 0;
124 | hash = 0;
125 | response = false;
126 | length = 0;
127 | for (int i = 0; i < 8; i++) {
128 | data[i] = 0;
129 | }
130 | }
131 |
132 | size_t TrackMessage::printTo(Print& p) const {
133 | size_t size = 0;
134 |
135 | size += printHex(p, hash, 4);
136 | size += p.print(response ? " R " : " ");
137 | size += printHex(p, command, 2);
138 | size += p.print(" ");
139 | size += printHex(p, length, 1);
140 |
141 | for (int i = 0; i < length; i++) {
142 | size += p.print(" ");
143 | size += printHex(p, data[i], 2);
144 | }
145 |
146 | return size;
147 | }
148 |
149 | boolean TrackMessage::parseFrom(String &s) {
150 | boolean result = true;
151 |
152 | clear();
153 |
154 | if (s.length() < 11) {
155 | return false;
156 | }
157 |
158 | hash = parseHex(s, 0, 4, &result);
159 | response = s.charAt(5) != ' ';
160 | command = parseHex(s, 7, 9, &result);
161 | length = parseHex(s, 10, 11, &result);
162 |
163 | if (length > 8) {
164 | return false;
165 | }
166 |
167 | if (s.length() < 11 + 3 * length) {
168 | return false;
169 | }
170 |
171 | for (int i = 0; i < length; i++) {
172 | data[i] = parseHex(s, 12 + 3 * i, 12 + 3 * i + 2, &result);
173 | }
174 |
175 | return result;
176 | }
177 |
178 | // ===================================================================
179 | // === TrackController ===============================================
180 | // ===================================================================
181 |
182 | TrackController::TrackController() {
183 | if (mDebug) {
184 | Serial.println(F("### Creating controller"));
185 | }
186 |
187 | init(0, false, false);
188 | }
189 |
190 | TrackController::TrackController(word hash, boolean debug) {
191 | if (mDebug) {
192 | Serial.println(F("### Creating controller"));
193 | }
194 |
195 | init(hash, debug, false);
196 | }
197 |
198 | TrackController::~TrackController() {
199 | if (mDebug) {
200 | Serial.println(F("### Destroying controller"));
201 | }
202 |
203 | end();
204 | }
205 |
206 | void TrackController::init(word hash, boolean debug, boolean loopback) {
207 | mHash = hash;
208 | mDebug = debug;
209 | mLoopback = loopback;
210 | }
211 |
212 | word TrackController::getHash() {
213 | return mHash;
214 | }
215 |
216 | boolean TrackController::isDebug() {
217 | return mDebug;
218 | }
219 |
220 | boolean TrackController::isLoopback() {
221 | return mLoopback;
222 | }
223 |
224 | void TrackController::begin() {
225 | // Even if we don't use the real SS pin
226 | // on all boards, it must be set to out,
227 | // otherwise SPI might switch to slave
228 | // and we just hang. Do not delete!
229 | pinMode(SS, OUTPUT);
230 |
231 | attachInterrupt(CAN_INT, enqueue, LOW);
232 |
233 | if (!can_init(5, mLoopback)) {
234 | Serial.println(F("!?! Init error"));
235 | Serial.println(F("!?! Emergency stop"));
236 | for (;;);
237 | }
238 |
239 | delay(500);
240 |
241 | if (!mLoopback) {
242 | TrackMessage message;
243 |
244 | message.clear();
245 | message.command = 0x1b;
246 | message.length = 0x05;
247 | message.data[4] = 0x11;
248 |
249 | sendMessage(message);
250 | }
251 |
252 | if (mHash == 0) {
253 | generateHash();
254 | }
255 |
256 | }
257 |
258 | void TrackController::generateHash() {
259 | TrackMessage message;
260 |
261 | boolean ok = false;
262 |
263 | while(!ok) {
264 | mHash = random(0x10000) & 0xff7f | 0x0300;
265 |
266 | if (mDebug) {
267 | Serial.print(F("### Trying new hash "));
268 | printHex(Serial, mHash, 4);
269 | Serial.println();
270 | }
271 |
272 | message.clear();
273 | message.command = 0x18;
274 |
275 | sendMessage(message);
276 |
277 | delay(500);
278 |
279 | ok = true;
280 | while(receiveMessage(message)) {
281 | if (message.hash == mHash) {
282 | ok = false;
283 | }
284 | }
285 | }
286 |
287 | if (mDebug) {
288 | Serial.println(F("### New hash looks good"));
289 | }
290 | }
291 |
292 | // end - no interrupts
293 |
294 | void TrackController::end() {
295 | detachInterrupt(CAN_INT);
296 |
297 | can_t t;
298 |
299 | boolean b = dequeue(&t);
300 | while (b) {
301 | b = dequeue(&t);
302 | }
303 | }
304 |
305 | boolean TrackController::sendMessage(TrackMessage &message) {
306 | can_t can;
307 |
308 | message.hash = mHash;
309 |
310 | can.id = ((uint32_t)message.command) << 17 | (uint32_t)message.hash;
311 | can.flags.extended = 1;
312 | can.flags.rtr = 0;
313 | can.length = message.length;
314 |
315 | for (int i = 0; i < message.length; i++) {
316 | can.data[i] = message.data[i];
317 | }
318 |
319 | if (mDebug) {
320 | Serial.print("==> ");
321 | Serial.println(message);
322 | }
323 |
324 | return can_send_message(&can);
325 | }
326 |
327 | boolean TrackController::receiveMessage(TrackMessage &message) {
328 | can_t can;
329 |
330 | boolean result = dequeue(&can);
331 | // boolean result = /* can_check_message() && */ can_get_message(&can);
332 |
333 | if (result) {
334 |
335 |
336 | // boolean result = /* can_check_message() && */ can_get_message(&can);
337 |
338 | // if (result) {
339 | /*
340 | if (mDebug) {
341 |
342 | Serial.print("ID :");
343 | Serial.println(can.id, HEX);
344 | Serial.print("EXIDE:");
345 | Serial.println(can.flags.extended, HEX);
346 | Serial.print("DLC:");
347 | Serial.println(can.length, HEX);
348 | Serial.print("DATA:");
349 |
350 | for (int i = 0; i < can.length; i++) {
351 | printHex(can.data[i]);
352 | }
353 |
354 | Serial.println();
355 | }
356 | */
357 | message.clear();
358 | message.command = (can.id >> 17) & 0xff;
359 | message.hash = can.id & 0xffff;
360 | message.response = bitRead(can.id, 16) || mLoopback;
361 | message.length = can.length;
362 |
363 | for (int i = 0; i < can.length; i++) {
364 | message.data[i] = can.data[i];
365 | }
366 |
367 | if (mDebug) {
368 | Serial.print("<== ");
369 | Serial.println(message);
370 | }
371 | }
372 |
373 | return result;
374 | }
375 |
376 | boolean TrackController::exchangeMessage(TrackMessage &out, TrackMessage &in, word timeout) {
377 | int command = out.command;
378 |
379 | if (!sendMessage(out)) {
380 | if (mDebug) {
381 | Serial.println(F("!?! Send error"));
382 | Serial.println(F("!?! Emergency stop"));
383 | setPower(false);
384 | for (;;);
385 | }
386 | }
387 |
388 | ulong time = millis();
389 |
390 | // TrackMessage response;
391 |
392 | while (millis() < time + timeout) {
393 | in.clear();
394 | boolean result = receiveMessage(in);
395 |
396 | if (result && in.command == command && in.response) {
397 | return true;
398 | }
399 | }
400 |
401 | if (mDebug) {
402 | Serial.println(F("!?! Receive timeout"));
403 | }
404 |
405 | return false;
406 | }
407 |
408 | boolean TrackController::setPower(boolean power) {
409 | TrackMessage message;
410 |
411 | if (power) {
412 | message.clear();
413 | message.command = 0x00;
414 | message.length = 0x07;
415 | message.data[4] = 9;
416 | message.data[6] = 0xD;
417 |
418 | exchangeMessage(message, message, 1000);
419 |
420 | message.clear();
421 | message.command = 0x00;
422 | message.length = 0x06;
423 | message.data[4] = 8;
424 | message.data[5] = 7;
425 |
426 | exchangeMessage(message, message, 1000);
427 | }
428 |
429 | message.clear();
430 | message.command = 0x00;
431 | message.length = 0x05;
432 | message.data[4] = power ? 0x01 : 0x00;
433 |
434 | return exchangeMessage(message, message, 1000);
435 | }
436 |
437 | boolean TrackController::setLocoDirection(word address, byte direction) {
438 | TrackMessage message;
439 |
440 | message.clear();
441 | message.command = 0x00;
442 | message.length = 0x05;
443 | message.data[2] = highByte(address);
444 | message.data[3] = lowByte(address);
445 | message.data[4] = 0x03;
446 |
447 | exchangeMessage(message, message, 1000);
448 |
449 | message.clear();
450 | message.command = 0x05;
451 | message.length = 0x05;
452 | message.data[2] = highByte(address);
453 | message.data[3] = lowByte(address);
454 | message.data[4] = direction;
455 |
456 | return exchangeMessage(message, message, 1000);
457 | }
458 |
459 | boolean TrackController::toggleLocoDirection(word address) {
460 | return setLocoDirection(address, DIR_CHANGE);
461 | }
462 |
463 | boolean TrackController::setLocoSpeed(word address, word speed) {
464 | TrackMessage message;
465 |
466 | message.clear();
467 | message.command = 0x04;
468 | message.length = 0x06;
469 | message.data[2] = highByte(address);
470 | message.data[3] = lowByte(address);
471 | message.data[4] = highByte(speed);
472 | message.data[5] = lowByte(speed);
473 |
474 | return exchangeMessage(message, message, 1000);
475 | }
476 |
477 | boolean TrackController::accelerateLoco(word address) {
478 | word speed;
479 |
480 | if (getLocoSpeed(address, &speed)) {
481 | speed += 77;
482 | if (speed > 1023) {
483 | speed = 1023;
484 | }
485 |
486 | return setLocoSpeed(address, speed);
487 | }
488 |
489 | return false;
490 | }
491 |
492 | boolean TrackController::decelerateLoco(word address) {
493 | word speed;
494 |
495 | if (getLocoSpeed(address, &speed)) {
496 | speed -= 77;
497 | if (speed > 32767) {
498 | speed = 0;
499 | }
500 |
501 | return setLocoSpeed(address, speed);
502 | }
503 |
504 | return false;
505 | }
506 |
507 | boolean TrackController::setLocoFunction(word address, byte function, byte power) {
508 | TrackMessage message;
509 |
510 | message.clear();
511 | message.command = 0x06;
512 | message.length = 0x06;
513 | message.data[2] = highByte(address);
514 | message.data[3] = lowByte(address);
515 | message.data[4] = function;
516 | message.data[5] = power;
517 |
518 | return exchangeMessage(message, message, 1000);
519 | }
520 |
521 | boolean TrackController::toggleLocoFunction(word address, byte function) {
522 | byte power;
523 | if (getLocoFunction(address, function, &power)) {
524 | return setLocoFunction(address, function, power ? 0 : 1);
525 | }
526 |
527 | return false;
528 | }
529 |
530 | boolean TrackController::setAccessory(word address, byte position, byte power,
531 | word time) {
532 | TrackMessage message;
533 |
534 | message.clear();
535 | message.command = 0x0b;
536 | message.length = 0x06;
537 | message.data[2] = highByte(address);
538 | message.data[3] = lowByte(address);
539 | message.data[4] = position;
540 | message.data[5] = power;
541 |
542 | exchangeMessage(message, message, 1000);
543 |
544 | if (time != 0) {
545 | delay(time);
546 |
547 | message.clear();
548 | message.command = 0x0b;
549 | message.length = 0x06;
550 | message.data[2] = highByte(address);
551 | message.data[3] = lowByte(address);
552 | message.data[4] = position;
553 |
554 | exchangeMessage(message, message, 1000);
555 | }
556 |
557 | return true;
558 | }
559 |
560 | boolean TrackController::setTurnout(word address, boolean straight) {
561 | return setAccessory(address, straight ? ACC_STRAIGHT : ACC_ROUND, 1, 1000);
562 | }
563 |
564 | boolean TrackController::getLocoDirection(word address, byte *direction) {
565 | TrackMessage message;
566 |
567 | message.clear();
568 | message.command = 0x05;
569 | message.length = 0x04;
570 | message.data[2] = highByte(address);
571 | message.data[3] = lowByte(address);
572 |
573 | if (exchangeMessage(message, message, 1000)) {
574 | direction[0] = message.data[4];
575 | return true;
576 | } else {
577 | return false;
578 | }
579 | }
580 |
581 | boolean TrackController::getLocoSpeed(word address, word *speed) {
582 | TrackMessage message;
583 |
584 | message.clear();
585 | message.command = 0x04;
586 | message.length = 0x04;
587 | message.data[2] = highByte(address);
588 | message.data[3] = lowByte(address);
589 |
590 | if (exchangeMessage(message, message, 1000)) {
591 | speed[0] = word(message.data[4], message.data[5]);
592 | return true;
593 | } else {
594 | return false;
595 | }
596 | }
597 |
598 | boolean TrackController::getLocoFunction(word address, byte function,
599 | byte *power) {
600 | TrackMessage message;
601 |
602 | message.clear();
603 | message.command = 0x06;
604 | message.length = 0x05;
605 | message.data[2] = highByte(address);
606 | message.data[3] = lowByte(address);
607 | message.data[4] = function;
608 |
609 | if (exchangeMessage(message, message, 1000)) {
610 | power[0] = message.data[5];
611 | return true;
612 | } else {
613 | return false;
614 | }
615 | }
616 |
617 | boolean TrackController::getAccessory(word address, byte *position, byte *power) {
618 | TrackMessage message;
619 |
620 | message.clear();
621 | message.command = 0x0b;
622 | message.length = 0x04;
623 | message.data[2] = highByte(address);
624 | message.data[3] = lowByte(address);
625 |
626 | if (exchangeMessage(message, message, 1000)) {
627 | position[0] = message.data[4];
628 | power[0] = message.data[5];
629 | return true;
630 | } else {
631 | return false;
632 | }
633 | }
634 |
635 | boolean TrackController::writeConfig(word address, word number, byte value) {
636 | TrackMessage message;
637 |
638 | message.clear();
639 | message.command = 0x08;
640 | message.length = 0x08;
641 | message.data[2] = highByte(address);
642 | message.data[3] = lowByte(address);
643 | message.data[4] = highByte(number);
644 | message.data[5] = lowByte(number);
645 | message.data[6] = value;
646 |
647 | return exchangeMessage(message, message, 10000);
648 | }
649 |
650 | boolean TrackController::readConfig(word address, word number, byte *value) {
651 | TrackMessage message;
652 |
653 | message.clear();
654 | message.command = 0x07;
655 | message.length = 0x07;
656 | message.data[2] = highByte(address);
657 | message.data[3] = lowByte(address);
658 | message.data[4] = highByte(number);
659 | message.data[5] = lowByte(number);
660 | message.data[6] = 0x01;
661 |
662 | if (exchangeMessage(message, message, 10000)) {
663 | value[0] = message.data[6];
664 | return true;
665 | } else {
666 | return false;
667 | }
668 | }
669 |
670 | boolean TrackController::getVersion(byte *high, byte *low) {
671 | boolean result = false;
672 |
673 | TrackMessage message;
674 |
675 | message.clear();
676 | message.command = 0x18;
677 |
678 | sendMessage(message);
679 |
680 | delay(500);
681 |
682 | while(receiveMessage(message)) {
683 | if (message.command = 0x18 && message.data[6] == 0x00 && message.data[7] == 0x10) {
684 | (*high) = message.data[4];
685 | (*low) = message.data[5];
686 | result = true;
687 | }
688 | }
689 |
690 | return result;
691 | }
692 |
693 | // ===================================================================
694 | // === TrackControllerInfrared =======================================
695 | // ===================================================================
696 |
697 | #define ADDR_LOCO_1 0b11000
698 | #define ADDR_LOCO_2 0b11001
699 | #define ADDR_LOCO_3 0b11011
700 | #define ADDR_LOCO_4 0b11100
701 | #define ADDR_TURNOUT_A 0b01110
702 | #define ADDR_TURNOUT_B 0b01111
703 |
704 | #define CMD_FUNCTION 0b1010000
705 | #define CMD_FASTER 0b0010000
706 | #define CMD_SLOWER 0b0010001
707 | #define CMD_DIRECTION 0b0001101
708 | #define CMD_POWER_OFF 0b0001100
709 | #define CMD_POWER_ON 0b0001110
710 |
711 | static word locoBits[] = { ADDR_LOCO_1, ADDR_LOCO_2, ADDR_LOCO_3, ADDR_LOCO_4 };
712 |
713 | TrackControllerInfrared::TrackControllerInfrared() {
714 | mPower = true;
715 |
716 | for (int i = 0; i < 2; i++) {
717 | for (int j = 1; j <= 4; j++) {
718 | toggleLocoDirection(j);
719 | }
720 | }
721 |
722 | setPower(false);
723 | }
724 |
725 | boolean TrackControllerInfrared::sendMessage(word address, word command) {
726 | if (mPower) {
727 | int transmission = (address << 6) | (command & 0x3f);
728 |
729 | if (command >= 0x40) {
730 | sendRC5(mToggle | transmission, 12, true);
731 | } else {
732 | sendRC5(mToggle | transmission, 12, false);
733 | }
734 |
735 | mToggle ^= 1 << 11;
736 |
737 | delay(50);
738 |
739 | return true;
740 | }
741 |
742 | return false;
743 | }
744 |
745 | boolean TrackControllerInfrared::accelerateLoco(int loco) {
746 | if (loco >= 1 && loco <= 4) {
747 | return sendMessage(locoBits[loco - 1], CMD_FASTER);
748 | }
749 |
750 | return false;
751 | }
752 |
753 | boolean TrackControllerInfrared::decelerateLoco(int loco) {
754 | if (loco >= 1 && loco <= 4) {
755 | return sendMessage(locoBits[loco - 1], CMD_SLOWER);
756 | }
757 |
758 | return false;
759 | }
760 |
761 | boolean TrackControllerInfrared::toggleLocoDirection(int loco) {
762 | if (loco >= 1 && loco <= 4) {
763 | return sendMessage(locoBits[loco - 1], CMD_DIRECTION);
764 | }
765 |
766 | return false;
767 | }
768 |
769 | boolean TrackControllerInfrared::toggleLocoFunction(int loco, int function) {
770 | if (loco >= 1 && loco <= 4 && function >= 0 && function <= 8) {
771 | return sendMessage(locoBits[loco - 1], CMD_FUNCTION | function);
772 | }
773 |
774 | return false;
775 | }
776 |
777 | boolean TrackControllerInfrared::setPower(boolean power) {
778 | if (mPower != power) {
779 | mPower = true;
780 | sendMessage(ADDR_LOCO_1, power ? CMD_POWER_ON : CMD_POWER_OFF);
781 | mPower = power;
782 |
783 | return true;
784 | }
785 |
786 | return false;
787 | }
788 |
789 | boolean TrackControllerInfrared::setTurnout(int turnout, boolean through) {
790 | if (turnout >= 1 && turnout <= 8) {
791 | word command = (turnout - 1) | (through ? 0x20 : 0x00);
792 | return sendMessage(ADDR_TURNOUT_A, command);
793 | } else if (turnout >= 9 && turnout <= 16) {
794 | word command = (turnout - 9) | (through ? 0x20 : 0x00);
795 | return sendMessage(ADDR_TURNOUT_B, command);
796 | }
797 |
798 | return false;
799 | }
800 |
801 | // ===================================================================
802 | // === TrackReporterS88 ==============================================
803 | // ===================================================================
804 |
805 | const int DATA = A0;
806 | const int CLOCK = 2;
807 | const int LOAD = 3;
808 | const int RESET = 4;
809 |
810 | const int TIME = 50;
811 |
812 | TrackReporterS88::TrackReporterS88(int modules) {
813 | mSize = modules;
814 |
815 | // pinMode(DATA, INPUT);
816 | pinMode(CLOCK, OUTPUT);
817 | pinMode(LOAD, OUTPUT);
818 | pinMode(RESET, OUTPUT);
819 | }
820 |
821 | void TrackReporterS88::refresh() {
822 | int myByte = 0;
823 | int myBit = 0;
824 |
825 | for (int i = 0; i <= sizeof(mSwitches); i++) {
826 | mSwitches[i] = 0;
827 | }
828 |
829 | digitalWrite(LOAD, HIGH);
830 | delayMicroseconds( TIME);
831 | digitalWrite(CLOCK, HIGH);
832 | delayMicroseconds(TIME);
833 | digitalWrite(CLOCK, LOW);
834 | delayMicroseconds(TIME);
835 | digitalWrite(RESET, HIGH);
836 | delayMicroseconds(TIME);
837 | digitalWrite(RESET, LOW);
838 | delayMicroseconds(TIME);
839 | digitalWrite(LOAD, LOW);
840 |
841 | delayMicroseconds(TIME / 2);
842 |
843 | bitWrite(mSwitches[myByte], myBit++, digitalRead(DATA));
844 | delayMicroseconds(TIME / 2);
845 |
846 | for (int i = 1; i < 16 * mSize; i++) {
847 | digitalWrite(CLOCK, HIGH);
848 | delayMicroseconds(TIME);
849 | digitalWrite(CLOCK, LOW);
850 |
851 | delayMicroseconds(TIME / 2);
852 | bitWrite(mSwitches[myByte], myBit++, digitalRead(DATA));
853 |
854 | if (myBit == 8) {
855 | myByte++;
856 | myBit = 0;
857 | }
858 |
859 | delayMicroseconds(TIME / 2);
860 | }
861 | }
862 |
863 | boolean TrackReporterS88::getValue(int index) {
864 | index--;
865 | return bitRead(mSwitches[index / 8], index % 8);
866 | }
867 |
868 | // ===================================================================
869 | // === TrackReporterIOX ==============================================
870 | // ===================================================================
871 |
872 |
873 | void SPI_begin() {
874 | digitalWrite(SS, HIGH);
875 | pinMode(SS, OUTPUT);
876 |
877 | SPCR |= _BV(MSTR);
878 | SPCR |= _BV(SPE);
879 |
880 | pinMode(SCK, OUTPUT);
881 | pinMode(MOSI, OUTPUT);
882 | }
883 |
884 | byte SPI_transfer(byte _data) {
885 | SPDR = _data;
886 | while (!(SPSR & _BV(SPIF)))
887 | ;
888 | return SPDR;
889 | }
890 |
891 | byte ioxCount;
892 |
893 | byte ioxSwitches[16];
894 |
895 | byte ioxSwitches2[16];
896 |
897 | unsigned int readRegister(byte address, byte index) {
898 | digitalWrite(6, LOW);
899 |
900 | SPI_transfer(65 | (address << 1));
901 | SPI_transfer(index);
902 | unsigned int result = SPI_transfer(255);
903 |
904 | digitalWrite(6, HIGH);
905 |
906 | return result;
907 | }
908 |
909 | void writeRegister(byte address, byte index, byte value) {
910 | digitalWrite(6, LOW);
911 |
912 | SPI_transfer(64 | (address << 1));
913 | SPI_transfer(index);
914 | SPI_transfer(value);
915 |
916 | digitalWrite(6, HIGH);
917 | }
918 |
919 | void handleInterrupt0() {
920 | noInterrupts();
921 |
922 | for (int i = 0; i < ioxCount; i++) {
923 | ioxSwitches[2 * i] = readRegister(i, 9);
924 | ioxSwitches[2 * i + 1] = readRegister(16 + i, 9);
925 |
926 | ioxSwitches2[2 * i] |= ioxSwitches[2 * i];
927 | ioxSwitches2[2 * i + 1] |= ioxSwitches[2 * i + 1];
928 | }
929 |
930 | interrupts();
931 | }
932 |
933 | TrackReporterIOX::TrackReporterIOX(int modules) {
934 | mCount = modules;
935 |
936 | ioxCount = modules;
937 |
938 | digitalWrite(6, HIGH);
939 | pinMode(6, OUTPUT);
940 |
941 | SPI_begin();
942 |
943 | noInterrupts();
944 | for (int i = 0; i < mCount; i++) {
945 | writeRegister(i, 5, 76); // Open drain, banks in case of 16 bit
946 |
947 | for (int j = 0; j <= 16; j += 16) {
948 | writeRegister(i, j + 0, 255); // IODIR: All GPIOs are inputs
949 | writeRegister(i, j + 1, 255); // IOPOL: GND means locial 1
950 | writeRegister(i, j + 2, 255); // GPINTEN: All interrupts enabled
951 | writeRegister(i, j + 3, 0); // DEFVAL: Compare default value
952 | //writeRegister(i, j + 4, 255); // INTCON: Compare against default value
953 | writeRegister(i, j + 6, 255); // Pull-up resistors
954 | readRegister(i, j + 9);
955 | }
956 | }
957 |
958 | attachInterrupt(1, &handleInterrupt0, LOW);
959 | interrupts();
960 | }
961 |
962 | TrackReporterIOX::~TrackReporterIOX() {
963 | detachInterrupt(1);
964 | }
965 |
966 | void TrackReporterIOX::refresh() {
967 | noInterrupts();
968 |
969 | for (int i = 0; i < mCount; i++) {
970 | mSwitches[i] = ioxSwitches2[i];
971 | ioxSwitches2[i] = ioxSwitches[i];
972 | }
973 |
974 | interrupts();
975 | }
976 |
977 | boolean TrackReporterIOX::getValue(int index) {
978 | index--;
979 | return bitRead(mSwitches[index / 8], index % 8);
980 | }
981 |
--------------------------------------------------------------------------------