❔ Making an async function or event system to get steps on an encoder.

I am using an encoder with the GPIO controller on my pi in C#, the main issue is I dont understand how to read the leading edges without making a constantly running function. I have ha 2 ideas of how to read the encoder in and its either an event to increase a counter, or an async function that is constantly running to look for the leading edge. the issue is im not particularly versed in either of those things. we are using this library: https://learn.microsoft.com/en-us/dotnet/api/system.device.gpio.gpiocontroller?source=recommendations&view=iot-dotnet-latest it has a async function for reading but I dont know how to use that to count without calling it every tick or something
GpioController Class (System.Device.Gpio)
Represents a general-purpose I/O (GPIO) controller.
266 Replies
JakenVeina
JakenVeina2y ago
I feel like GPIO isn't really an interface intended to do... that
Dyl_Pickle88
Dyl_Pickle88OP2y ago
then what are you supposed to do. I need to control the motor with the pi, what are my options. The pi can see when a pin changes, could I not make an event to increment a counter?
JakenVeina
JakenVeina2y ago
the most proper way to do something like this is with interrupts
Dyl_Pickle88
Dyl_Pickle88OP2y ago
the encoder is 2700 pulses per rotation, and the motor spins at 60 rpm, so its a maximum of 2700 hz the code should be able to keep up
JakenVeina
JakenVeina2y ago
I.E. events barring that, you're just gonna have to implement your own sampling loop this is a PWM motor speed feedback? if you've gotta make your own loop, you're definitely gonna want a timer
Dyl_Pickle88
Dyl_Pickle88OP2y ago
well its a dc motor controlled by pwm but it has a duel channel encoder on its output thats what we are trying to read so I just have 2 pins on read mode and i can read in from them
JakenVeina
JakenVeina2y ago
dual channel meaning differential between the two?
Dyl_Pickle88
Dyl_Pickle88OP2y ago
one is rising edge one is falling edge, you can tell the direction based on which goes high first
JakenVeina
JakenVeina2y ago
ahhh, okay anyway, yeah, if the GPIO interface doesn't provide events or callbacks for you, you'll need a sampling loop, based on a timer either System.Timers.Timer or System.Threading.Timer I'm rusty on what the differences are, there's like 5 different Timer APIs just in the BCL alone
Dyl_Pickle88
Dyl_Pickle88OP2y ago
ok so am i making like a tick event and just every tick read it?
JakenVeina
JakenVeina2y ago
that's pretty much how a Timer works, yeah
Dyl_Pickle88
Dyl_Pickle88OP2y ago
so this is more like just reading it every chance u can then. not really an event system
JakenVeina
JakenVeina2y ago
and don't count on the Timer being accurate, bias all your readings with timestamps right sampling then you can generate your OWN event, if needed, for your other code
Dyl_Pickle88
Dyl_Pickle88OP2y ago
ok so the async functions it provides are useless
JakenVeina
JakenVeina2y ago
maybe? what async functions?
Dyl_Pickle88
Dyl_Pickle88OP2y ago
WaitForEvent(Int32, PinEventTypes, TimeSpan)
JakenVeina
JakenVeina2y ago
well, that doesn't sound useless
Dyl_Pickle88
Dyl_Pickle88OP2y ago
yeah i didnt think it was just didnt know how to use it
JakenVeina
JakenVeina2y ago
standby, lemme move to my desktop and link me the docs for that
JakenVeina
JakenVeina2y ago
so, uhh, yeah I would not call this an async function maybe but, uhhh it do be havin' that PinEventTypes enum
Dyl_Pickle88
Dyl_Pickle88OP2y ago
it do
JakenVeina
JakenVeina2y ago
ahh, yeah, here we go you want .WaitForEventAsync() presumably and, I mean, the docs specify pretty cleanly how to use it
Dyl_Pickle88
Dyl_Pickle88OP2y ago
yeah, i said there was an async function, i just copied the wrong one. but dont you have to re call it every time it finishes
JakenVeina
JakenVeina2y ago
you call it and it will wait for an event to occur, as you specify in the case of the "Async" version, it will wait asynchronously in that it will not wait, it will return a Task that will complete when the event occurs which you can await so, for practical usage, in your scenario you'll want to setup an async loop something to the effect of
Dyl_Pickle88
Dyl_Pickle88OP2y ago
I thought it would be a constantly running function that every time it got a read a high it would re call that function to look for another. and just increment a counter that can be called whenever just have no idea how to do that
JakenVeina
JakenVeina2y ago
public async Task ListenToEncoderAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

// look at the event and do stuff
}
}
public async Task ListenToEncoderAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

// look at the event and do stuff
}
}
or maybe something like
public class MotorController
{
public double CurrentMotorSpeed { get; set; }

public async Task RunAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

CurrentMotorSpeed = ...
}
}
}
public class MotorController
{
public double CurrentMotorSpeed { get; set; }

public async Task RunAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

CurrentMotorSpeed = ...
}
}
}
Dyl_Pickle88
Dyl_Pickle88OP2y ago
so as long as the token isnt reached it will keep calling the even function.
JakenVeina
JakenVeina2y ago
it's a loop it will loop until you tell it not to loop
Dyl_Pickle88
Dyl_Pickle88OP2y ago
public class MotorController
{
public int currentStep{ get; set; }

public async Task RunAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

currentStep += 1;
}
}
}
public class MotorController
{
public int currentStep{ get; set; }

public async Task RunAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var eventResult = await controller.WaitForEventAsync(cancellationToken);

currentStep += 1;
}
}
}
and with this if i output currentStep it will give me the current step
JakenVeina
JakenVeina2y ago
uhh
Dyl_Pickle88
Dyl_Pickle88OP2y ago
i just call this function once at the start of the program and that task will stay running?
JakenVeina
JakenVeina2y ago
I'll assume you meant to put that in proper English and say "yes" yes the top level of your program will probably look something like
var stopTokenSource = new CancellationTokenSource();

applicationLifetime.StopRequested += stopTokenSource.Cancel();

await Task.WhenAll(
motorController.RunAsync(stopTokenSource),
logicController.RunAsync(stopTokenSource),
// whatever else
);
var stopTokenSource = new CancellationTokenSource();

applicationLifetime.StopRequested += stopTokenSource.Cancel();

