• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricks-micropython / 19016791193

02 Nov 2025 06:40PM UTC coverage: 57.167% (-2.6%) from 59.744%
19016791193

Pull #406

github

laurensvalk
bricks/virtualhub: Replace with embedded simulation.

Instead of using the newly introduced simhub alongside the virtualhub, we'll just replace the old one entirely now that it has reached feature parity. We can keep calling it the virtualhub.
Pull Request #406: New virtual hub for more effective debugging

41 of 48 new or added lines in 7 files covered. (85.42%)

414 existing lines in 53 files now uncovered.

4479 of 7835 relevant lines covered (57.17%)

17178392.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.23
/lib/pbio/src/angle.c
1
// SPDX-License-Identifier: MIT
2
// Copyright (c) 2022-2023 The Pybricks Authors
3

4
// SPDX-License-Identifier: BSD-3-Clause
5
// Copyright (c) 2022-2023 LEGO System A/S
6

7
#include <stdbool.h>
8

9
#include <pbio/angle.h>
10
#include <pbio/int_math.h>
11

12
// Millidegrees per rotation
13
#define MDEG_PER_ROT (360000)
14

15
// Maximum number of rotations that still fit in a 30 bit millidegree value.
16
#define SMALL_ROT_MAX (INT32_MAX / 2 / MDEG_PER_ROT - 1)
17

18
void pbio_angle_flush(pbio_angle_t *a) {
2,147,792,593✔
19
    while (a->millidegrees > MDEG_PER_ROT) {
2,147,826,792✔
20
        a->millidegrees -= MDEG_PER_ROT;
2,147,517,846✔
21
        a->rotations += 1;
2,147,517,846✔
22
    }
23
    while (a->millidegrees < -MDEG_PER_ROT) {
2,147,812,644✔
24
        a->millidegrees += MDEG_PER_ROT;
72,148,849✔
25
        a->rotations -= 1;
72,148,849✔
26
    }
27
}
2,147,792,593✔
28

29
/**
30
 * Gets the angular difference as: result = a - b.
31
 *
32
 * @param [in]  a       Angle a.
33
 * @param [in]  b       Angle b.
34
 * @param [out] result  Result.
35
 */
36
void pbio_angle_diff(const pbio_angle_t *a, const pbio_angle_t *b, pbio_angle_t *result) {
544,052✔
37
    result->rotations = a->rotations - b->rotations;
544,052✔
38
    result->millidegrees = a->millidegrees - b->millidegrees;
544,052✔
39
    pbio_angle_flush(result);
544,052✔
40
}
544,052✔
41

42
/**
43
 * Gets the angular difference as: result = a - b, and scale to millidegrees.
44
 *
45
 * This should only be used for angles less than 5965 rotations apart which can
46
 * be checked with pbio_angle_diff_is_small if unknown.
47
 *
48
 * @param [in]  a       Angle a.
49
 * @param [in]  b       Angle b.
50
 * @return int32_t      Difference in millidegrees.
51
 */
52
int32_t pbio_angle_diff_mdeg(const pbio_angle_t *a, const pbio_angle_t *b) {
2,147,646,810✔
53
    return (a->rotations - b->rotations) * MDEG_PER_ROT + a->millidegrees - b->millidegrees;
2,147,646,810✔
54
}
55

56
/**
57
 * Checks if difference (a - b) can fit in millidegrees alone.
58
 *
59
 * @param [in]  a       Angle a.
60
 * @param [in]  b       Angle b.
61
 * @return              True if (a-b) in millidegrees is valid.
62
 */
63
bool pbio_angle_diff_is_small(const pbio_angle_t *a, const pbio_angle_t *b) {
345,703✔
64
    // Compute the full difference, and flush to whole rotations if possible.
65
    pbio_angle_t diff;
66
    pbio_angle_diff(a, b, &diff);
345,703✔
67

68
    // Return true if the rotation component is small enough.
69
    return diff.rotations < SMALL_ROT_MAX && diff.rotations > -SMALL_ROT_MAX;
345,703✔
70
}
71

72
/**
73
 * Gets the angular sum as: result = a + b.
74
 *
75
 * @param [in]  a       Angle a.
76
 * @param [in]  b       Angle b.
77
 * @param [out] result  Result.
78
 */
