summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2023-10-12 22:16:08 +0200
committermanuel <manuel@mausz.at>2023-10-12 22:16:08 +0200
commitad98a69759bb827e00b4c458c285862f0b48f1d4 (patch)
treee429f3a3260e9a4e357fd4a1a5b1a02c6aae8ad2
parent68c812fe8c690b8a24637076f83537d6a2e94f32 (diff)
downloadarduino-ad98a69759bb827e00b4c458c285862f0b48f1d4.tar.gz
arduino-ad98a69759bb827e00b4c458c285862f0b48f1d4.tar.bz2
arduino-ad98a69759bb827e00b4c458c285862f0b48f1d4.zip
add duplo train remote
-rw-r--r--duplo_train/.gitignore5
-rw-r--r--duplo_train/platformio.ini26
-rw-r--r--duplo_train/src/main.cpp349
-rw-r--r--duplo_train/src/myhub.h177
4 files changed, 557 insertions, 0 deletions
diff --git a/duplo_train/.gitignore b/duplo_train/.gitignore
new file mode 100644
index 0000000..89cc49c
--- /dev/null
+++ b/duplo_train/.gitignore
@@ -0,0 +1,5 @@
1.pio
2.vscode/.browse.c_cpp.db*
3.vscode/c_cpp_properties.json
4.vscode/launch.json
5.vscode/ipch
diff --git a/duplo_train/platformio.ini b/duplo_train/platformio.ini
new file mode 100644
index 0000000..31a8788
--- /dev/null
+++ b/duplo_train/platformio.ini
@@ -0,0 +1,26 @@
1; PlatformIO Project Configuration File
2;
3; Build options: build flags, source filter
4; Upload options: custom upload port, speed and extra flags
5; Library options: dependencies, extra library storages
6; Advanced options: extra scripting
7;
8; Please visit documentation for the other options and examples
9; https://docs.platformio.org/page/projectconf.html
10
11[env]
12platform = espressif32
13board = wemos_d1_mini32
14framework = arduino
15lib_deps =
16 NimBLE-Arduino
17 Legoino
18 Bounce2
19monitor_speed = 115200
20;build_flags = -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
21
22[env:wifi_toggle]
23build_flags = -D WIFI_TOGGLE
24
25[platformio]
26lib_dir=/home/manuel/coding/Arduino/libraries \ No newline at end of file
diff --git a/duplo_train/src/main.cpp b/duplo_train/src/main.cpp
new file mode 100644
index 0000000..0922c6e
--- /dev/null
+++ b/duplo_train/src/main.cpp
@@ -0,0 +1,349 @@
1
2#include <Arduino.h>
3#include <Bounce2.h>
4#include <WiFi.h>
5
6#include <esp_wifi.h>
7#include <esp_bt.h>
8#include <soc/soc.h>
9#include <soc/rtc_cntl_reg.h>
10
11#include "myhub.h"
12
13const uint8_t BUTTON_BASE_1 = 25;
14const uint8_t BUTTON_BASE_2 = 22;
15const uint8_t BUTTON_BASE_3 = 26;
16const uint8_t BUTTON_BASE_4 = 18;
17const uint8_t BUTTON_BASE_5 = 33;
18const uint8_t BUTTON_BASE_6 = 5;
19const uint8_t BUTTON_BASE_7 = 19;
20const uint8_t BUTTON_BASE_8 = 23;
21const uint8_t BUTTON_HANDLE_1 = 17;
22const uint8_t BUTTON_HANDLE_2 = 16;
23const uint8_t POTI_THROTTLE = 32;
24
25const int idle_shutdown = 10 * 60 * 1000;
26unsigned long last_scan_start = 0;
27
28Bounce2::Button button_base1 = Bounce2::Button();
29Bounce2::Button button_base2 = Bounce2::Button();
30Bounce2::Button button_base3 = Bounce2::Button();
31Bounce2::Button button_base4 = Bounce2::Button();
32Bounce2::Button button_base5 = Bounce2::Button();
33Bounce2::Button button_base6 = Bounce2::Button();
34Bounce2::Button button_base7 = Bounce2::Button();
35Bounce2::Button button_base8 = Bounce2::Button();
36Bounce2::Button button_handle1 = Bounce2::Button();
37Bounce2::Button button_handle2 = Bounce2::Button();
38
39MyHub myHub;
40
41void color_sensor_callback(void *hub, byte port_number,
42 DeviceType device_type, uint8_t *data)
43{
44 MyHub *myHub = (MyHub *)hub;
45
46 if (device_type == DeviceType::DUPLO_TRAIN_BASE_COLOR_SENSOR)
47 {
48 int color = myHub->parseColor(data);
49 Serial.print("Color: ");
50 Serial.println(color);
51 }
52}
53
54void speedometer_sensor_callback(void *hub, byte port_number,
55 DeviceType device_type, uint8_t *data)
56{
57 MyHub *myHub = (MyHub *)hub;
58
59 if (device_type == DeviceType::DUPLO_TRAIN_BASE_SPEEDOMETER)
60 {
61 int current_speed = myHub->parseSpeedometer(data);
62 Serial.print("speedometer="); Serial.println(current_speed);
63 if (current_speed == 0)
64 {
65 //TODO child has fun with "crashing" the train into things
66 // and the train reacts with the break sound
67 //myHub->stopMotor();
68 myHub->stopMotorWithBreak();
69 }
70 else if (myHub->isMotorStopped())
71 {
72 if (current_speed > 10)
73 myHub->setMotorSpeedLevel(1);
74 else if (current_speed < -10)
75 myHub->setMotorSpeedLevel(-1);
76 }
77 }
78}
79
80#ifdef WIFI_TOGGLE
81enum wifi_enable_state
82 : uint8_t
83{
84 Enable = 0,
85 Disable = 1,
86 None = 2,
87};
88
89hw_timer_t *timer = NULL;
90
91volatile wifi_enable_state wifi_enable = wifi_enable_state::None;
92void IRAM_ATTR on_timer()
93{
94 static volatile int cnt = 0;
95 cnt = (cnt < 15) ? cnt + 1 : 0;
96 if (cnt == 0)
97 wifi_enable = wifi_enable_state::Enable;
98 else if (cnt == 1)
99 wifi_enable = wifi_enable_state::Disable;
100}
101#endif
102
103void controller_deep_sleep()
104{
105 Serial.print("Controller deep sleep enabled");
106
107#ifdef WIFI_TOGGLE
108 WiFi.disconnect(true);
109 WiFi.mode(WIFI_OFF);
110 esp_wifi_stop();
111#endif
112
113 NimBLEDevice::getScan()->stop();
114 NimBLEDevice::deinit();
115 esp_bt_controller_disable();
116
117 // disable brownout detector
118 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
119 //adc_power_release();
120 esp_deep_sleep_start();
121}
122
123void setup()
124{
125 // disable brownout detector
126 // problems with the usb battery?
127 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
128
129 Serial.begin(115200);
130 pinMode(BUILTIN_LED, OUTPUT);
131
132 analogReadResolution(12); //12 bits
133 analogSetAttenuation(ADC_0db); //For all pins
134 analogSetPinAttenuation(POTI_THROTTLE, ADC_11db); //11db attenuation
135
136 button_base1.attach(BUTTON_BASE_1, INPUT_PULLUP);
137 button_base2.attach(BUTTON_BASE_2, INPUT_PULLUP);
138 button_base3.attach(BUTTON_BASE_3, INPUT_PULLUP);
139 button_base4.attach(BUTTON_BASE_4, INPUT_PULLUP);
140 button_base5.attach(BUTTON_BASE_5, INPUT_PULLUP);
141 button_base6.attach(BUTTON_BASE_6, INPUT_PULLUP);
142 button_base7.attach(BUTTON_BASE_7, INPUT_PULLUP);
143 button_base8.attach(BUTTON_BASE_8, INPUT_PULLUP);
144 button_handle1.attach(BUTTON_HANDLE_1, INPUT_PULLUP);
145 button_handle2.attach(BUTTON_HANDLE_2, INPUT_PULLUP);
146
147 button_base1.setPressedState(LOW);
148 button_base2.setPressedState(LOW);
149 button_base3.setPressedState(LOW);
150 button_base4.setPressedState(LOW);
151 button_base5.setPressedState(LOW);
152 button_base6.setPressedState(LOW);
153 button_base7.setPressedState(LOW);
154 button_base8.setPressedState(LOW);
155 button_handle1.setPressedState(LOW);
156 button_handle2.setPressedState(LOW);
157
158 last_scan_start = 0;
159 myHub.init();
160
161 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_DEFAULT);
162 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL0);
163 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL1);
164 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL2);
165 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL3);
166 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL4);
167 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL5);
168 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL6);
169 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL7);
170 NimBLEDevice::setPower(ESP_PWR_LVL_P9, ESP_BLE_PWR_TYPE_CONN_HDL8);
171
172#ifdef WIFI_TOGGLE
173 timer = timerBegin(0, 80, true); // 1 tick = 1/(80MHZ) => x/80 = 1us
174 timerAttachInterrupt(timer, &on_timer, true);
175 timerAlarmWrite(timer, 1000000, true); // 1000000 * 1us = 1sec
176 timerAlarmEnable(timer);
177#endif
178}
179
180void loop()
181{
182 /*
183 * TODO
184 * - color sensor
185 * - throttle
186 */
187 if (!myHub.isConnecting() && !myHub.isConnected())
188 {
189 if (last_scan_start == 0)
190 {
191 delay(1000); // avoid fast-reconnect loops
192 last_scan_start = millis();
193 }
194 if (millis() - last_scan_start > idle_shutdown)
195 (void)controller_deep_sleep(); // never returns
196
197 if (!NimBLEDevice::getScan()->isScanning())
198 {
199 myHub.init();
200 Serial.println("BLE is scanning...");
201 }
202 }
203 else if (myHub.isConnecting())
204 {
205 Serial.println("Duplo Hub found");
206 myHub.connectHub();
207 if (myHub.isConnected())
208 {
209 last_scan_start = 0;
210
211 Serial.println("Connected to Duplo Hub");
212 myHub.onConnect();
213 delay(200);
214
215 // connect speed sensor and activate it for updates
216 myHub.activatePortDevice((byte)DuploTrainHubPort::SPEEDOMETER,
217 speedometer_sensor_callback);
218 delay(200);
219
220 // activate light
221 myHub.activateRgbLight();
222 delay(200);
223
224 // activate speaker
225 myHub.activateBaseSpeaker();
226 delay(200);
227 }
228 else
229 {
230 Serial.println("Failed to connect to Duplo Hub");
231 return;
232 }
233 }
234 else if (myHub.isConnected())
235 {
236 button_base1.update();
237 if (button_base1.pressed())
238 {
239 Serial.println("Button1 pressed");
240 myHub.playSound((byte)DuploTrainBaseSound::STEAM);
241 }
242
243 button_base2.update();
244 if (button_base2.pressed())
245 {
246 Serial.println("Button2 - brake pressed");
247 myHub.stopMotorWithBreak();
248 }
249
250 button_base3.update();
251 if (button_base3.pressed())
252 {
253 Serial.println("Button3 - light color pressed");
254 myHub.cycleLightColor();
255 }
256
257 button_base4.update();
258 if (button_base4.pressed())
259 {
260 Serial.println("Button4 - light toggle pressed");
261 myHub.toggleLight();
262 }
263
264 button_base5.update();
265 if (button_base5.pressed())
266 {
267 Serial.println("Button5 - refill pressed");
268 myHub.playSound((byte)DuploTrainBaseSound::WATER_REFILL);
269 }
270
271 button_base6.update();
272 if (button_base6.pressed())
273 {
274 Serial.println("Button6 - horn pressed");
275 myHub.playSound((byte)DuploTrainBaseSound::HORN);
276 }
277
278 button_base7.update();
279 if (button_base7.pressed())
280 {
281 Serial.println("Button7 - brake pressed");
282 myHub.stopMotorWithBreak();
283 }
284
285 button_base8.update();
286 if (button_base8.pressed())
287 {
288 Serial.println("Button8 pressed");
289 myHub.playSound((byte)DuploTrainBaseSound::STATION_DEPARTURE);
290 }
291
292 button_handle1.update();
293 if (button_handle1.pressed())
294 {
295 Serial.println("Button Handle1 - speed++");
296 myHub.incrMotorSpeed();
297 }
298
299 button_handle2.update();
300 if (button_handle2.pressed())
301 {
302 Serial.println("Button Handle2 - speed--");
303 myHub.decrMotorSpeed();
304 }
305
306 if (myHub.isMotorStopped() && millis() - myHub.millisLastCommand() > idle_shutdown)
307 {
308 myHub.shutDownHub();
309 delay(500);
310 myHub.shutDownHub();
311 delay(500);
312
313 (void)controller_deep_sleep(); // never returns
314 }
315
316#if 0
317 int potValue = analogRead(POTI_THROTTLE);
318 delay(50);
319 int potValue2 = analogRead(POTI_THROTTLE);
320 if (potValue == potValue2) {
321 //if (potValue <= )
322 Serial.print(" pot value "); Serial.println(potValue);
323 }
324 int speedValue = map(potValue, 0, 4095, -2, 2);
325 if (potValue == potValue2 && speed != speedValue)
326 {
327 Serial.print("old speed "); Serial.print(speed);
328 Serial.print(" new speed "); Serial.print(speedValue);
329 Serial.print("New speed "); Serial.println(speedValue * 35);
330 //myHub.setBasicMotorSpeed(motorPort, speedValue * 35);
331 speed = speedValue;
332 }
333 delay(50);
334#endif
335 }
336
337#ifdef WIFI_TOGGLE
338 if (wifi_enable != wifi_enable_state::None)
339 {
340 timerAlarmDisable(timer);
341 Serial.print("new mode ");
342 Serial.println((wifi_enable == wifi_enable_state::Enable) ? "WIFI_STA" : "WIFI_OFF");
343 WiFi.mode((wifi_enable == wifi_enable_state::Enable) ? WIFI_STA : WIFI_OFF);
344 digitalWrite(BUILTIN_LED, (wifi_enable == wifi_enable_state::Enable) ? HIGH : LOW);
345 wifi_enable = wifi_enable_state::None;
346 timerAlarmEnable(timer);
347 }
348#endif
349}
diff --git a/duplo_train/src/myhub.h b/duplo_train/src/myhub.h
new file mode 100644
index 0000000..2a3e690
--- /dev/null
+++ b/duplo_train/src/myhub.h
@@ -0,0 +1,177 @@
1#pragma once
2
3#include "Lpf2Hub.h"
4
5class MyHub
6 : public Lpf2Hub
7{
8public:
9 void activateBaseSpeaker()
10 {
11 byte setSoundMode[8] = { 0x41, (byte)DuploTrainHubPort::SPEAKER, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01 };
12 Lpf2Hub::WriteValue(setSoundMode, 8);
13 m_last_cmd = millis();
14 }
15
16 void playSound(byte sound)
17 {
18 byte playSound[6] = { 0x81, (byte)DuploTrainHubPort::SPEAKER, 0x11, 0x51, 0x01, sound };
19 Lpf2Hub::WriteValue(playSound, 6);
20 m_last_cmd = millis();
21 }
22
23 void activateRgbLight()
24 {
25 byte port = getPortForDeviceType((byte)DeviceType::HUB_LED);
26 byte setColorMode[8] = { 0x41, port, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00 };
27 Lpf2Hub::WriteValue(setColorMode, 8);
28 m_last_cmd = millis();
29 }
30
31 void setLedColor(Color color)
32 {
33 byte port = getPortForDeviceType((byte)DeviceType::HUB_LED);
34 byte setColor[6] = { 0x81, port, 0x11, 0x51, 0x00, color };
35 Lpf2Hub::WriteValue(setColor, 6);
36 m_last_cmd = millis();
37 }
38
39 void setMotorSpeedLevel(int speed_level)
40 {
41 if (speed_level > 3)
42 speed_level = 3;
43 else if (speed_level < -3)
44 speed_level = -3;
45 Serial.print("new speed="); Serial.println(speed_level);
46
47 int speed_pct = 0;
48 switch(speed_level)
49 {
50 case 3:
51 speed_pct = 70;
52 break;
53 case 2:
54 speed_pct = 50;
55 break;
56 case 1:
57 speed_pct = 30;
58 break;
59 case -1:
60 speed_pct = -30;
61 break;
62 case -2:
63 speed_pct = -50;
64 break;
65 case -3:
66 speed_pct = -70;
67 break;
68 default:
69 speed_pct = 0;
70 break;
71 }
72
73 m_speed_level = speed_level;
74 setBasicMotorSpeed(speed_pct);
75 if (abs(m_speed_level) == 1)
76 {
77 delay(200);
78 setBasicMotorSpeed(speed_pct);
79 }
80 }
81
82 void incrMotorSpeed()
83 {
84 setMotorSpeedLevel(m_speed_level + 1);
85 }
86
87 void decrMotorSpeed()
88 {
89 setMotorSpeedLevel(m_speed_level - 1);
90 }
91
92 void stopMotor()
93 {
94 stopBasicMotor();
95 m_speed_level = 0;
96 }
97
98 void stopMotorWithBreak()
99 {
100 if (abs(m_speed_level) == 3)
101 {
102 playSound((byte)DuploTrainBaseSound::BRAKE);
103 delay(200);
104 }
105 return stopMotor();
106 }
107
108 int isMotorStopped()
109 {
110 // make sure last motor command is at least 1 seconds ago
111 // otherwise issueing a stop motor command might also falsely trigger this
112 return (m_speed_level == 0 && millis() - m_last_motor_cmd > 1000);
113 }
114
115 int currentSpeedLevel()
116 {
117 return m_speed_level;
118 }
119
120 void toggleLight()
121 {
122 if (m_color == Color::BLACK)
123 {
124 setLedColor(m_prev_color);
125 m_color = m_prev_color;
126 m_prev_color = Color::BLACK;
127 }
128 else
129 {
130 setLedColor(Color::BLACK);
131 m_prev_color = m_color;
132 m_color = Color::BLACK;
133 }
134 }
135
136 void cycleLightColor()
137 {
138 m_color = (Color)(int(m_color) + 1);
139 if (m_color >= Color::NUM_COLORS)
140 m_color = Color::PINK;
141 setLedColor(m_color);
142 }
143
144 void onConnect()
145 {
146 m_speed_level = 0;
147 m_last_cmd = m_last_motor_cmd = millis();
148 m_color = m_prev_color = Color::BLUE;
149 }
150
151 unsigned long millisLastCommand()
152 {
153 return m_last_cmd;
154 }
155
156private:
157 void stopBasicMotor()
158 {
159 Lpf2Hub::stopBasicMotor((byte)DuploTrainHubPort::MOTOR);
160 m_last_cmd = millis();
161 m_last_motor_cmd = m_last_cmd;
162 }
163
164 void setBasicMotorSpeed(int speed = 0)
165 {
166 Lpf2Hub::setBasicMotorSpeed((byte)DuploTrainHubPort::MOTOR, speed);
167 m_last_cmd = millis();
168 m_last_motor_cmd = m_last_cmd;
169 }
170
171private:
172 int m_speed_level = 0;
173 unsigned long m_last_motor_cmd = 0;
174 unsigned long m_last_cmd;
175 Color m_color = Color::BLACK;
176 Color m_prev_color = Color::BLACK;
177};