await Task.WhenAll(
motorController.RunAsync(stopTokenSource),
logicController.RunAsync(stopTokenSource),
// whatever else
);
better yet you'll use Microsoft's already-engineered solution for this kinda thing
JakenVeina
JakenVeina2y ago
.NET Generic Host - .NET
Learn about the .NET Generic Host, which is responsible for app startup and lifetime management.
JakenVeina
JakenVeina2y ago
long story short
public static void Main()
{
using var host = new HostBuilder()
.ConfigureServices(services => services
.AddHostedService<MotorControllerService>()
.AddHostedService<OtherBackgroundService>()
.AddHostedService<AnotherBackgroundService>())
.Build();

host.Run();
}
public static void Main()
{
using var host = new HostBuilder()
.ConfigureServices(services => services
.AddHostedService<MotorControllerService>()
.AddHostedService<OtherBackgroundService>()
.AddHostedService<AnotherBackgroundService>())
.Build();

host.Run();
}
Dyl_Pickle88
Dyl_Pickle88OP2y ago
then in my main how to i get my currentStep value
JakenVeina
JakenVeina2y ago
DI I.E. you don't do it in Main() Main() is just for orchestrating all the different things that run in parallel
Dyl_Pickle88
Dyl_Pickle88OP2y ago
can you not just run the main body on he main thread?
JakenVeina
JakenVeina2y ago
what are you ultimately trying to do with CurrentStep you can
Dyl_Pickle88
Dyl_Pickle88OP2y ago
well im using it for position values
JakenVeina
JakenVeina2y ago
you can code whatever you feel like coding for
Dyl_Pickle88
Dyl_Pickle88OP2y ago
i need to know what direction my motor is facing so i need to know my step count in the body of the code to know how much to turn it
JakenVeina
JakenVeina2y ago
the point is that when you have complex things interacting, you should use the tools that are made for you to do those complex things like have multiple separate logical things running in parallel
Dyl_Pickle88
Dyl_Pickle88OP2y ago
yes the issue is im on a pi, and the number of threads i have is 4
JakenVeina
JakenVeina2y ago
exactly that's why you're using async/await the number of physical threads you have available is irrelevant you get logical parallelism, regardless of hardware
Dyl_Pickle88
Dyl_Pickle88OP2y ago
well if i queue 5 tasks, one isnt running and if its the encoder one i am losing data and my position am i not?
JakenVeina
JakenVeina2y ago
if you're properly leveraging async/await, "queue 5 tasks" tells you nothing about how many physical threads you're using so, like context what is this all for
Dyl_Pickle88
Dyl_Pickle88OP2y ago
I'm in a rocket team and this is for a steerable parachute system, we need to know how far the motors have turned because that is giving the angle of the parachute
JakenVeina
JakenVeina2y ago
k so here's the thing your program is going to spend 99% of its time doing literally nothing
Dyl_Pickle88
Dyl_Pickle88OP2y ago
so we need to read gps data, imu data, bmp data, calculate our heading 10 times a second and move the motor
JakenVeina
JakenVeina2y ago
right
Dyl_Pickle88
Dyl_Pickle88OP2y ago
as fast as we can ideally
JakenVeina
JakenVeina2y ago
your goal is to write a program that sits idle, most of the time, waiting for input from one of these sources when a batch of data comes in from the GPS unit or the IMU or the motor controller you spend a small amount of time processing that input then go back to being idle, waiting for the next one
Dyl_Pickle88
Dyl_Pickle88OP2y ago
well 4 things are going to be constantly happening. there is almost no down time. the motor will be spinning constantly to keep up with the rotation of the physical system, and the encoders will be changing at a rate of 45 times a second
JakenVeina
JakenVeina2y ago
and your CPU runs about 10^6 times faster than that from the point of view of your CPU/program, the majority of your time is spent doing nothing
Dyl_Pickle88
Dyl_Pickle88OP2y ago
i get that, just wasnt sure since the 2 encoder tasks are permanently running if that counts ad essentially -2 threads since its awaiting for the next rising edge i didnt know if anything else would run on those threads
JakenVeina
JakenVeina2y ago
I gotta say, you REALLY lucked out on who you got to pick up this thread not only am I actually an electronics engineer, as opposed to just a computer scientist or web developer, like most folks here
Dyl_Pickle88
Dyl_Pickle88OP2y ago
yeah im not the best with threads so im glad you know this
JakenVeina
JakenVeina2y ago
I haven't been active around here for like a year, until just the past week or so so, I'm gonna go ahead and sidetrack for a moment to talk about how you should be handling these I/O devices let's take the motor controller you mentioned the motor spins at 2700 RPM earlier? or that the encoder can spit out a signal at 2700Hz maximum?
Dyl_Pickle88
Dyl_Pickle88OP2y ago
no it spins at 60 rpm the encoder is 2700 pulses per rotation
JakenVeina
JakenVeina2y ago
k so, your maximum signal rate is 2700*60 well, really probably double that? what's the width of these pulses?
Dyl_Pickle88
Dyl_Pickle88OP2y ago
There are ~2700 pulses per rotation and the motor can only output 60 RPM so the math works out to ~2700 pulses per second AKA 2700Hz
JakenVeina
JakenVeina2y ago
right, right, RPM not RPS but still, what's the pulse width
Dyl_Pickle88
Dyl_Pickle88OP2y ago
With a cpu clock of 1.5GHz and the nyquist theorem, the Pi should be fast enough right? I don't know the contact arc length on the encoder
JakenVeina
JakenVeina2y ago
at 2700Hz, pulse delay would be 37ms aha you're already where I'm trying to go
Dyl_Pickle88
Dyl_Pickle88OP2y ago
No it would be 0.37ms right?
JakenVeina
JakenVeina2y ago
it's worth verifying it specifically, but yeah, you're looking for the sampling frequency of the GPIO controller to be at least double the switching frequency of the signal
Dyl_Pickle88
Dyl_Pickle88OP2y ago
I dont know about the hardware of the Pi. So who knows if the sample rate is anywhere near the cpu clock
JakenVeina
JakenVeina2y ago
it's 3.7e-4, so... fuck, stop making my brain look bad that's actually 370us, then, yeah?
Dyl_Pickle88
Dyl_Pickle88OP2y ago
yes
JakenVeina
JakenVeina2y ago
yeah, okay so, I dunno if that's something that's controllable with the GPIO interface like I said earlier, the ideal setup is that .WaitForEventAsync() is actually listening for an interrupt I.E. it's not sampling at all, it's sampling in hardware you definitely don't want the GPIO controller sampling "constantly" you want it sampling, at most, at 2x the switching frequency, or ideally not at all same goes for all your other data sources they're either sending you data, via interrupts or you're setting up a sampling loop in software, to sample at some SPECIFIC frequency, depending on the data source all that comes together to form a program that spends the vast majority of its time idle
Dyl_Pickle88
Dyl_Pickle88OP2y ago
ill look more into that host builder, i still dont know how you are passing data from service to service
JakenVeina
JakenVeina2y ago
through the DI system Microsoft.Extensions.DependencyInjection
Dyl_Pickle88
Dyl_Pickle88OP2y ago
oh ok
JakenVeina
JakenVeina2y ago
example your MotorControllerService might look like this
Dyl_Pickle88
Dyl_Pickle88OP2y ago
also for an await is it not suspending that thread untill it gets an answer?
JakenVeina
JakenVeina2y ago
that is pretty much what is happening, yes except it's not suspending a thread it's suspending a logical thread which is even better if you're properly applying async/await what you're actually doing is delegating the entirety of your program to the ThreadPool every time something triggers your code like, when a GPIO event occurs what actually happens is that your code gets sent to the ThreadPool to be scheduled
Dyl_Pickle88
Dyl_Pickle88OP2y ago
ok ill be back in like 5 i gotta drive home real quick. I have to switch accounts this in one setup on the computer not mine @FacePalmGamer is my actual account
JakenVeina
JakenVeina2y ago
the ThreadPool picks out an available thread to run your code and your code runs on that thread, until the next time an await makes it suspend at which point the physical thread goes back to the ThreadPool so, you could have any number of "logical" threads running all at once say, one for every one of your data peripherals and then maybe another for your main control loop maybe that's 6 "logical" threads but they're not all "physically" running at once they spend most of their time suspended whenever something triggers one of them to resume, it gets ASSIGNED to a physical thread instead of each one taking up its own physical thread with traditional threading, each one of these would be its own Thread object, running on a physical OS-managed thread and when one needs to resume, it sends a signal to the OS to give it some CPU time which requires assigning that thread to a core, to execute or waiting for a free core I mean, it all sounds VEEEEEERRY similar to what I just described for "logical" threads, because it is but at the end of the day, a "logical" thread in .NET land is WAY more lightweight than a physical thread, managed by the OS so, it's a decent performance boost, especially if you're running on something like a PI with resource constraints or where your application is quite time-sensitive, which yours is so example
public class MotorControllerService
: BackgroundService
{
public double CurrentMotorSpeed { get; private set; }

public double TargetMotorSpeed { get; set; }

protected override ExecuteAsync(CancellationToken cancellationToken)
{
while(cancellationToken.IsCancellationRequested)
{
var eventResult = await _gpioController.WaitForEventAsync(..., cancellationToken);

// Perform calculations

CurrentMotorSpeed = ...

// Perform more calculations
var speedDifference = TargetMotorSpeed - CurrentMotorSpeed;
...
_pwmController.Output = ...
}
}
}
public class MotorControllerService
: BackgroundService
{
public double CurrentMotorSpeed { get; private set; }

public double TargetMotorSpeed { get; set; }

protected override ExecuteAsync(CancellationToken cancellationToken)
{
while(cancellationToken.IsCancellationRequested)
{
var eventResult = await _gpioController.WaitForEventAsync(..., cancellationToken);

// Perform calculations

CurrentMotorSpeed = ...

// Perform more calculations
var speedDifference = TargetMotorSpeed - CurrentMotorSpeed;
...
_pwmController.Output = ...
}
}
}
BackgroundService is a base class that comes with the hosting framework it's a half-implementation of IHostedService, which is the real magic
FacePalmGamer
FacePalmGamer2y ago
oh ok, then the only question is when the logic thread is suspended if its not on a physical thread how can it ever be notified that it got what it wanted?
JakenVeina
JakenVeina2y ago
IHostedService is
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
public interface IHostedService
{
Task StartAsync(CancellationToken cancellationToken);
Task StopAsync(CancellationToken cancellationToken);
}
and when you do IHost.Run() the host searches for all registered IHostedService classes, and calls StartAsync() on them and then will call StopAsync() later, if instructed to do so in this way, you can setup a variety of different "background" tasks that are constantly "running" on top of that if you register MotorController the right way, with the IoC container other services can reference it so, for a program like this, you probably want some central "control" loop and that can be its own BackgroundService
FacePalmGamer
FacePalmGamer2y ago
yes this is what i would refer to as my main body
read sensor
calculate heading
change motor position
read sensor
calculate heading
change motor position
JakenVeina
JakenVeina2y ago
and then loop over that so, you would setup that class to have MotorControllerService as a "dependency"
FacePalmGamer
FacePalmGamer2y ago
ok so i wont have any "meat" in my main. it will just be build this service manager and thats it
JakenVeina
JakenVeina2y ago
and within the ExecuteAsync() method of that class, you would do
_motorController.TargetMotorSpeed = ...
_motorController.TargetMotorSpeed = ...
or something to that effect right, the interesting stuff would be going on within its own BackgroundService class maybe PrimaryControlLoopService or similar
FacePalmGamer
FacePalmGamer2y ago
ok I never knew about building an app like this this is really interesting
JakenVeina
JakenVeina2y ago
little bit you certainly don't HAVE to but boy it beats trying to micro-manage all this kinda stuff within one master Main() method in my mind and yeah, I keep going back to that MotorControllerService class because you can make your life a lot easier with it you keep saying you need to know the motor's position, but... do you?
FacePalmGamer
FacePalmGamer2y ago
well almost all data from the sensor, i will just call and ask for it when needed, but the encoder is different since it has to update constantly so i didnt know how to achieve that effect
JakenVeina
JakenVeina2y ago
is that what you REALLY care about? or do you care about using the position, incrementally, to figure out the speed? is the motor constantly spinning?
FacePalmGamer
FacePalmGamer2y ago
yes, i need to know the motor a is at 75% and motor b is at 75% so that my heading is 45 degrees north east
JakenVeina
JakenVeina2y ago
or is it more like a servo?
FacePalmGamer
FacePalmGamer2y ago
but as the physical payload is spinning those values will change to match the rotation so that my flight heading remains at 45 degrees north east so the motors position will follow a sin and cos wave to keep the heading constant
JakenVeina
JakenVeina2y ago
is this a gyro?
FacePalmGamer
FacePalmGamer2y ago
and the encoder verifies where it is at. i don't care about the speed at all, just the ratio of the motors there is a gyro on board on the imu so we have a heading to get our rotation that we need to counteract
JakenVeina
JakenVeina2y ago
I can't fully picture it, but alright whatever value you're interested in whatever value is meaningful for your navigation logic that's the value you expose on MotorControllerService so that NavigationService (let's call it that now, that's better)
FacePalmGamer
FacePalmGamer2y ago
imagine a falling brick with a parachute in the shape of a + each side has a wire. if you pull in the left side and release the right side, you will go directly left.
JakenVeina
JakenVeina2y ago
can poll that value whenever it wants so, the motors are essentially servos, controlling flaps
FacePalmGamer
FacePalmGamer2y ago
yes
JakenVeina
JakenVeina2y ago
lovely
FacePalmGamer
FacePalmGamer2y ago
we are using these as VERY BEEFY servos
JakenVeina
JakenVeina2y ago
yeah the reason you want a MotorConrollerService is that the process of reading feedback from the motors is asynchronous, compared to how often your navigation loop needs to poll for motor position if any of your other sensors operate that way, you'll want a ControllerService for them as well and then your main NavigationService will be a loop that runs at a particular update frequency yes, you want it to run as fast as possible, but not LITERALLY you'll want to set the update frequency based on the response times of your sensors, and your motor controller outputs (and any other outputs)
FacePalmGamer
FacePalmGamer2y ago
do you set the update time of each service individually?
JakenVeina
JakenVeina2y ago
like, there's a point where running more-frequent updates doesn't get you any practical value, because the sensors and the motors can't respond any faster so, if you run updates faster than that, you're just wasting power from the sound of it, the only service that'll explicitly be a timed update loop, is the main navigation the motor controller is a loop, but it isn't timed, it's interrupt-triggered and the other sensors are either not interrupt-based, in which case, they likely don't need a separate service, or also interrupt-triggered, thus also not-timed
FacePalmGamer
FacePalmGamer2y ago
I call them with i2C when i want a value
JakenVeina
JakenVeina2y ago
yeah you're explicitly polling them
FacePalmGamer
FacePalmGamer2y ago
yup
JakenVeina
JakenVeina2y ago
cause they run their own sampling/polling loops, in hardware or firmware the motor controllers don't they can't be polled so, you need a background service to MAKE them pollable the only other concern might be that if any of the other sensors take a particularly LONG time to pool, you might want a separate service for that polling, just to help keep the navigation loop from bogging down I.E. that would let you, say, run the navigation loop 3 times faster than the GPS polling loop allowing the navigation to take advantage of the faster speed of all the other sensors
FacePalmGamer
FacePalmGamer2y ago
the way i was doing the motor before when we were using steppers was just
void TurnMotorAsync(double percent)
{
int stepAmount = percent*TOTAL_STEP
for(i = 0; i < stepAmount; i++)
...
}
void TurnMotorAsync(double percent)
{
int stepAmount = percent*TOTAL_STEP
for(i = 0; i < stepAmount; i++)
...
}
and that got me the turn i wanted. with these since they are dc it should be closer to
void TurnMotor(double percent)
{
pwm.start()
pwm.dutycycle = .095 //this is full speed on these motors
while(stepAmount < percent*TOTAL_STEP);
pwm.stop()
}
void TurnMotor(double percent)
{
pwm.start()
pwm.dutycycle = .095 //this is full speed on these motors
while(stepAmount < percent*TOTAL_STEP);
pwm.stop()
}
dont ask why we switched form stepper to dc, long story would i want that in a service or just the main service
JakenVeina
JakenVeina2y ago
personally, I would probably put all that outside of the main loop cause it's a closed-loop system you have one input to that loop, which is effectively a target heading yeah?
FacePalmGamer
FacePalmGamer2y ago
yes and i might need to change that before it reaches it if i am able to update the heading faster than i reach the location I would
JakenVeina
JakenVeina2y ago
so, you can setup that loop to just have a target position or target heading input
FacePalmGamer
FacePalmGamer2y ago
percentage is best. the equation spits out a percentage of each motor i need (so the ratio)
JakenVeina
JakenVeina2y ago
and because its speed is limited purely by the responses from the encoder, it can run basically as efficiently as possible as soon as the encoder event comes in, you can calculate the new PWM value to output and you then have the freedom to make that feeback more sophisticated without affecting the navigation logic like, make it a full PID loop, instead of just a P loop the navigation loop wouldn't care now maybe that doesn't make sense how much do the motor outputs depend on feedback from, say, the GPS or IMU? maybe the motor loop isn't as "closed" as I describe it
FacePalmGamer
FacePalmGamer2y ago
IMU is what we care about. GPS is for initial heading, after that imu is trying to keep us there
JakenVeina
JakenVeina2y ago
if so, maybe just have the motor controller just publish the position value from the most-recent input from the encoder and navigation can do the rest
FacePalmGamer
FacePalmGamer2y ago
as IMU says hey we have spun 15 degrees (because falling objects spin) we have to counteract that with motors
JakenVeina
JakenVeina2y ago
yeah like okay so we established earlier we're using these motors as glorified servos but, they're actually not, right? the position is not controlled in hardware, you're controlling it in software, right? the PWM output is "target motor speed" essentially, yeah?
FacePalmGamer
FacePalmGamer2y ago
so just so i understand this ima going to write some psudo code
main service:
Gets gps cords
Calcs heading
IMU lists current heading
Find difference
calculate Motor amount
Call motor spin service
creates data package to save to local drive
Motor spin service:
Calls encoder serive
turns motor until params met?
encoder service
update value whenever you can
main service:
Gets gps cords
Calcs heading
IMU lists current heading
Find difference
calculate Motor amount
Call motor spin service
creates data package to save to local drive
Motor spin service:
Calls encoder serive
turns motor until params met?
encoder service
update value whenever you can
i can link them we are using them like servos thouhg
JakenVeina
JakenVeina2y ago
right, but you're making that happen in software?
FacePalmGamer
FacePalmGamer2y ago
pwm only controls speed. there is almost no reason not to put it at full throttle until your encoder says stop
JakenVeina
JakenVeina2y ago
a constant PWM signal does not equate to constant motor position? right okay so, 100% I think MotorControllerService should be the service that contains that secondary control loop
FacePalmGamer
FacePalmGamer2y ago
the only reason pwm is anything but full forward or full reverse is so it doesnt over heat/ more precision
JakenVeina
JakenVeina2y ago
yeah your nav loop decides what the motor position OUGHT to be and the motor loop adjusts speed back and forth in its best attempt to get there and yeah, that sounds like a perfect use-case for a PID loop
JakenVeina
JakenVeina2y ago
and it definitely benefits you to have that loop run as responsively as possible I.E. realtime it doesn't run on a timer, it runs the moment any input is received either input from the encoder, or input from the nav loop all of those inputs, plus information from the previous loop, go together to determine the desired power setting for the motor
FacePalmGamer
FacePalmGamer2y ago
ok so i am correct in assuming i will have 3 tasks
JakenVeina
JakenVeina2y ago
from what you describe, you'll have that, and you'll have the main loop I would say 2 you don't really gain anything by having encoder-reading and motor spin as separate your motor spin logic can be completely reactive (and iterative, I suppose)
private double CalculateDesiredMotorPower(
double currentMotorPosition,
double targetMotorPosition,
double previousMotorPower,
TimeSpan timeSinceLastUpdate);
private double CalculateDesiredMotorPower(
double currentMotorPosition,
double targetMotorPosition,
double previousMotorPower,
TimeSpan timeSinceLastUpdate);
and your goal is to call that function whenever an encoder signal comes in (I.E. currentMotorPosition changes) or targetMotorPosition is changed by the main loop fully-reactive cause either your output power to the motor is non-zero meaning the motor will move and new encoder data will come in momentarily or you reach your target position, and the motor output is 0 and nothing triggers this function
FacePalmGamer
FacePalmGamer2y ago
ok i see, so it will look something like
protected override TurnMotor(CancellationToken cancellationToken)
{
double percent
pwm.start()
pwm.dutycycle = .095 //this is full speed on these motors
while(cancellationToken.IsCancellationRequested)
{
var eventResult = await _gpioController.WaitForEventAsync(..., cancellationToken);
}
pwm.stop()
}
protected override TurnMotor(CancellationToken cancellationToken)
{
double percent
pwm.start()
pwm.dutycycle = .095 //this is full speed on these motors
while(cancellationToken.IsCancellationRequested)
{
var eventResult = await _gpioController.WaitForEventAsync(..., cancellationToken);
}
pwm.stop()
}
and then i would just edit double percent via the DI thinkg u mention in the main loop
JakenVeina
JakenVeina2y ago
not until the main loop changes targetMotorPosition again kinda obviously, you're missing the part where you change the PWM output
FacePalmGamer
FacePalmGamer2y ago
well the pwm doesnt really need to change
JakenVeina
JakenVeina2y ago
how so?
FacePalmGamer
FacePalmGamer2y ago
either its full forward or full reversed or stopped
JakenVeina
JakenVeina2y ago
so it does need to change
FacePalmGamer
FacePalmGamer2y ago
yeah it would just be if the desired encoder value is less then current change
JakenVeina
JakenVeina2y ago
is forward/reverse/off controlled by some other signal? yeah or greater and I would definitely thing that scaling the motor dutycycle would be a good idea
FacePalmGamer
FacePalmGamer2y ago
pwm.dutycycle = .045 //full reverse
pwm.dutycylce = .075 //stopped
pwm.dutycycle = .095 //full forwards

pwm.dutycycle = .045 //full reverse
pwm.dutycylce = .075 //stopped
pwm.dutycycle = .095 //full forwards

JakenVeina
JakenVeina2y ago
right so, like
FacePalmGamer
FacePalmGamer2y ago
yeah i am trying to dumb it down as much as possible for now, as long as i have a good direction i will make it more proper when i get there
JakenVeina
JakenVeina2y ago
absolutely yes enhancing the control loop to scale the motor power would definitely fall under "tuning" of the control loop
FacePalmGamer
FacePalmGamer2y ago
so is the service just constantly running then im using DI to edit the percent or is there something im missing that i didnt understand
JakenVeina
JakenVeina2y ago
constantly running, logically
FacePalmGamer
FacePalmGamer2y ago
yes
JakenVeina
JakenVeina2y ago
physically, it's sitting mostly idle
FacePalmGamer
FacePalmGamer2y ago
mostly idle in the thread pool
JakenVeina
JakenVeina2y ago
physically, it only responds to specific events either an encoder signal, or a change from the main loop in that regard, your loop code above is flawed it doesn't actually "respond" to changes from the main loop it only responds to encoder input if you tuned that loop well enough, so that it got the motors to the exact right positon, and the encoders stopped giving you input the whole thing would freeze if the main loop set a new target, the motor loop would never try to reach it cause it's still waiting on a signal from the encoder which will never come (I mean, realistically, turbulance will bump it around, but yeah)
FacePalmGamer
FacePalmGamer2y ago
oh so how do i make an interrupt from the main loop change the desired value of the motor position
JakenVeina
JakenVeina2y ago
a couple ways you can either not use .WaitForEventAsync() (which doesn't mean you lose all the advantage of the ThreadPool stuff, you're just gonna do it a bit differently) you'd use (it looks like) RegisterCallbackForPinValueChangedEvent
FacePalmGamer
FacePalmGamer2y ago
well hold on before you start typing a paragraph
JakenVeina
JakenVeina2y ago
hehe
FacePalmGamer
FacePalmGamer2y ago
with the current system. we are awaiting 1 pin
JakenVeina
JakenVeina2y ago
k
FacePalmGamer
FacePalmGamer2y ago
there are 2 pins letting you know direction. is it possible to wait for a few things at once for the first one to respond
JakenVeina
JakenVeina2y ago
yeah, that's what we're heading towards
FacePalmGamer
FacePalmGamer2y ago
gotcha
JakenVeina
JakenVeina2y ago
and actually, it's 4, right? 2 per motor?
FacePalmGamer
FacePalmGamer2y ago
yeah but each motor would be its own service correct?
JakenVeina
JakenVeina2y ago
well, maybe
FacePalmGamer
FacePalmGamer2y ago
so i will have 3 services (2 are for motors, since i have 2 motors) the main loop will do all the logic each motor loop controls that motor and looks for encoder signal
JakenVeina
JakenVeina2y ago
you'd want them to be 2 instances of the same class
FacePalmGamer
FacePalmGamer2y ago
that way the main loop can just say "hey motor 1 try to go here, motor 2 try here"
JakenVeina
JakenVeina2y ago
yeah
FacePalmGamer
FacePalmGamer2y ago
yeah just different input values
JakenVeina
JakenVeina2y ago
the problem is that the DI system isn't quite setup for that it's a little tricky I suppose you COULD do
FacePalmGamer
FacePalmGamer2y ago
i could just copy paste the class and give them slightly different names
JakenVeina
JakenVeina2y ago
nah that's smelly
services
.AddSingleton<IHostedService>(_ => new MotorControllerService(Motor.A))
.AddSingleton<IHostedService>(_ => new MotorControllerService(Motor.B))
services
.AddSingleton<IHostedService>(_ => new MotorControllerService(Motor.A))
.AddSingleton<IHostedService>(_ => new MotorControllerService(Motor.B))
and then your main nav service would have to do
public NavigationService(IEnumerable<MotorControllerService> motorControllers)
{
_motorControllerA = motorControllers.First(motorController => motorController.Motor = Motor.A);
_motorControllerB = motorControllers.First(motorController => motorController.Motor = Motor.B);
}
public NavigationService(IEnumerable<MotorControllerService> motorControllers)
{
_motorControllerA = motorControllers.First(motorController => motorController.Motor = Motor.A);
_motorControllerB = motorControllers.First(motorController => motorController.Motor = Motor.B);
}
something like that each controller would have to self-identify which motor it's controlling cause the DI system will only give you a collection of all of them that've been registered but that should work
FacePalmGamer
FacePalmGamer2y ago
ok so you identify each one on startup and make a local variable in the main loop service to call later
JakenVeina
JakenVeina2y ago
yeah
FacePalmGamer
FacePalmGamer2y ago
ok then now back to the motor service. how do you awat 3 things and do something differently for each
JakenVeina
JakenVeina2y ago
well, in this case, you don't REALLY want to do something differently like sort of but also kinda not so the older-fashioned way would be to just use event handlers callbacks looking at GpioController, it has RegisterCallbackForPinValueChangedEvent() which is identical to WaitForEvent() and WaitForEventAsync() except yet another mechanism for response you GIVE it a function reference to be called instead of getting a Task to be awaited
FacePalmGamer
FacePalmGamer2y ago
yeah I have used events for winforms, drawing on tick, as well as unity for various things
JakenVeina
JakenVeina2y ago
yup same thing and your "event" for when the main loop makes an update is just.... the method that the main loop is already calling the only thing you gotta worry about is thread-safety since the main loop, and the callbacks can potentially be running simultaenously on different threads the more async-y way to do it would be to use a Channel
FacePalmGamer
FacePalmGamer2y ago
so it would be
triggerevent += IncreaseCounter
triggerevent += IncreaseCounter
for each pin, and then how do u do the main changing the desired percentage?
JakenVeina
JakenVeina2y ago
and your "event" for when the main loop makes an update is just.... the method that the main loop is already calling
so
public class MotorControllerService
: IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
_gpioController.RegisterCallbackForPinValueChanged(OnPinValueChanged);
}

public double CurrentMotorPosition
{
get => _currentMotorPosition;
private set
{
_currentMotorPosition = value;
UpdateMotorOutput();
}
}

public double TargetMotorPosition
{
get => _targetMotorPosition;
set =>
{
_targetMotorPosition = value;
UpdateMotorOutput();
}
}

private void OnPinValueChanged(object sender, PinValueChangedEventArgs args)
{
CurrentMotorPosition = ...
}

private void UpdateMotorOutput()
{
pwm.OutputValue = ...
}
}
public class MotorControllerService
: IHostedService
{
public Task StartAsync(CancellationToken cancellationToken)
{
_gpioController.RegisterCallbackForPinValueChanged(OnPinValueChanged);
}

public double CurrentMotorPosition
{
get => _currentMotorPosition;
private set
{
_currentMotorPosition = value;
UpdateMotorOutput();
}
}

public double TargetMotorPosition
{
get => _targetMotorPosition;
set =>
{
_targetMotorPosition = value;
UpdateMotorOutput();
}
}

private void OnPinValueChanged(object sender, PinValueChangedEventArgs args)
{
CurrentMotorPosition = ...
}

private void UpdateMotorOutput()
{
pwm.OutputValue = ...
}
}
as soon as the main loop sets TargetMotorPosition, UpdateMotorOutput() gets called
FacePalmGamer
FacePalmGamer2y ago
yeah i see that
JakenVeina
JakenVeina2y ago
it also gets called as soon as a pin value changes, after a quick re-calculation of what CurrentMotorPosition is or however you want to implement that bit
FacePalmGamer
FacePalmGamer2y ago
how does the main loop set targetmotorposition
JakenVeina
JakenVeina2y ago
that's the, uhhh explicit way to do it
FacePalmGamer
FacePalmGamer2y ago
is this where the DI thing u mention comes in
JakenVeina
JakenVeina2y ago
public class NavigationService
{
public NavigationService(IEnumerable<MotorControllerService> motorControllers)
{

}
}
public class NavigationService
{
public NavigationService(IEnumerable<MotorControllerService> motorControllers)
{

}
}
yes you register all of your different classes and services and whatever with the IoC (Inversion of Control) container when the host starts up, it uses dependency injection to request all registered IHostedService objects from the container, and starts them
FacePalmGamer
FacePalmGamer2y ago
ok then can you just set that value like its a property?
JakenVeina
JakenVeina2y ago
since NavigationService would be one of them and NavigationService has a constructor that requests IEnumerable<MotorControllerService> as a dependency that dependency gets "injected" by the IoC container, when it constructs the instances of that class which the main host requested the IoC container "injects" all registered MotorControllerService objects into NavigationService, via the constructor and then it'd be NavigationService's responsibility to identify which controller belongs to which motor and then, yeah it's just a reference to a class like any other
Dyl_Pickle88
Dyl_Pickle88OP2y ago
Different person here from before who referenced the nyquist theorem. This is not a servo because we dont know the absolute position of the output shaft. The only accurate description is what is actually being used. A DC motor with a gear reduction and an encoder. We'll be using limit switches to determine when we have reached the start/end of the spool of parachute cable. This is I guess more akin to a stepper in which you never know the true position of the output shaft but know how many radians it has turned since you started moving it.
FacePalmGamer
FacePalmGamer2y ago
ok so this is the event way to do it. is there a proper way to do it with await and actually await on both pins or something from the main loop
JakenVeina
JakenVeina2y ago
wait what the fuck when did you change people
FacePalmGamer
FacePalmGamer2y ago
this is a team thing, we are the programmers if that wasnt clear. it was his laptop i was using
JakenVeina
JakenVeina2y ago
I completely missed that
FacePalmGamer
FacePalmGamer2y ago
we both went home
JakenVeina
JakenVeina2y ago
I mean, I heard you say it was a team thing
Dyl_Pickle88
Dyl_Pickle88OP2y ago
The art of misdirection lol
JakenVeina
JakenVeina2y ago
completely missed that I was talking to two people well played
FacePalmGamer
FacePalmGamer2y ago
lol anyways ima re ping that
JakenVeina
JakenVeina2y ago
so, uhh
FacePalmGamer
FacePalmGamer2y ago
i get this gets the same effective result, but knowing how to await multiple things would be nice
JakenVeina
JakenVeina2y ago
yeah there's a couple ways to do it you can do Task.WhenAny() when you call .WaitForEventAsync() it returns a Task which you don't HAVE to await right away if you have another thing you might want to await, you can call that, and get it's Task and then do a combination await on both of them or any number of Tasks Task.WhenAll() accepts any number of input Tasks, and return a Task that will completed when all of the inputs have completed Task.WhenAny() is the same, except its result Task will complete when ANY of the inputs have completed
FacePalmGamer
FacePalmGamer2y ago
I have used when all actually with a snake AI, just running a bunch of snake generations. forgot about whenany
JakenVeina
JakenVeina2y ago
so, you could build a Task that you trigger to complete, whenever the main loop makes an update
FacePalmGamer
FacePalmGamer2y ago
how do i get a task from the main to give me back a new percent value
JakenVeina
JakenVeina2y ago
you'd use a TaskCompletionSource most likely but this is messy in a couple ways first, after await Task.WhenAny() completes, you then need to go BACK and check which of the two tasks completed then extract the result and loop back and make sure you pass the same task that didn't complete to Task.WhenAny() along with a new Task to replace the one that did complete
FacePalmGamer
FacePalmGamer2y ago
well in the case that the main class changed the value that we want to achieve, the loop variable will be updated and we go back to waiting for encoders and or the main to update again
JakenVeina
JakenVeina2y ago
it's also messy in that it doesn't really allow for multiple-triggerings from the main loop correct, I'm just saying the code to do all that correctly is messy and also not QUITE foolproof, if you're just using a TaskCompletionSource
FacePalmGamer
FacePalmGamer2y ago
ah i see so the event method seems to be the clean way to do this
JakenVeina
JakenVeina2y ago
the much cleaner thing to do, if you want an async/await solution is use a Channel
FacePalmGamer
FacePalmGamer2y ago
oh yeah u mentioned that havent heard of that one
JakenVeina
JakenVeina2y ago
it's rather niche but it's SUCH a cool API call me a nerd, but it's one of my favorite APIs in .NET
FacePalmGamer
FacePalmGamer2y ago
so is this just a line of constantly changing data that can be read from? essentially a data stream you can read write to and have multiple members interacting with it
JakenVeina
JakenVeina2y ago
yeah, logically
FacePalmGamer
FacePalmGamer2y ago
cool, but how does this help the await system i see you could put the encoder value and the desired motor position on it, this way the main loop could just edit the channel and the motor position loop would update but you would still have to waitany for either channel a or channel b on the motor unless there is more to this
JakenVeina
JakenVeina2y ago
you can be a little cleverer, with channels
public class MotorControllerService
: BackgroundService
{
public Task SetTargetMotorPositionAsync(double targetMotorPosition)
=> _motorInputs.Writer.WriteAsync(MotorInput.TargetPositionChanged(targetMotorPosition));

protected override Task ExecuteAsync(CancellationToken cancellationToken)
=> Task.WhenAll(
ListenToEncoderAsync(cancellationToken),
ListenForMotorInputsAsync(cancellationToken));

private Task ListenToEncoderAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var gpioEvent = await _gpioController.WaitForEventAsync(cancellationToken);

var currentMotorPosition = ...

await _motorInputs.Writer.WriteAsync(MotorInput.MotorPositionChanged(currentMotorPosition));
}
}

private Task ListenForMotorInputsAsync(CancellationToken cancellationToken)
{
var currentMotorPosition = 0.0d;
var targetMotorPosition = 0.0d;
await foreach(var input = _motorInputs.ReadAllAsync(cancellationToken))
{
switch (input.Type)
{
case MotorInputType.CurrentPosition:
currentMotorPosition = input.Value;
break;

case MotorInputType.TargetPosition:
targetMotorPosition = input.Value;
break;
}

_pwm.Output = ...
}
}

private readonly Channel<MotorInput> _motorInputs;
}
public class MotorControllerService
: BackgroundService
{
public Task SetTargetMotorPositionAsync(double targetMotorPosition)
=> _motorInputs.Writer.WriteAsync(MotorInput.TargetPositionChanged(targetMotorPosition));

protected override Task ExecuteAsync(CancellationToken cancellationToken)
=> Task.WhenAll(
ListenToEncoderAsync(cancellationToken),
ListenForMotorInputsAsync(cancellationToken));

private Task ListenToEncoderAsync(CancellationToken cancellationToken)
{
while(!cancellationToken.IsCancellationRequested)
{
var gpioEvent = await _gpioController.WaitForEventAsync(cancellationToken);

var currentMotorPosition = ...

await _motorInputs.Writer.WriteAsync(MotorInput.MotorPositionChanged(currentMotorPosition));
}
}

private Task ListenForMotorInputsAsync(CancellationToken cancellationToken)
{
var currentMotorPosition = 0.0d;
var targetMotorPosition = 0.0d;
await foreach(var input = _motorInputs.ReadAllAsync(cancellationToken))
{
switch (input.Type)
{
case MotorInputType.CurrentPosition:
currentMotorPosition = input.Value;
break;

case MotorInputType.TargetPosition:
targetMotorPosition = input.Value;
break;
}

_pwm.Output = ...
}
}

private readonly Channel<MotorInput> _motorInputs;
}
possibly a bit over-engineered, but yeah now that I write it out, like Channels are cool but one or two SempahoreSlims would probably be more appropriate here
FacePalmGamer
FacePalmGamer2y ago
ok humor me. why does everyone use var. that is the dumbest thing to me. like its why python is an abomination. i want to know what type my variables are what is the point of making var x = 0.0d why not just double x = 0
JakenVeina
JakenVeina2y ago
personally because I mostly don't give a shit what the types are
FacePalmGamer
FacePalmGamer2y ago
i get using it in a pinch because of long datatypes but like for the double cmon
JakenVeina
JakenVeina2y ago
the vast majority of the time, the types are right, or shit doesn't compile the much better approach, IMO, is for the code to be self-descriptive var input = _motorInputs.ReadAllAsync() I can see at a glance that input is some kinda... motor input what specifically? I don't care if I DO care, I'll go look at the definition of that type I don't need the details of the definition of that type cluttering up the readability of this line of code same goes for currentMotorPosition and targetMotorPosition as far as readability goes, what the specific type of those variables is is irrelevant I care that I can perform operations on them if I want to know specifically, either for debugging or sanity checking, I can hover and make sure
FacePalmGamer
FacePalmGamer2y ago
i can see where u are coming form, but personally i will never use it
JakenVeina
JakenVeina2y ago
and that's valid
FacePalmGamer
FacePalmGamer2y ago
just was never sure if it was an industry thing or just a personal decision
JakenVeina
JakenVeina2y ago
little of both for me, it's probably also a function of me writing a LOT of JavaScript and TypeScript for work, alongside .NET
FacePalmGamer
FacePalmGamer2y ago
anyways back to this
JakenVeina
JakenVeina2y ago
where not only do I not WANT to know the type, half the time, I CAN'T know the type, for LUL
FacePalmGamer
FacePalmGamer2y ago
kek big hater of webdesign JS is terrible i cant do it
JakenVeina
JakenVeina2y ago
agreed
FacePalmGamer
FacePalmGamer2y ago
anyways in the code the channel is a read only, how does it get changed?
JakenVeina
JakenVeina2y ago
there's two spots where it's being written to _motorInputs.Writer.WriteAsync() which is exactly what we discussed the encoder listener writes to it
FacePalmGamer
FacePalmGamer2y ago
ah SetTargetMotorPositionAsync and ListenToEncoderAsync
JakenVeina
JakenVeina2y ago
and the caller from the main loop, yeah
FacePalmGamer
FacePalmGamer2y ago
i see that now
JakenVeina
JakenVeina2y ago
MotorInput is assumed to be like a private struct you could write along with a MotorInputType enum
FacePalmGamer
FacePalmGamer2y ago
what is calling the encoder function in this case
JakenVeina
JakenVeina2y ago
ListenToEncoderAsync()?
FacePalmGamer
FacePalmGamer2y ago
yeah
JakenVeina
JakenVeina2y ago
ExecuteAsync()
FacePalmGamer
FacePalmGamer2y ago
is it the ececuteasync ok so that is the service caller it constantly runs that correct?
JakenVeina
JakenVeina2y ago
logically, yes it's async, so whenever it hits an await for a Task that's incomplete, the method suspends as discussed earlier
FacePalmGamer
FacePalmGamer2y ago
public Task SetTargetMotorPositionAsync(double targetMotorPosition) => _motorInputs.Writer.WriteAsync(MotorInput.TargetPositionChanged(targetMotorPosition)); is there any reason to make this async?
JakenVeina
JakenVeina2y ago
yes
FacePalmGamer
FacePalmGamer2y ago
its just setting a double
JakenVeina
JakenVeina2y ago
it's doing it thread-safely it makes the caller wait if the channel is currently in the middle of being read from, for example and whenever you want something to wait, you want async in reality, that's an await that will virtually always complete immediately, and thus not require a suspension like
FacePalmGamer
FacePalmGamer2y ago
but the writer is writing async and you arent awaiting it. so are you just awaiting it in the main loop?
JakenVeina
JakenVeina2y ago
reading from the channel causes it to momentarily lock as would writing to it well, yeah I don't need to await it if I'm just returning the Task directly
public async Task DoSomethingAsync()
=> await DoSomethingElseAsync();
public async Task DoSomethingAsync()
=> await DoSomethingElseAsync();
the await here is redundant
public Task DoSomethingAsync()
=> DoSomethingElseAsync();
public Task DoSomethingAsync()
=> DoSomethingElseAsync();
functionally equivalent
FacePalmGamer
FacePalmGamer2y ago
wasnt sure if you even needed to await it or return the task, because main doesnt really care if that is done as long as it happens
JakenVeina
JakenVeina2y ago
the main loop would await SetTargetPositionAsync() regardless unless the main loop also has the opportunity to elide it which it won't
FacePalmGamer
FacePalmGamer2y ago
yeah, but you could just make it iterative and just have that line. its gonna be called then immediately awaited
JakenVeina
JakenVeina2y ago
because the main loop is a loop, and thus has both pre-await and post-await actions to take an await can be elided if it's the only one within a method, and there are no post-await actions that need to be taken I.E. the method doesn't actually care about the completion of the Task
FacePalmGamer
FacePalmGamer2y ago
alright. I have the event method, and the channel await method I will look more into doing this DI thing, and setting up the service app builder thanks for all the info its really helpful
JakenVeina
JakenVeina2y ago
read the Microsoft docs for the hosting platform and for DI
FacePalmGamer
FacePalmGamer2y ago
was planning on it
JakenVeina
JakenVeina2y ago
.NET Generic Host - .NET
Learn about the .NET Generic Host, which is responsible for app startup and lifetime management.
JakenVeina
JakenVeina2y ago
Dependency injection - .NET
Learn how to use dependency injection within your .NET apps. Discover how to registration services, define service lifetimes, and express dependencies in C#.
JakenVeina
JakenVeina2y ago
and if you don't feel like using these libs for this project, feel free not to they're just tools useful tools, but if you're not comfortable, if you just wanna focus on the project with what you already know like nothing wrong with that
FacePalmGamer
FacePalmGamer2y ago
i mean after you basically gave me the motor service i just need to figure out how to do the injections then build it, and building it doesn't seem to be hard i got a month to get this working plenty of time lol
JakenVeina
JakenVeina2y ago
o7
FacePalmGamer
FacePalmGamer2y ago
i also have to write a library for an IMU but thats a different problem never written one before but got some info on it yesterday from some people in #help-0 using the binary reader writer and primitive library, so just gotta learn those as well and write a bunch of registries and decode data couldn't possibly be that hard (is what i will keep telling myself until its done)
JakenVeina
JakenVeina2y ago
sounds fun, actually I haven't done low-level stuff like that in a long time would be interesting to do in C#
FacePalmGamer
FacePalmGamer2y ago
i mean i want to learn how to do it for sure, but i have like almost no place to start there is already a library for it in python and C so i was gonna direct convert it 1:1 from C and got told no the other day so now im starting from scratch making it myself
JakenVeina
JakenVeina2y ago
like, someone here said that?
FacePalmGamer
FacePalmGamer2y ago
in the help chat yeah
JakenVeina
JakenVeina2y ago
like, you were gonna just take the C code and convert it to C# yeah, lol, not quite so simple
FacePalmGamer
FacePalmGamer2y ago
because i was asking about an equivalent to Unions from C
JakenVeina
JakenVeina2y ago
loooooooooooool
FacePalmGamer
FacePalmGamer2y ago
yeah pointers are dumb
JakenVeina
JakenVeina2y ago
the most effective bet would probably be to P/Invoke into the C library which is a bit outside my expertise I only really know how that stuff works in theory
FacePalmGamer
FacePalmGamer2y ago
i will keep that in mind as a possible solution if im out of time i have an older sensor i can throw in if i run out, but ideally i get this working
JakenVeina
JakenVeina2y ago
but in theory, you should just be able to load the DLL compiled from C, and call methods within it
FacePalmGamer
FacePalmGamer2y ago
I have 3 people on my team helping me, so ill probably hand the motor off to someone and focus more on that since now i can point them in a direction and can reference this chat
JakenVeina
JakenVeina2y ago
well, good luck
FacePalmGamer
FacePalmGamer2y ago
tyvm, and thanks again for all the info
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity. Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity. Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server