79
void pbio_angle_sum(const pbio_angle_t *a, const pbio_angle_t *b, pbio_angle_t *result) {
41,459✔
80
    result->rotations = a->rotations + b->rotations;
41,459✔
81
    result->millidegrees = a->millidegrees + b->millidegrees;
41,459✔
82
    pbio_angle_flush(result);
41,459✔
83
}
41,459✔
84

85
/**
86
 * Gets the angular average as: result = (a + b) / 2.
87
 *
88
 * @param [in]  a       Angle a.
89
 * @param [in]  b       Angle b.
90
 * @param [out] result  Result.
91
 */
92
void pbio_angle_avg(const pbio_angle_t *a, const pbio_angle_t *b, pbio_angle_t *result) {
38,382✔
93
    pbio_angle_sum(a, b, result);
38,382✔
94
    result->millidegrees = result->millidegrees / 2 + (result->rotations % 2) * MDEG_PER_ROT / 2;
38,382✔
95
    result->rotations /= 2;
38,382✔
96
}
38,382✔
97

98
/**
99
 * Adds a given number of millidegrees to an existing angle.
100
 *
101
 * @param [in]  a         Angle a.
102
 * @param [in]  increment Millidegrees to add.
103
 */
104
void pbio_angle_add_mdeg(pbio_angle_t *a, int32_t increment) {
2,147,603,464✔
105
    pbio_angle_flush(a);
2,147,603,464✔
106
    a->millidegrees += increment;
2,147,603,464✔
107
}
2,147,603,464✔
108

109
/**
110
 * Negates existing angle as: a = -a
111
 *
112
 * @param       a    Angle a.
113
 */
114
void pbio_angle_neg(pbio_angle_t *a) {
39,291✔
115
    a->millidegrees *= -1;
39,291✔
116
    a->rotations *= -1;
39,291✔
117
}
39,291✔
118

119
/**
120
 * Scales down high resolution angle to single integer.
121
 *
122
 * For example, if scale is 1000, this converts the angle in
123
 * millidegrees to the value in degrees.
124
 *
125
 * @param [out]  a       Angle a.
126
 * @param [in]   scale   Ratio between high resolution angle and input.
127
 */
128
int32_t pbio_angle_to_low_res(const pbio_angle_t *a, int32_t scale) {
3,074✔
129

130
    // Fail safely on zero division.
131
    if (scale < 1) {
3,074✔
UNCOV
132
        return 0;
×
133
    }
134

135
    // Scale down rotations component. NB: Truncates, does not round.
136
    int32_t rotations_component = pbio_int_math_mult_then_div(a->rotations, MDEG_PER_ROT, scale);
3,074✔
137

138
    // Scale down millidegree component, rounded to nearest ouput unit.
139
    int32_t millidegree_component = (a->millidegrees + pbio_int_math_sign(a->millidegrees) * scale / 2) / scale;
3,074✔
140

141
    return rotations_component + millidegree_component;
3,074✔
142
}
143

144
/**
145
 * Scales down high resolution angle to floating point value.
146
 *
147
 * @param [out]  a       Angle a.
148
 * @param [in]   scale   Ratio between high resolution angle and input.
149
 */
150
float pbio_angle_to_low_res_float(const pbio_angle_t *a, float scale) {
2✔
151

152
    // Fail safely on zero division.
153
    if (scale < 1) {
2✔
UNCOV
154
        return 0;
×
155
    }
156

157
    return a->rotations * (MDEG_PER_ROT / scale) + a->millidegrees / scale;
2✔
158
}
159

160
/**
161
 * Populates object from scaled-up integer value.
162
 *
163
 * For example, if @p scale is 1000, this converts the @p input in
164
 * degrees to @p a in millidegrees.
165
 *
166
 * @param [out]  a       Angle a.
167
 * @param [in]   input   Value to convert.
168
 * @param [in]   scale   Ratio between high resolution angle and input.
169
 */
170
void pbio_angle_from_low_res(pbio_angle_t *a, int32_t input, int32_t scale) {
125✔
171

172
    // Fail safely on zero division.
173
    if (scale < 1 || scale > MDEG_PER_ROT) {
125✔
UNCOV
174
        return;
×
175
    }
176

177
    // Get whole rotations.
178
    a->rotations = input / (MDEG_PER_ROT / scale);
125✔
179

180
    // The round off is the truncated part in user units.
181
    int32_t roundoff_user = input - a->rotations * (MDEG_PER_ROT / scale);
125✔
182

183
    // We'll keep that portion in the millidegrees component.
184
    a->millidegrees = roundoff_user * scale;
125✔
185
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc