libstp
Robotics library for Botball
Loading...
Searching...
No Matches
MockPlatform.hpp
Go to the documentation of this file.
1#pragma once
2#include <cstdint>
3#include <array>
4#include <atomic>
5#include <functional>
6#include <memory>
7#include <random>
8#include <chrono>
9#include <mutex>
10
12
14{
15 enum class MotorDir : uint8_t { Off = 0b00, CCW = 0b01, CW = 0b10, ServoLike = 0b11 };
16 enum class ServoMode : uint8_t { FullyDisabled = 0, Disabled = 1, Enabled = 2 };
17
24 class MockPlatform
25 {
26 public:
27 static MockPlatform& instance()
28 {
29 static MockPlatform impl;
30 return impl;
31 }
32
33 bool init();
34 bool update();
35
36 // Motor control
37 void setMotor(uint8_t port, MotorDir dir, uint32_t value);
42 void setMotorVelocity(uint8_t port, int signedBemfUnits);
43 float getBemf(uint8_t port) const;
44 int getMotorPosition(uint8_t port) const;
45
46 // Servo control
47 void setServo(uint8_t port, ServoMode mode, uint16_t pos);
48
49 // Analog sensors (simulated)
50 uint16_t getAnalog(uint8_t port) const;
51
52 // Digital inputs (simulated)
53 bool getDigital(uint8_t bit) const;
54 uint16_t getDigitalRaw() const;
55
56 // IMU data (simulated)
57 float getGyroX() const;
58 float getGyroY() const;
59 float getGyroZ() const;
60 float getAccelX() const;
61 float getAccelY() const;
62 float getAccelZ() const;
63 float getMagX() const;
64 float getMagY() const;
65 float getMagZ() const;
66 float getQuatW() const;
67 float getQuatX() const;
68 float getQuatY() const;
69 float getQuatZ() const;
70
71 uint32_t getLastUpdateUs() const;
72
73 // Test utilities for contributors and higher-level tests.
74 void setAnalogValue(uint8_t port, uint16_t value);
75 void setDigitalValue(uint8_t bit, bool value);
76 void setIMUValues(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
77
78 // ─── Simulation integration ───
79 // When a SimWorld is attached, setMotor() commands are routed into it,
80 // MockOdometry reads its pose, and gyroZ() reports
81 // its yaw rate. Call tickSim(dt) to advance it. The mock HAL remains
82 // fully functional without a sim (all sim methods become no-ops).
84 const libstp::sim::SimMotorMap& motors,
86 const libstp::sim::Pose2D& startPose);
87 void detachSim();
88 bool hasSim() const;
91
97
105
110
111 float simYawRate() const;
112
116 int simCurrentLayer() const;
117
118 void tickSim(float dtSeconds);
119
125 void setAutoTickEnabled(bool enabled);
126 bool isAutoTickEnabled() const;
127
131 using ClockFn = std::function<double()>;
133
137 void setAutoTickMaxStep(float maxDtSeconds);
138
142
143 private:
144 MockPlatform();
145 ~MockPlatform() = default;
146
147 // Motor state
148 struct MotorState {
149 MotorDir direction = MotorDir::Off;
150 uint32_t speed = 0;
151 float position = 0.0f; // Simulated encoder position
152 float bemf = 0.0f; // Back EMF simulation
153 };
154
155 // Servo state
156 struct ServoState {
158 uint16_t position = 1500; // Default center position
159 };
160
161 // Two locks, with a strict order: m_mutex (HAL state) first, then
162 // m_simMutex (simulation). This lets a long sim physics step run
163 // without gating cheap HAL reads (analog, digital, IMU) on the
164 // same mutex. Methods that touch both (setMotor, getBemf, getAnalog,
165 // getGyroZ) take them in order.
166 mutable std::mutex m_mutex;
167 std::array<MotorState, 4> m_motors;
168 std::array<ServoState, 4> m_servos;
169 std::array<uint16_t, 6> m_analog_values;
170 uint16_t m_digital_values;
171
172 // IMU simulation
173 mutable std::mt19937 m_rng;
174 mutable std::normal_distribution<float> m_noise_dist;
175
176 struct IMUState {
177 float gyro_x = 0.0f, gyro_y = 0.0f, gyro_z = 0.0f;
178 float accel_x = 0.0f, accel_y = 0.0f, accel_z = 9.81f; // Default gravity
179 float mag_x = 0.0f, mag_y = 0.0f, mag_z = 1.0f; // Default magnetic field
180 // DMP fused orientation quaternion — default identity (flat).
181 float quat_w = 1.0f, quat_x = 0.0f, quat_y = 0.0f, quat_z = 0.0f;
182 } m_imu;
183
184 std::chrono::high_resolution_clock::time_point m_start_time;
185
186 mutable std::mutex m_simMutex;
187 std::unique_ptr<libstp::sim::SimWorld> m_sim;
188 libstp::sim::Pose2D m_simOrigin{};
189 libstp::sim::Pose2D m_odoOrigin{};
190 bool m_autoTick{false};
191 ClockFn m_clock;
192 double m_lastTickTime{0.0};
193 float m_autoTickMaxStep{0.05f};
194
195 void updateMotorSimulation();
196 float addNoise(float value) const;
197 int motorCommandToSignedPercent(MotorDir dir, uint32_t value) const;
198 void autoTickLocked(); // caller holds m_simMutex
199 };
200
201 // Free functions keep mock driver code shaped similarly to wombat driver code.
202 inline void setMotor(uint8_t port, MotorDir dir, uint32_t value)
203 {
204 MockPlatform::instance().setMotor(port, dir, value);
205 }
206
207 inline void setServo(uint8_t port, ServoMode mode, uint16_t pos)
208 {
209 MockPlatform::instance().setServo(port, mode, pos);
210 }
211
212 inline uint16_t analog(uint8_t idx)
213 {
214 return MockPlatform::instance().getAnalog(idx);
215 }
216
217 inline bool digital(uint8_t bit)
218 {
220 }
221
222 inline uint16_t digitalRaw()
223 {
225 }
226
227 inline float bemf(uint8_t mot)
228 {
229 return MockPlatform::instance().getBemf(mot);
230 }
231
232 inline float gyroX() { return MockPlatform::instance().getGyroX(); }
233 inline float gyroY() { return MockPlatform::instance().getGyroY(); }
234 inline float gyroZ() { return MockPlatform::instance().getGyroZ(); }
235
236 inline float accelX() { return MockPlatform::instance().getAccelX(); }
237 inline float accelY() { return MockPlatform::instance().getAccelY(); }
238 inline float accelZ() { return MockPlatform::instance().getAccelZ(); }
239
240 inline float magX() { return MockPlatform::instance().getMagX(); }
241 inline float magY() { return MockPlatform::instance().getMagY(); }
242 inline float magZ() { return MockPlatform::instance().getMagZ(); }
243
244 inline float quatW() { return MockPlatform::instance().getQuatW(); }
245 inline float quatX() { return MockPlatform::instance().getQuatX(); }
246 inline float quatY() { return MockPlatform::instance().getQuatY(); }
247 inline float quatZ() { return MockPlatform::instance().getQuatZ(); }
248
249 inline uint32_t lastUpdateUs()
250 {
252 }
253}
bool getDigital(uint8_t bit) const
libstp::sim::Pose2D simRelativePose() const
void setClockSource(ClockFn clock)
libstp::sim::SimWorld * sim()
std::function< double()> ClockFn
libstp::sim::Pose2D simOdometryRelativePose() const
int getMotorPosition(uint8_t port) const
uint16_t getAnalog(uint8_t port) const
void setAutoTickMaxStep(float maxDtSeconds)
void setMotor(uint8_t port, MotorDir dir, uint32_t value)
void setServo(uint8_t port, ServoMode mode, uint16_t pos)
void setDigitalValue(uint8_t bit, bool value)
libstp::sim::Pose2D simPose() const
void setAutoTickEnabled(bool enabled)
void configureSim(const libstp::sim::RobotConfig &robot, const libstp::sim::SimMotorMap &motors, libstp::sim::WorldMap map, const libstp::sim::Pose2D &startPose)
void setMotorVelocity(uint8_t port, int signedBemfUnits)
void tickSim(float dtSeconds)
float getBemf(uint8_t port) const
void setAnalogValue(uint8_t port, uint16_t value)
static MockPlatform & instance()
void setIMUValues(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz)
libstp::map::WorldMap WorldMap
Definition WorldMap.hpp:12
void setServo(uint8_t port, ServoMode mode, uint16_t pos)
uint16_t analog(uint8_t idx)
bool digital(uint8_t bit)
float bemf(uint8_t mot)
void setMotor(uint8_t port, MotorDir dir, uint32_t value)