Scott Hanselman

A multi-player server-side GameBoy Emulator written in .NET Core and Angular

March 5, '18 Comments [4] Posted in Docker | DotNetCore | Javascript | Open Source
Sponsored By

Server-side GameBoyOne of the great joys of sharing and discovering code online is when you stumble upon something so truly epic, so amazing, that you have to dig in. Head over to https://github.com/axle-h/Retro.Net and ask yourself why this GitHub project has only 20 stars?

Alex Haslehurst has created some retro hardware libraries in open source .NET Core with an Angular Front End!

Translation?

A multiplayer server-side Game Boy emulator. Epic.

You can run it in minutes with

docker run -p 2500:2500 alexhaslehurst/server-side-gameboy

Then just browse to http://localhost:2500 and play Tetris on the original GameBoy!

I love this for a number of reasons.

First, I love his perspective:

Please check out my GameBoy emulator written in .NET Core; Retro.Net. Yes, a GameBoy emulator written in .NET Core. Why? Why not. I plan to do a few write-ups about my experience with this project. Firstly: why it was a bad idea.

  1. Emulation on .NET
  2. Emulating the GameBoy CPU on .NET

The biggest issue one has trying to emulate a CPU with a platform like .NET is the lack of reliable high-precision timing. However, he manages a nice from-scratch emulation of the Z80 processor, modeling low level things like registers in very high level C#. I love that public class GameBoyFlagsRegister is a thing. ;) I did similar things when I ported a 15 year old "Tiny CPU" to .NET Core/C#.

Address space diagram from https://ax-h.com/software/development/emulation/2017/12/03/emulating-the-gameboy-cpu-on-dot-net.html

Be sure to check out Alex's extremely detailed explanation on how he modeled the Z80 microprocessor.

Luckily the GameBoy CPU, a Sharp LR35902, is derived from the popular and very well documented Zilog Z80 - A microprocessor that is unbelievably still in production today, over 40 years after it’s introduction.

The Z80 is an 8-bit microprocessor, meaning that each operation is natively performed on a single byte. The instruction set does have some 16-bit operations but these are just executed as multiple cycles of 8-bit logic. The Z80 has a 16-bit wide address bus, which logically represents a 64K memory map. Data is transferred to the CPU over an 8-bit wide data bus but this is irrelevant to simulating the system at state machine level. The Z80 and the Intel 8080 that it derives from have 256 I/O ports for accessing external peripherals but the GameBoy CPU has none - favouring memory mapped I/O instead

He didn't just create an emulator - there's lots of those - but uniquely he runs it on the server-side while allowing shared controls in a browser. "In between each unique frame, all connected clients can vote on what the next control input should be. The server will choose the one with the most votes… most of the time." Massively multi-player online GameBoy! Then he streams out the next frame! "GPU rendering is completed on the server once per unique frame, compressed with LZ4 and streamed out to all connected clients over websockets."

This is a great learning repository because:

  • it has complex business logic on the server-side but the front end uses Angular and web-sockets and open web technologies.
  • It's also nice that he has a complete multi-stage Dockerfile that is itself a great example of how to build both .NET Core and Angular apps in Docker.
  • Extensive (thousands) of Unit Tests with the Shouldly Assertion Framework and Moq Mocking Framework.
  • Great example usages of Reactive Programming
  • Unit Testing on both server AND client, using Karma Unit Testing for Angular

Here's a few favorite elegant code snippets in this huge repository.

The Reactive Button Presses:

_joyPadSubscription = _joyPadSubject
    .Buffer(FrameLength)
    .Where(x => x.Any())
    .Subscribe(presses =>
                {
                    var (button, name) = presses
                        .Where(x => !string.IsNullOrEmpty(x.name))
                        .GroupBy(x => x.button)
                        .OrderByDescending(grp => grp.Count())
                        .Select(grp => (button: grp.Key, name: grp.Select(x => x.name).First()))
                        .FirstOrDefault();
                    joyPad.PressOne(button);
                    Publish(name, $"Pressed {button}");

                    Thread.Sleep(ButtonPressLength);
                    joyPad.ReleaseAll();
                });

The GPU Renderer:

private void Paint()
{
    var renderSettings = new RenderSettings(_gpuRegisters);

    var backgroundTileMap = _tileRam.ReadBytes(renderSettings.BackgroundTileMapAddress, 0x400);
    var tileSet = _tileRam.ReadBytes(renderSettings.TileSetAddress, 0x1000);
    var windowTileMap = renderSettings.WindowEnabled ? _tileRam.ReadBytes(renderSettings.WindowTileMapAddress, 0x400) : new byte[0];

    byte[] spriteOam, spriteTileSet;
    if (renderSettings.SpritesEnabled) {
        // If the background tiles are read from the sprite pattern table then we can reuse the bytes.
        spriteTileSet = renderSettings.SpriteAndBackgroundTileSetShared ? tileSet : _tileRam.ReadBytes(0x0, 0x1000);
        spriteOam = _spriteRam.ReadBytes(0x0, 0xa0);
    }
    else {
        spriteOam = spriteTileSet = new byte[0];
    }

    var renderState = new RenderState(renderSettings, tileSet, backgroundTileMap, windowTileMap, spriteOam, spriteTileSet);

    var renderStateChange = renderState.GetRenderStateChange(_lastRenderState);
    if (renderStateChange == RenderStateChange.None) {
        // No need to render the same frame twice.
        _frameSkip = 0;
        _framesRendered++;
        return;
    }

    _lastRenderState = renderState;
    _tileMapPointer = _tileMapPointer == null ? new TileMapPointer(renderState) : _tileMapPointer.Reset(renderState, renderStateChange);
    var bitmapPalette = _gpuRegisters.LcdMonochromePaletteRegister.Pallette;
    for (var y = 0; y < LcdHeight; y++) {
        for (var x = 0; x < LcdWidth; x++) {
            _lcdBuffer.SetPixel(x, y, (byte) bitmapPalette[_tileMapPointer.Pixel]);

            if (x + 1 < LcdWidth) {
                _tileMapPointer.NextColumn();
            }
        }

        if (y + 1 < LcdHeight){
            _tileMapPointer.NextRow();
        }
    }
    
    _renderer.Paint(_lcdBuffer);
    _frameSkip = 0;
    _framesRendered++;
}

The GameBoy Frames are composed on the server side then compressed and sent to the client over WebSockets. He's got backgrounds and sprites working, and there's still work to be done.

The Raw LCD is an HTML5 canvas:

<canvas #rawLcd [width]="lcdWidth" [height]="lcdHeight" class="d-none"></canvas>
<canvas #lcd
        [style.max-width]="maxWidth + 'px'"
        [style.max-height]="maxHeight + 'px'"
        [style.min-width]="minWidth + 'px'"
        [style.min-height]="minHeight + 'px'"
        class="lcd"></canvas>

I love this whole project because it has everything. TypeScript, 2D JavaScript Canvas, retro-gaming, and so much more!

const raw: HTMLCanvasElement = this.rawLcdCanvas.nativeElement;
const rawContext: CanvasRenderingContext2D = raw.getContext("2d");
const img = rawContext.createImageData(this.lcdWidth, this.lcdHeight);

for (let y = 0; y < this.lcdHeight; y++) {
  for (let x = 0; x < this.lcdWidth; x++) {
    const index = y * this.lcdWidth + x;
    const imgIndex = index * 4;
    const colourIndex = this.service.frame[index];
    if (colourIndex < 0 || colourIndex >= colours.length) {
      throw new Error("Unknown colour: " + colourIndex);
    }

    const colour = colours[colourIndex];

    img.data[imgIndex] = colour.red;
    img.data[imgIndex + 1] = colour.green;
    img.data[imgIndex + 2] = colour.blue;
    img.data[imgIndex + 3] = 255;
  }
}
rawContext.putImageData(img, 0, 0);

context.drawImage(raw, lcdX, lcdY, lcdW, lcdH);

I would encourage you to go STAR and CLONE https://github.com/axle-h/Retro.Net and give it a run with Docker! You can then use Visual Studio Code and .NET Core to compile and run it locally. He's looking for help with GameBoy sound and a Debugger.


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Running ASP.NET Core on GoDaddy's cheapest shared Linux Hosting - Don't Try This At Home

March 1, '18 Comments [18] Posted in DotNetCore | Linux | Open Source
Sponsored By

First, a disclaimer. Don't do this. I did this to test a theory and to prove a point. ASP.NET Core and the .NET Core that it runs on are open source and run pretty much anywhere. I wanted to see if I could run an ASP.NET Core site on GoDaddy's cheapest hosting ($3, although it scales to $8) that basically supports only PHP. It's not a full Linux VM. It's locked-down and limited. You don't have root. You are missing most tools you'd expect you'd have.

BUT.

I wanted to see if I could get ASP.NET Core running on it anyway. Maybe if I do, they (and other inexpensive hosts) will talk to the .NET team, learn that ASP.NET Core is open source and could easily run on their existing infrastructure.

AGAIN: Don't do this. It's hacky. It's silly. But it's hella cool. IMHO. Also, big thanks to Tomas Weinfurt for his help!

First, I went to GoDaddy and signed up for their cheap hosting. Again, not a VM, but their shared one. I also registered supercheapaspnetsite.com as well. They use a cPanel-based web management system that doesn't really let you do anything. You can turn on SSH, do some PHP stuff, and generally poke around, but it's not exactly low-level.

First I ssh (shoosh!) in and see what I'm working with. I'm shooshing with Ubuntu on Windows 10 feature, that every developer should turn on. It's makes it really easy to work with Linux hosts if you're starting from Linux on Windows 10.

secretname@theirvmname [/proc]$ cat version
Linux version 2.6.32-773.26.1.lve1.4.46.el6.x86_64 (mockbuild@build.cloudlinux.com) (gcc version 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) ) #1 SMP Tue Dec 5 18:55:41 EST 2017
secretname@theirvmname [/proc]$

OK, looks like Red Hat, so CentOS 6 should be compatible.

I'm going to use .NET Core 2.1 (which is in preview now!) and get the SDK at https://www.microsoft.com/net/download/all and install it on my Windows machine where I will develop and build the app. I don't NEED to use Windows to do this, but it's the laptop I have and it's also nice to know I can build on Windows but target CentOS/RHEL6.

Next I'll make a new ASP.NET site with

dotnet new razor

and then I'll publish a self-contained version like this:

dotnet publish -r rhel.6-x64

And those files will end up in a folder like \supercheapaspnetsite\bin\Debug\netcoreapp2.1\rhel.6-x64\publish\

NOTE: You may need to add the NuGet feed for the dailies for this .NET Core preview in order to get the RHEL6 runtime downloaded during this local publish.

Then I used WinSCP (or whatever FTP/SCP client you like, rsync, etc) to get the files over to the ~/www folder on your GoDaddy shared site. Then I

chmod +x ./supercheapasnetsite

to make it executable. Now, from my ssh session at GoDaddy, let's try to run my app!

secretname@theirvmname [~/www]$ ./supercheapaspnetsite
Failed to load hb, error: libunwind.so.8: cannot open shared object file: No such file or directory
Failed to bind to CoreCLR at '/home/secretname/public_html/libcoreclr.so'

Of course it couldn't be that easy, right? .NET Core wants the unwind library (shared object) and it doesn't exist on this locked down system.

AND I don't have yum/apt/rpm or a way to install it right?

I could go looking for tar.gz file somewhere like this http://download.savannah.nongnu.org/releases/libunwind/ but I need to think about versions and make sure things line up. Given that I'm targeting CentOS6, I should start here https://centos.pkgs.org/6/epel-x86_64/libunwind-1.1-3.el6.x86_64.rpm.html and download libunwind-1.1-3.el6.x86_64.rpm.

I need to crack open that rpm file and get the library. RPM packages are just headers on top of a CPIO archive, so I can apt-get install rpm2cpio from my local Ubuntu instances (on Windows 10). Then from /mnt/c/users/scott/Downloads (where I downloaded the file) I will extract it.

rpm2cpio ./libunwind-1.1-3.el6.x86_64.rpm | cpio -idmv

There they are.

image

This part is cool. Even though I have these files, I don't have root or any way to "install" them. However I could either export/use the LD_LIBRARY_PATH environment variable to control how libraries get loaded OR I could put these files in $ORIGIN/netcoredeps. You can read more about Self Contained Linux Applications on .NET Core here.

The main executable of published .NET Core applications (which is the .NET Core host) has an RPATH property set to $ORIGIN/netcoredeps. That means that when Linux shared library loader is looking for shared libraries, it looks to this location before looking to default shared library locations. It is worth noting that the paths specified by the LD_LIBRARY_PATHenvironment variable or libraries specified by the LD_PRELOAD environment variable are still used before the RPATH property. So, in order to use local copies of the third-party libraries, developers need to create a directory named netcoredeps next to the main application executable and copy all the necessary dependencies into it.

At this point I've added a "netcoredeps" folder to my public folder, and then copied it (scp) over to GoDaddy. Let's run it again.

secretname@theirvmname [~/www]$ ./supercheapaspnetsite
FailFast: Couldn't find a valid ICU package installed on the system. Set the configuration flag System.Globalization.Invariant to true if you want to run with no globalization support.

   at System.Environment.FailFast(System.String)
   at System.Globalization.GlobalizationMode.GetGlobalizationInvariantMode()
   at System.Globalization.GlobalizationMode..cctor()
   at System.Globalization.CultureData.CreateCultureWithInvariantData()
   at System.Globalization.CultureData.get_Invariant()
   at System.Globalization.CultureInfo..cctor()
   at System.StringComparer..cctor()
   at System.AppDomain.InitializeCompatibilityFlags()
   at System.AppDomain.Setup(System.Object)
Aborted

Ok, now it's complaining about ICU packages. These are for globalization. That is also mentioned in the self-contained-linux apps docs and there's a precompiled binary I could download. But there's options.

If your app doesn't explicitly opt out of using globalization, you also need to add libicuuc.so.{version}, libicui18n.so.{version}, and libicudata.so.{version}

I like "opt-out" so I don't have to go dig these ups (although I could) so I can either set the CORECLR_GLOBAL_INVARIANT env var to 1, or I can add System.Globalization.Invariant = true to supercheapaspnetsite.runtimeconfig.json, which I'll do with just to be obnoxious. ;)

When I run it again I get another complained about libuv. Yet another shared library that isn't installed on this instance. I could  go get it and put it in netcoredeps OR since I'm using the .NET Core 2.1, I could try something new. There were some improvements made in .NET Core 2.1 around sockets and http performance. On the client side, these new managed libraries are written from the ground up in managed code using the new high-performance Span<T> and on the server-side I could use Kestrel's (Kestrel is the .NET Core webserver) experimental UseSockets() as they are starting to move that over.

In other words, I can bypass libuv usage entirely by changing my Program.cs to use the use UseSockets() like this.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
     WebHost.CreateDefaultBuilder(args)
     .UseSockets()
     .UseStartup<Startup>();

Let's run it again. I'll add the ASPNETCORE_URLS environment variable and set it to a high port like 8080. Remember, I'm not admin so I can't use any port under 1024.

secretname@theirvmname [~/www]$ export ASPNETCORE_URLS="http://*:8080"
secretname@theirvmname [~/www]$ ./supercheapaspnetsite
Hosting environment: Production
Content root path: /home/secretname/public_html
Now listening on: http://0.0.0.0:8080
Application started. Press Ctrl+C to shut down.

Holy crap it actually started.

Ok, but I can't access it from supercheapaspnetsite.com:8080 because this is GoDaddy's locked down managed shared hosting. I can't just open a port or forward a port in their control panel.

But. They use Apache, and that has the .htaccess file!

Could I use mod_proxy and try this?

ProxyPassReverse / http://127.0.0.1:8080/

Looks like no, they haven't turned this on. Likely they don't want to proxy off to external domains, but it'd be nice if they allowed localhost. Bummer. So close.

Fine, I'll proxy the traffic myself. (Not perfect, but this is all a spike)

RewriteRule ^(.*)$  "show.php" [L]

Cool, now a cheesy proxy goes in show.php.

<?php
$site = 'http://127.0.0.1:8080';
$request = $_SERVER['REQUEST_URI'];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $site . $request);
curl_setopt($ch, CURLOPT_HEADER, TRUE);
$f = fopen("headers.txt", "a");
    curl_setopt($ch, CURLOPT_VERBOSE, 0);
    curl_setopt($ch, CURLOPT_STDERR, $f);
    #don't output curl response, I need to strip the headers.
    #yes I know I can just CURLOPT_HEADER, false and all this 
    # goes away, but for testing we log headers
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$hold = curl_exec($ch);

#strip headers
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($hold, 0, $header_size);
$response = substr($hold, $header_size);
$headerArray = explode(PHP_EOL, $headers);

echo $response; #echo ourselves. Yes I know curl can do this for us.
?>

Cheesy, yes. Works for GET? Also, yes. This really is Apache's job, not ours, but kudos to Tomas for this evil idea.

An ASP.NET Core app at a host that doesn't support it

Boom. How about another page at /about? Yes.

Another page with ASP.NET Core at a host that doesn't support it

Lovely. But I had to run the app myself. I have no supervisor or process manager (again this is already handled by GoDaddy for PHP but I'm in unprivileged world.) Shooshing in and running it is a bad idea and not sustainable. (Well, this whole thing is not sustainable, but still.)

We could copy "screen" over and start it up and detach like use screen ./supercheapaspnet app, but again, if it crashes, no one will start it. We do have crontab, so for now, we'll launch the app on a schedule occasionally to do a health check and if needed, keep it running. Also added a few debugging tools in ~/bin:

secretname@theirvmname [~/bin]$ ll
total 304
drwxrwxr-x  2    4096 Feb 28 20:13 ./
drwx--x--x 20    4096 Mar  1 01:32 ../
-rwxr-xr-x  1  150776 Feb 28 20:10 lsof*
-rwxr-xr-x  1   21816 Feb 28 20:13 nc*
-rwxr-xr-x  1  123360 Feb 28 20:07 netstat*

All in all, not that hard. ASP.NET Core and .NET Core underneath it can run pretty much anywhere, just like PHP, Python, whatever.

If you're a host and you want to talk to someone at Microsoft about setting up ASP.NET Core shared hosting, email Sourabh.Shirhatti@microsoft.com and talk to them! If you are GoDaddy, I apologize, and you should also email. ;)


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

The Squishy Side of Open Source

February 21, '18 Comments [9] Posted in Open Source
Sponsored By

The Squishy Side of Open SourceA few months back my friend Keeley Hammond and I did a workshop for Women Who Code Portland called The Squishy Side of Open Source. We'd done a number of workshops before on how to use Git and the Command Line, and I've done a documentary film with Rob Conery called Get Involved In Tech: The Social Developer (watch it free!) but Keeley and I wanted to really dive into the interpersonal "soft" or squishy parts. We think that we all need to work to bring kindness back into open source.

Contributing to open source for the first time can be scary and a little overwhelming. In addition to the technical skills required, the social dynamics of contributing to a library and participating in a code review can seem strange.

That means how people talk to each other, what to do when pull requests go south, when issues heat up due to misunderstandings,

Keeley has published the deck up on SpeakerDeck. In this workshop, we talked about the work and details that go into maintaining an open source community, tell real stories from his experiences and go over what to expect when contributing to open source and how to navigate it.

Key Takeaways:

  • Understanding the work that open source maintainers do, and how to show respect for them.
  • Understanding Codes of Conduct and Style Guides for OSS repos and how to abide by them.
  • Tips for communicating clearly, and dealing with uncomfortable or hostile communication.

Good communication is a key part of contributing to open source.

  • Give context.
  • Do your homework beforehand. It’s OK not to know things, but before asking for help, check a project’s README, documentation, issues (open or closed) and search the internet for an answer.
  • Keep requests short and direct. Many projects have more incoming requests than people available to help. Be concise.
  • Keep all communication public.
  • It’s okay to ask questions (but be patient!). Show them the same patience that you’d want them to show to you.
Keep it classy. Context gets lost across languages, cultures, geographies, and time zones. Assume good intentions in these conversations.

Where to start?

What are some good resources you've found for understanding the squishy side of open source?


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

How to set up Kubernetes on Windows 10 with Docker for Windows and run ASP.NET Core

January 30, '18 Comments [9] Posted in DotNetCore | Kubernetes | Open Source
Sponsored By

Docker for Windows is really coming along nicely. They have both a Stable and Edge channel and the Edge (beta, experimental) one just included a lovely new feature - Kubernetes support. Per their docs, Kubernetes is only available in Docker for Windows 18.02 CE Edge. They set most everything up nicely and put Kubectl into your path and setup a context. If you use kubectl for other things - like your own Raspberry Pi Kubernetes Cluster, then you'll need to be aware of switching contexts. Same thing applies if you have one in the cloud, like the Kubernetes Cluster I made in Azure AKS.

Got Docker for Windows? If you have not yet installed Docker for Windows, see Install Docker for Windows for an explanation of stable and edge channels, system requirements, and download/install information.

It's easy to get started, just click "Enable Kubernetes" and Docker for Windows will download and start the images you need. I clicked "show system containers" because I like to see what's hidden from me, but you decide for yourself. Do be aware - there's a TON.

Enabling Kubernetes in Docker for Windows

By default, you won't get the Kubernetes Dashboard - of which I'm a fan - so you may want to install that. If you follow the default instructions (and you're a noob like me) then you'll likely end up with a Dashboard that is pretty locked down. It can be somewhat frustrating to get access to your own development dashboard, so I use the alternative (read: totally insecure) dashboard, like this:

C:\> kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/alternative/kubernetes-dashboard.yaml

I also like charts and graphs so I added these as well:

C:\> kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/influxdb.yaml
C:\> kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/heapster.yaml
C:\> kubectl create -f https://raw.githubusercontent.com/kubernetes/heapster/master/deploy/kube-config/influxdb/grafana.yaml

I can access the dashboard by default by running "kubectl proxy" then visiting this http://localhost:8001/ui and I'll get redirected to the dashboard:

Kuberenetes Dashboard

Now I can run through all the cool Kubernetes tutorials like the Guestbook Kubernetes Sample Application from the convenience of my Windows 10 machine. (I'm running a SurfaceBook 2 on the current non-Beta Windows 10.)

There are a lot of nice samples on running .NET Core and ASP.NET Core apps with Docker up at https://github.com/dotnet/dotnet-docker-samples/

I made a quick ASP.NET Core app called kubeaspnetapp:

C:\Users\scott\Desktop>dotnet new razor -o kubeaspnetapp
The template "ASP.NET Core Web App" was created successfully.
...snip...
Restore succeeded.

Then added a two-stage build DockerFile that looks like this:

FROM microsoft/aspnetcore-build:2.0 AS build-env
WORKDIR /app

# copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# build runtime image
FROM microsoft/aspnetcore:2.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "kubeaspnetapp.dll"]

And built and tagged the image with:

C:\Users\scott\Desktop\kubeaspnetapp>docker build -t kubeaspnetapp:v1 .

Then I create a quick Deployment that manages a Pod that runs the Container:

C:\Users\scott\Desktop\kubeaspnetapp>kubectl run kubeaspnetapp --image=kubeaspnetapp:v1 --port=80
deployment "kubeaspnetapp" created

Now I'll expose it to the "outside." Again, this is usually done with .yaml files but it's a good learning exercise and it's all local.

C:\Users\scott\Desktop\kubeaspnetapp>kubectl get deployments
NAME            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kubeaspnetapp   1         1         1            1           1m

C:\Users\scott\Desktop\kubeaspnetapp>kubectl get pods
NAME                             READY     STATUS    RESTARTS   AGE
kubeaspnetapp-778f6d49bd-rct59   1/1       Running   0          1m

C:\Users\scott\Desktop\kubeaspnetapp>
C:\Users\scott\Desktop\kubeaspnetapp>
C:\Users\scott\Desktop\kubeaspnetapp>kubectl expose deployment kubeaspnetapp --type=NodePort
service "kubeaspnetapp" exposed

C:\Users\scott\Desktop\kubeaspnetapp>kubectl get services
NAME            TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubeaspnetapp   LoadBalancer   10.98.234.67   <pending>     80:31756/TCP     5s
kubernetes      ClusterIP      10.96.0.1      <none>        443/TCP          1d

Then I'll hit http://127.0.0.1:31756 in my browser...note how that port is brokering to the internal port 80 where the app listens...and there's my ASP.NET Core app running locally on Kubernetes, set up with Docker for Windows. Nice.

My ASP.NET Core app running in Kubernetes local on my Windows 10 machine

Here's me getting the startup logs from that pod:

C:\Users\scott\>kubectl get pods
NAME                             READY     STATUS    RESTARTS   AGE
kubeaspnetapp-7fd7f7ffb9-8gnzd   1/1       Running   0          6m

C:\Users\scott\Dropbox\k8s for pi\aspnetcoreapp>kubectl logs kubeaspnetapp-7fd7f7ffb9-8gnzd
Hosting environment: Production
Content root path: /app
Now listening on: http://[::]:80
Application started. Press Ctrl+C to shut down.

Pretty cool. As all the tooling and things across Windows, Docker, Kubernetes, Visual Studio (all flavors) continues to get better and better, I can only imagine this experience will get better and better. I look forward to a time when I can freely mix containers from different OSs and easily push them all en masse to Azure.


Sponsor: Get the latest JetBrains Rider for debugging third-party .NET code, Smart Step Into, more debugger improvements, C# Interactive, new project wizard, and formatting code in columns.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Building a Raspberry Pi Car Robot with WiFi and Video

January 29, '18 Comments [7] Posted in Hardware | Open Source
Sponsored By

The SunFounder Raspberry Pi Car kit comes wtih everything you need except the 18650 batteries. You'll need to get those elsewhere.Last year I found a company called SunFounder that makes great Raspberry Pi-related kits and stuff. I got their Raspberry Pi 10" Touchscreen LCD and enjoyed it very much. This month I picked up the SunFounder PiCar 2.0 kit and built it with the kids. The kit includes everything you need except for the Raspberry Pi itself, a mini SD Card (the Pi uses that as  hard drive), and two 18650 rechargeable lithium batteries. Those batteries are enough to power both the Pi itself (so the car isn't tethered) as well as provide enough voltage to run the 3 servos AND motors to drive and steer the car around. You can also expand the car with other attachments like light sensors, line followers, and more.

The PiCar 2.0 includes the chassis, a nice USB WiFi adapter with antenna (one less thing to think about if you're using a Raspberry Pi  like me), a USB webcam for computer vision scenarios. It includes a TB6612 Motor Driver, PCA9685 PWM (Pulse Width Modulation) Servo Driver with 16 channels for future expansion. The kit also helpfully includes all the tools, screwdriver, wrenches, and bolts.

Preparing to build the SunFounder Raspberry Pi car

All the code for the SunFounder PiCar-V is on GitHub and while there can be a few hiccups with some of the English instructions, there are a bunch of YouTube videos and folks online doing the same thing so we had no trouble making the robot in a weekend.

Building a SunFounder Raspberry Pi Car

PRO TIP - Boot your new Raspberry Pi up with ssh enabled and already joined to your wifi

You'll need to use a tool like Etcher.io to burn a copy of the Raspbian operating system on to a mini SD card. I prefer to save time and avoid having to connect a new Raspberry Pi to HDMI and a mouse and keyboard, so I get the Pi onto my wifi network and enable SSH by copying these two files to the root of the file system of the freshly burned mini SD card. This will cause the Pi to automatically join your network when it boots up for the first time. Then I used Ubuntu on Windows 10 to ssh into the Pi and follow the instructions.

  • Make a 0 byte file called "ssh" and copy it to the root of the new PI disk
  • Make a file called "wpa_supplicant.conf" with just linefeeds at the end and make it look like this. Copy it to the root of the new PI disk.
country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="YOURWIFI"
    scan_ssid=1
    psk="yourwifipassword"
    key_mgmt=WPA-PSK
}

I like to use Notepad2 or Visual Studio Code to change the line endings of a file. You can see the CRLF or the LF in the status car and click it. Unix/Raspbian/Raspberry Pi likes just an LF (line feed) for the lineending, while Windows defaults to using CRLF (Carriage Return/Line Feed, or 0x13 0x10) for text files.

Changing the line endings to Unix

The default Raspberry username is pi and the default password is raspberry. You may want to change that. SunFounder has a decent "install_dependencies" script that you'll run on the Pi:

Installing the PiCar dependencies

Once you've built the PiCar you can ssh in and run their development server that gives you a little WebAPI to control the car. The SunFounder folks are pretty good at web development (less so with mobile apps) and have a nice Django app to control the PiCar.

Here's the view from the front camera of the PiCar as viewed through local website on port 8000. It's looking at my computer looking at itself. ;)

Viewing the PiCar camera through the Django website

You're able to control the PiCar from this web interface with the keyboard. You can move the car and steer with WASD, as well as move the head/camera independently. You will need to enter the settings area (upper right corner) and calibrate the back wheels direction. By default, one wheel may go the opposite direction because they can't be sure how you mounted them, so you'll need to reverse one wheel to ensure they both go in the same direction.

They also included a client application, also written in Python. On Windows you'll need to install Python, and when you run client.py you may get an error:

ImportError: No module named requests

You'll need to run "pip3 install requests" as that module isn't installed by default.

Additionally, Python apps aren't smart about High-DPI displays, so I went to C:\Users\scott\appdata\local\Programs\Python\Python36 and right click'ed the Python.exe and set the DPI setting to "System (Enhanced)" like this.

Overridding DPI settings to System (Enhanced)

The client app is best for "Zeroing out" the camera and wheels, in case they are favoring one side or the other.

image

All in all, building the SunFounder "Raspberry Pi Video Car Kit 2.0" with the kids was a great experience. The next step is to see what else we can do with it!

  • Add a speaker so it talks?
  • Add Alexa support so you can talk to it?
  • Make the car drive around and take pictures, then use Azure cognitive services to announce what it sees?
  • Or, as my little boys say, "add weapons and make another bot for it to fight!"

What do you think?

* I use Amazon affiliate links and appreciate it when you use them! It supports this blog and sometimes gives me enough money to buy gadgets like this!


Sponsor: Unleash a faster Python! Supercharge your applications performance on future forward Intel® platforms with The Intel® Distribution for Python. Available for Windows, Linux, and macOS. Get the Intel® Distribution for Python Now!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb
Previous Page Page 2 of 48 in the Open Source category Next Page

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.