Whois information without the pushy hard sell tactics

Did you ever want to learn about a domain registration but were put off by the hard sell tactics that basically all web-based whois searches subject you to? Me, too. Here’s what you can do.

The details
Linux – so that includes you, Raspberry Pi owners – has a little utility called whois which you can use to get the registrant information of a domain, e.g.,

$ whois johnstechtalk.com

   Registry Domain ID: 1795918838_DOMAIN_COM-VRSN
   Registrar WHOIS Server: whois.godaddy.com
   Registrar URL: http://www.godaddy.com
   Updated Date: 2017-03-27T00:52:51Z
   Creation Date: 2013-04-23T00:54:17Z
   Registry Expiry Date: 2019-04-23T00:54:17Z
   Registrar: GoDaddy.com, LLC
   Registrar IANA ID: 146
   Registrar Abuse Contact Email: abuse@godaddy.com
   Registrar Abuse Contact Phone: 480-624-2505
   Domain Status: clientDeleteProhibited https://icann.org/epp#clientDeleteProhibited
   Domain Status: clientRenewProhibited https://icann.org/epp#clientRenewProhibited
   Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
   Domain Status: clientUpdateProhibited https://icann.org/epp#clientUpdateProhibited
   DNSSEC: unsigned
   URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of whois database: 2018-04-19T19:59:35Z <<<

Admittedly that did not tell us much, but it points us to another whois server we can try, whois.godaddy.com. So try that:

$ whois ‐h whois.godaddy.com johnstechtalk.com

Registry Domain ID: 1795918838_DOMAIN_COM-VRSN
Registrar WHOIS Server: whois.godaddy.com
Registrar URL: http://www.godaddy.com
Updated Date: 2017-03-27T00:52:50Z
Creation Date: 2013-04-23T00:54:17Z
Registrar Registration Expiration Date: 2019-04-23T00:54:17Z
Registrar: GoDaddy.com, LLC
Registrar IANA ID: 146
Registrar Abuse Contact Email: abuse@godaddy.com
Registrar Abuse Contact Phone: +1.4806242505
Domain Status: clientTransferProhibited http://www.icann.org/epp#clientTransferProhibited
Domain Status: clientUpdateProhibited http://www.icann.org/epp#clientUpdateProhibited
Domain Status: clientRenewProhibited http://www.icann.org/epp#clientRenewProhibited
Domain Status: clientDeleteProhibited http://www.icann.org/epp#clientDeleteProhibited
Registry Registrant ID: Not Available From Registry
Registrant Name: ******** ******** (see Notes section below on how to view unmasked data)
Registrant Organization:
Registrant Street: ***** ****
Registrant City: Newton
Registrant State/Province: New Jersey
Registrant Postal Code: 078**
Registrant Country: US
Registrant Phone: +*.**********
Registrant Phone Ext:
Registrant Fax:
Registrant Fax Ext:
Registrant Email: ********@*****.***
Registry Admin ID: Not Available From Registry
Admin Name: ******** ******** (see Notes section below on how to view unmasked data)

So now we’re getting somewhere. So GoDaddy tries to force you to their web page an sell you stuff in any case. Not at all surprising for anyone who’s ever been a GoDaddy customer (includes yours truly). Because that’s what they do. But not all registrars do that.

Here’s a real-life example which made me decide this technique should be more broadly disseminated. I searched for information on a domain in Argentina:

$ whois buenosaires.com.ar

This TLD has no whois server, but you can access the whois database at

Now if you actually try their suggested whois server, it doesn’t even work:

$ whois ‐h www.nic.ar buenosaires.com.ar


What you can do to find the correct whois server is use iana – Internet Assigned Numbers Authority – namely, this page:


So for Argentina I clicked on .ar (I expected to find a separate listing for .com.ar but that was not the case), leading to the page:

See it? At the bottom it shows Whois server: nic.com.ar. So I try that and voila, meaningful information is returned, no ads accompanying:

$ whois ‐h nic.com.ar buenosaires.com.ar

% La información a la que estás accediendo se provee exclusivamente para
% fines relacionados con operaciones sobre nombres de dominios y DNS,
% quedando absolutamente prohibido su uso para otros fines.
% de la información que los usuarios declaran con la sola finalidad de
% registrar nombres de dominio en ‘.ar’, para ser publicada en el sitio web
% de NIC Argentina.
% La información personal que consta en la base de datos generada a partir
% del sistema de registro de nombres de dominios se encuentra amparada por
% la Ley N° 25326 “Protección de Datos Personales” y el Decreto
% Reglamentario 1558/01.
domain:         buenosaires.com.ar
registrant:     50030338720
registrar:      nicar
registered:     2012-07-05 00:00:00
changed:        2017-06-27 17:42:45.944889
expire:         2018-07-05 00:00:00
contact:        50030338720
registrar:      nicar
created:        2013-09-05 00:00:00
changed:        2018-04-17 13:14:55.331068
nserver:        ns-1588.awsdns-06.co.uk ()
nserver:        ns-925.awsdns-51.net ()
nserver:        ns-1385.awsdns-45.org ()
nserver:        ns-239.awsdns-29.com ()
registrar:      nicar
created:        2016-07-01 00:02:28.608837

2nd example: goto.jobs
I actually needed this one! So I learned of a domain goto.jobs and I wanted to get some background. So here goes…
$ whois goto.jobs

getaddrinfo(jobswhois.verisign-grs.com): Name or service not known

So off to a bad start, right? So we hit up the .jobs link on iana, https://www.iana.org/domains/root/db/jobs.html, and we spy a reference to their whois server:

Registry Information
This domain is managed under ICANN's registrar system. You may register domains in .JOBS through an ICANN accredited registrar. The official list of ICANN accredited registrars is available on ICANN's website.
URL for registration services: http://www.goto.jobs
WHOIS Server: whois.nic.jobs

So we try that:
$ whois ‐h whois.nic.jobs goto.jobs

   Domain Name: GOTO.JOBS
   Registry Domain ID: 91478530_DOMAIN_JOBS-VRSN
   Registrar WHOIS Server: whois-all.nameshare.com
   Registrar URL: http://www.nameshare.com
   Updated Date: 2018-03-29T20:08:46Z
   Creation Date: 2010-02-04T23:54:33Z
   Registry Expiry Date: 2019-02-04T23:54:33Z
   Registrar: Name Share, Inc
   Registrar IANA ID: 667
   Registrar Abuse Contact Email:
   Registrar Abuse Contact Phone:
   Domain Status: clientTransferProhibited https://icann.org/epp#clientTransferProhibited
   Name Server: NS1.REGISTRY.JOBS
   Name Server: NS2.REGISTRY.JOBS
   DNSSEC: unsigned
   URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
>>> Last update of WHOIS database: 2018-04-23T18:54:31Z <<<

Better, but it seems to merely point to a registrar and its whois server:

Registrar WHOIS Server: whois-all.nameshare.com

So let’s try that:

$ whois ‐h whois-all.nameshare.com goto.jobs

Domain Name: GOTO.JOBS
Registry Domain ID: 91478530_DOMAIN_JOBS-VRSN
Registrar WHOIS Server: whois-jobs.nameshare.com
Registrar URL: http://www.nameshare.com
Updated Date: 2018-03-29T20:08:46Z
Creation Date: 2010-02-04T23:54:33Z
Registrar Registration Expiration Date: 2017-02-04T23:54:33Z
Registrar: NameShare, Inc.
Registrar IANA ID: 667
Registrar Abuse Contact Email: abuse-2014-2@encirca.com
Registrar Abuse Contact Phone: +1.7809429975
Domain Status: clientTransferProhibited http://www.icann.org/epp#clientTransferProhibited
Registry Registrant ID:
Registrant Name: DNS Administrator
Registrant Organization: Employ Media LLC
Registrant Street: 3029 Prospect Avenue
Registrant City: Cleveland
Registrant State/Province: OH
Registrant Postal Code: 44115
Registrant Country: United States
Registrant Phone: +1.2064261500
Registrant Phone Ext:
Registrant Fax: +1.1111111111
Registrant Fax Ext:
Registrant Email: supportgoto@goto.jobs
Registry Admin ID:
Admin Name: DNS Administrator
Admin Organization: Employ Media LLC
Admin Street: 3029 Prospect Avenue

Bingo! We have hit pay dirt. We have meaningful information about the registrant – an address, phone number and email address – and received no obnoxious ads in return. For me it’s worth the extra steps.

References and related
Here’s that iana root zone database link again: https://www.iana.org/domains/root/db

Posted in DNS, Linux, Network Technologies, Raspberry Pi, Security | Leave a comment

A taste of the Instagram API

I always want to know more about how things really work behind the scenes, so I was excited when I overheard talk about how one company uses the Instagram API to do some cool things. An API is an application programming interface. It allows you to write programs to automate tasks and do some really cool stuff. So I spoke to one of my sources who shared with me a few companies he knows about who use Instagram’s API to do some cool things. Unfortunately, none of them were willing to reveal the technical details of how they interact with the API, so I am left with only the marketing descriptions of what they have managed to do with it. But what they don’t realize is that as a capable IT person, in some cases I only have to hear that a thing is possible to motivate me. I have literally gone into meetings telling a customer No that’s not possible, hearing from them Yeah, well, they have it running in Europe, and going back to my desk afterwards to totally revise my opinion of what is or isn’t possible and how it could be done. Having said all that, here is what these companies have managed to do, without revealing the secret sauce of how they do it.

Example apps
Post scheduling software
This is used by social media managers to schedule their Instagram posts weeks or months in advance. It allows them to make a bunch of posts at once quickly and saves them time. A friend of a friend in NYC owns a company that does this. His website is bettrsocial.com

Analytic software
Simply Measured offers a free Instagram report for users with up to 25,000 followers. The stats and insights are presented clearly and will help inform your Instagram posting strategy. The report lets you quickly see what has worked well in your Instagram marketing so you can apply these insights to future posts. Web site: https://simplymeasured.com

Automaton software
Some companies connect with Instagram’s API to automate redundant tasks and increase traffic to your Instagram page. Social Network Elite is one of the best sources for growing organic Instagram followers.

Although I don’t even have an Instagram account, I am interested in APIs. The Instagram API does not look too daunting and seems well-documented. I cite a few small businesses that put it to use to do cool stuff. Unfortunately at this time I can’t deliver on the promise of the title of this article – a taste of the API – because I haven’t received any details about the actual usage. Perhaps in some future I will get my own account and develop my own application.

References and related
The Instagram API is documented here: https://www.instagram.com/developer/
My attempt to use the GoDaddy domain API.

Posted in Admin, Web Site Technologies | Tagged , | Leave a comment

Open Notebook: How does Citrix printing work anyway

I’m speaking of the old Citrix Receiver client. You launch that and that puts you in a Citrix ICA “jail.” I recently help a company move an app which had been a browser-based app to a browser within Citrix. Users complained they could not print from it… All their local printers were gone. Only a Citrix Universal Printer can be chosen.

What to do?

The solution
When you print, choose the Citrix universal printer.

Click on print again. You get a print preview screen.

Click on the printer symbol in the top bar. You will get your local printer list to choose from

Click on print again and the print job will be sent to the desired printer.

Simple enough, unless you’re going through it for the first time!

How did Citrix Receiver client break out of the jail?
I am told that it uses EMF format. That’s Enhanced Metafile, a successor to WMF, Windows metafile. EMF is a graphics language used in printer drivers. The Wikipedia article on this is surprisingly brief and skeletal: https://en.wikipedia.org/wiki/Windows_Metafile#Variants. So I guess it’s not really a jail at all – that was just my term. And the details beyond this unsatisfactory explanation I do not know. I’ll keep it on the back burner in case I ever get an opportunity to learn more about it.

Open Notebook background
I sometimes write blog posts as a sort of high-quality journal entry. I may very well be the only person who ever refers to them, and that’s OK. It contains enough information to prod my memory though it may not be polished enough to help many others.

References and related
The ICA that I referred to is the communications protocol used between classic Citrix Receiver client and a Citrix server (what we used to call an NFuse server). Wikipedia has a good article on it: https://en.wikipedia.org/wiki/Independent_Computing_Architecture

Posted in Web Site Technologies | Tagged , | Leave a comment

Raspberry Pi as Retro Arcade Games emulator

I am not going to attempt to provide a guide as there are much better guides out there than anything I can produce.

In addition to the arcade function, we wanted to display a slidedeck when not being used for gaming.

Two main approaches I see are

1) install RetroPie, then add X packages
2) install Raspbian, then install RetroPie on top of that

The reason we want X is to run a presentation software such as pipresents, which we are already familiar with.

For approach 1) I roughly followed this installation order.

Install lightdm and lxde
This takes a long time, maybe 30 minutes:
sudo apt install lxde lxde-core lxterminal lxappearance
sudo apt install lightdm
sudo apt-get install xutils
sudo apt-get install xserver-xorg

But one of my games didn’t run properly afterwards, so I am focused on method 2) for now.

I’m having trouble running startx from a non-console terminal. One thing I’m trying is:
sudo usermod -a -G tty pi
sudo apt-get install xserver-xorg-legacy
These two commands still didn’t do the trick, so I edited this file


and replaced allowed_user=console with allowed_users=anybody, and that worked! Once.

Then I installed RetroPie, turned it off so it does not autostart, and tried startx from a non-console terminal and I see this error:

(EE) xf86OpenConsole: Cannot open virtual console 2 (Permission denied)

then I re-installed xserver-xorg-legacy and startx once again worked. Hmm.

The instructions for installing RetroPie on top of an existing Raspbian installation are here:


You should be comfortable with the linux command line. In the end I like this method of installation the best. I’ve done it several times now.

Equipment ideas
These $15 speakers https://www.amazon.com/gp/product/B003JTHO3U/ref=oh_aui_detailpage_o01_s01?ie=UTF8&psc=1 only use the USB port for power. They have a standard mini-stereo jack that is compatible with the Pi. I bought them. The Pi has enough juice to power them, which is convenient.
I went with NES (Nintendo Entertainment System) games. This pair of USB controllers I am told are a good approximation of the real thing: https://www.amazon.com/gp/product/B075ZN1GXK/ref=oh_aui_detailpage_o02_s00?ie=UTF8&psc=1. they’re only about $14.
Two player arcade quality controller from Recroommasters. About $349.

How to configure two player setup when you have an arcade-style console with only one USB connection
I find the documentation available on the Internet on this particular topic is terrible. In fact I never did find it. This YouTube video was just created. Although it’s specific to their Xtension console it looks to me applicable to any similar console:


It takes a little getting used to. There are two main places where you do some configuration. There’s the RetroPie Configuration. Then there’s the emulationstation menu. The main thing to do from the emulationstation menu, which is launched by clicking Start from the main emulationstation screen, is to map the controller keys. For instance I program for an NES controller at home, and bring it to school where there is a cool two-player arcade-style controller which will have to be re-mapped.
The RetroPie configuration shows up from the main screen when you hit the down arrow key or something like that, then A. From here you can launch traditional raspi-config. I also used it to go into RetroPie setup, then into configuration and have emulationstation autolaunch at boot-up. You can also do a reboot from RetroPie setup.

To force sound out of the 3.5 mm stereo jack, go to RetroPie Configuration|RetroPie Setup|Configuration/tools|801 – audio settings|Headphones – 3.5 mm jack.

To get volume to 100% which you will need with the speakers I list below, go to emulation station menu|sound settings|system volume. By default it seems to be 77% which just isn’t enough juice.

References and related
Good discussion on X windows, display managers and desktop environments: https://raspberrypi.stackexchange.com/questions/26836/possible-to-reinstall-x-server-and-use-graphical-after-having-removed-it
Speakers for about $15: https://www.amazon.com/gp/product/B003JTHO3U/ref=oh_aui_detailpage_o01_s01?ie=UTF8&psc=1
Nintendo style USB controllers, $14: https://www.amazon.com/gp/product/B075ZN1GXK/ref=oh_aui_detailpage_o02_s00?ie=UTF8&psc=1
Two player setup with an arcade controller that has only one USB connection.
Arcade style two player console. Very cool. https://www.recroommasters.com/Xtension-Two-Player-Control-Board-Emulator-Edition-p/rm-xt-sd-board-ee.htm

Posted in Linux, Raspberry Pi | Tagged , | Leave a comment

Measuring bandwidth on Checkpoint Gaia

Sometimes you don’t have the tools you want but you have enough to make do. Such is the case with the command line utilities of the CLI of Checkpoint Gaia. It’s like a basic Linux. The company I consult for is beginning to hit some bandwidth limits and I wanted to understand overall traffic flow better. In the absence of any proper bandwidth monitors I used the netstat command and some approximations. Crude thouigh it may be it already gave me a much better idea about my traffic than I had going into this project.

The details
I call this BASH script netstats.sh

# for Gaia, not IPSO
while /bin/true; do
  v[1]=`netstat -Ieth1-01 -e|grep RX|grep TX`
  n[1]="vlan 102           "
  v[2]=`netstat -Ieth1-05 -e|grep RX|grep TX`
  n[2]="vlan 103 200.78.39    "
  v[3]=`netstat -Ieth1-02 -e|grep RX|grep TX`
  n[3]="vlan 103 10.31.42"
  v[4]=`netstat -Ieth1-03 -e|grep RX|grep TX`
  n[4]="trunk for VPN      "
# interesting line:
#           RX bytes:4785585828883 (4.3 TiB)  TX bytes:7150474860130 (6.5 TiB)
  for i in {1..4}; do
    RX=`echo ${v[$i]}|cut -d: -f2|awk '{print $1}'`
    TX=`echo ${v[$i]}|cut -d: -f3|awk '{print $1}'`
#    echo "vlan ${n[$i]}        RX,TX: $RX, $TX"
    if [ $c -gt 0 ]; then
      RXdiff=`expr $RX - ${RXold[$i]}`
      TXdiff=`expr $TX - ${TXold[$i]}`
# observed scaling factor: 8.1 bits/byte
      echo "${n[$i]}    RX,TX: $RXrate, $TXrate Mbps"
# old values
  c=$(( $c + 1 ))
  sleep 10

It’s pretty self-explanatory. I would just note that in the older IPSO OS you don’t have the ability to get the bytes transferred from netstat. Just the number of packets, which is an inherently cruder measure. The calibration of 8.1 bits per byte (there is overhead from the frames) is maybe a little crude but it’s what I measured over the source of a couple minutes.

A quick glance at Redhat or CentOS shows me that this same script, with appropriate modifications for the interface names (eth0, eth1, etc), would also work on those OSes.

I really, really wanted some kind of measure for IPSO as well. So I tackled that as best I could. Here is that script:

# for IPSO, not Gaia
while [ 1 -gt 0 ]; do
# eth1-01: vlan 802; eth1-05: vlan 803 (144.29); eth1-02: vlan 803 (10.201.145)
  v[1]=`netstat -Ieth-s4p1|tail -1`
  n[1]="vlan 208.129.99     "
  v[2]=`netstat -Ieth-s4p2|tail -1`
  n[2]="vlan 208.156.254     "
  v[3]=`netstat -Ieth-s4p3|tail -1`
  n[3]="vlan 208.149.129     "
  v[4]=`netstat -Ieth-s4p4|tail -1`
  n[4]="trunk for Cisco and b2b"
# interesting line:
#Name         Mtu   Network     Address             Ipkts Ierrs    Opkts Oerrs  Coll
#eth-s4p1     16018 <Link>      0:a0:8e:c4:ff:f4 72780201     0 56423000     0     0
  for i in {1..4}; do
    RX=`echo ${v[$i]}|awk '{print $5}'`
    TX=`echo ${v[$i]}|awk '{print $7}'`
#    echo "vlan ${n[$i]}        RX,TX: $RX, $TX"
    if [ $c -gt 0 ]; then
      RXdiff=$(($RX - ${RXold[$i]}))
      TXdiff=$(($TX - ${TXold[$i]}))
# observed: .0043 mbits/packet
# observed: .0056 mbits/packet
      echo "${n[$i]}    RX,TX: $RXrate, $TXrate Mbps"
# old values
  c=$(( $c + 1 ))
  sleep 10

The conversion to bits is probably only accurate to +/- 25%, because it depends a lot on the application, i.e., VPN concentrator versus proxy server. I just averaged all applications together because that’s the best I could do. I compared it to a Cisco router’s statistics.

A script is provided which gives a measure of Mbps bandwidth usage by polling netstat periodically. It’s not exact, but even crude measures can help a network engineer.

Posted in Admin, Linux, Network Technologies | Tagged , | Leave a comment

Consumer tech: Solar Panels – the things they never tell you

Since solar panels are a major commitment I did some research first. My criteria (high-efficiency, not manufactured in China, carried by a local installer) was met by Sunpower X-series whose panels generate 345 KWh, which was pretty much at the high end in 2017.

The details
I live in a northern latitude area (41° latitude) with plenty of snowfall. I have a sloped roof.

I just assumed that the snow would melt off the panels at more or less the same rate as off the rest of the roof.

That is not at all the case.

I was working from home one day in the kitchen when I heard someone on the roof. At least that’s what it sounded like. The person seemed to be clearing the snow off my panels, how thoughtful of my installer to send someone to do that. The snow was thundering and avalanching off the roof onto my deck. Eventually I realized the mini-avalanches were real, the person up there on the roof was no more real than Santa Claus.

So yes, the snow slides off those panels in thunderous mini-avalanches. So today after a big snow event, this has been going on this morning, the day after, on and off for hours. Around the panels the roof retains its snow, but the panels themselves have lost all theirs. I see my neighbors’ panels are also cleared so this must be a universal phenomenon.

It’s worth mentioning because it’s a little frightening when you first hear it.

Power consumption vastly overestimated
I suppose this next problem is peculiar to just my installation. Sunpower gives you this nice portal so you see what you’re generating and what you’re using. In my case the generation numbers seem plausible, but the usage numbers are way off.

February bill shows 621 KWh metered, 386 KWh out = 235 KWh billed.

Sunpower shows 1442 KWh used, 531 KWh generated, for a net of -910 KWh.

So we can compare 235 to the 910. The should be about the same yet there is a huge difference.

The usage is almost, but not quite doubled. if we add 235 billed to the 531 generated we’d have 766 used. So usage is overestimated by a factor 1.89. But I doubt it’s a simple formula like that to correct their numbers. During the time of generation – daylight – the usage estimates numbers dip. So I don’t know what they’ve done wrong, and my installer says their support is horrible. It’s been nine months and I’ve just asked for an update. It’s more annoyance than anything.

Posted in Consumer Tech | Tagged | Leave a comment

Verizon Tips I need but can never easily find

Verizon Phone Finder
They will lead you to some page that seems to suggest you needed to buy a premium service from them if you need to find your phone. Don’t fall for it. Or maybe you will get their other page which only sends you on to Google’s page. So, for Android phones, here is the universal phone finder link:


It’s pretty cool. It shows pretty precisely where the phone is, how much power is left and gives you the chance to ring it for five minutes and even lock it.

Change your Verizon Wireless Voicemail password
This is even harder to find unless you have just the right search terms.

From a Verizon app on your phone
I haven’t used this method.

From the My Verizon web site
I just used this so it works pretty well:

How to set up your voicemail so as not to enter a password
Basic idea: add a contact for Voicemail tack on two pauses, then append the password. So that will be


For instance I named this contact VM. If my password were 1234 the number would be


To generate the “,” character go to the special characters key to the right of # when typing in the number.

Posted in Consumer Tech | Tagged | Leave a comment

Multiple IPs on the Raspberry Pi

In my previous post I showed how to turn a Raspberry Pi plus USB camera into something like an IP camera. In the course of that work I found it wasn’t so easy as it was in the past to assign static IPs upon boot. So I came up with my own unique method, which combines a modicum of Linux knowledge with a dash of networking knowledge.

The requirements
I sort of invented these requirements for myself, putting myself in the pickle I found myself in. I am working with a friend’s Pi 3 and didn’t want to mess it up too badly. Yet I wanted to easily work with it at home, and for the Robotics team. How to do it all?

I decided to permit the DHCP client, now called dhcpcd, running. So it will assign an IP address and appropriate gateway if there is a DHCP server present on the network. When I test at home I sometimes don’t use DHCP. When I bring my test setup to Robotics, more often than not I have my own little isolated LAN and no DHCP server. So, knowing that a single interface can have two or even more than two IP adresses, I created the following list of requirements for myself.

Act as DHCP client if there is DHCP server.
Assign static IP of so it works in my home.
Assign another static IP of so it works with a predictable IP in the robotics environment.
Let the two above IP assignments work even in the absence of a DHCP server!

Sounds kind of simple, but it’s not so easy.

I’m running a Raspberry Pi 3 with Raspbian Stretch (the release after Jessie).

Initial approach
With this version you’re supposed to use the file


to create a static IP.

But it works like c**p, at least when you want to push it and have it meet all the requirements above. It’s got a bug and doesn’t allow you to meet all the above requirements. I experimented. But my method does work.

The final solution
So in the end I leave /etc/dhcpcd.conf alone!

I use this new (to me) feature that crontab has an @reboot feature that calls its argument at boot time – just what we need.

Then I combined some old school use of ifconfig plus newer school command ip.

Here’s the script, which I call ip-assign.sh.

sleep 2
# see if there is a dhcp-assigned IP already. If so 'scope global' appears in the listing
#  ip add show eth0 sample output:
ip add show eth0|grep -q 'scope global'
if [ $? == 0 ]; then
# first IP
ifconfig eth0 $addflag netmask broadcast
# next IP
ifconfig eth0 add netmask broadcast

What I observed is that eth0 already has an IP assigned to it (for instance from a DHCP server), then the string “scope global” appears when you run ip add, otherwise it doesn’t. Furthermore, ifconfig has an optional argument I noticed call add, which seems to exist in order to add additional virtual interfaces – precisely what we want. But if there is no IP yet assigned we should call ifconfig the first time with the add argument. If I had had additional virtual IPs I could have just kept on going…

So to call this at boot time I use my lazy method. I edit the crontab file and insert a line like this:

@reboot sudo ~/ip‐assign.sh > /tmp/ip‐assign.log 2>&1

So without a DHCP server I have after booting:
$ ip add show eth0

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether b8:27:eb:e3:02:74 brd ff:ff:ff:ff:ff:ff
    inet brd scope global eth0:0
       valid_lft forever preferred_lft forever
    inet brd scope global eth0
       valid_lft forever preferred_lft forever
    inet brd scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::e923:3131:224c:ecd/64 scope link
       valid_lft forever preferred_lft forever

If you’re lazy like me just type
$ ip add
and you’ll get the other interfaces as well. It’s very easy to type, too!

Note the broadcast (brd) addresses are reversed from how you’d expect them. I decided that it doesn’t matter as long as they’re both present somewhere with the correct value. It’s all using the one physical interface so the interface doesn’t really care. And from all my testing I am right I believe on this point.

Disable WiFi – wlan0
To disable WiFi entirely, which you may want to do if using in a FIRST FRC competition, add this to /boot/config.txt and reboot:
After doing that wlan0 does not even show up when you do an ip add.

References and related
Raspberry Pi plus USB camera: brought together like an IP camera.

Posted in Linux, Network Technologies, Raspberry Pi | Tagged , , , | Leave a comment

Raspberry Pi USB webcam turned into IP camera

Why would you even want to do this when you can buy a native IP webcam for less? I’m not sure, but i found myself in this situation so it could happen to others, and I found some things that worked and some that required quite some effort.

In my previous post I spoke about using opencv on Raspberry Pi.

This post is more about getting at an image with a minimum of lag time and relatively low bandwidth.

The setup
The specific camera I am working with is an ELP mini USB camera for $20.

What I did not do
I considered bolting on an add-on to opencv to convert the video stream into mjpeg. But the process looked relatively obscure so I did not feel that was a good way to go.

I skimmed through the mjpeg (motion jpeg) standard. Looks pretty straightforward. i even considered writing my own streamer. It’s probably not too hard to write a bad one! But I feared it would be unreliable so I didn’t go that route. It’s just jpeg, separator, jpeg, separator, jpeg, etc. Here’s the Wikipedia link: https://en.wikipedia.org/wiki/Motion_JPEG.

I think the best software for is mjpg_streamer. It is not available as a simple package. So you have to compile it and patch it.

Follow his recipe
This guy’s recipe worked for me:

Mostly! I needed the patch as well (which he also mentions). his instructions for the patch aren’t accurate.

He provides a link. You need to save the contents by launching the downloaded file and saving it as input_uvc_patch.txt after opening it in Windows Notepad (if you’re doing this download through Windows).

On the Pi, you would do these steps:

cd ~/mjpg-streamer
patch ‐p0 < input_uvc_patch.txt make USE_LIBV4L2=true clean all sudo make DESTDIR=/usr/local install That is, assuming you had copied the patch file into that ~/mjpg-streamer directory. Before we get too far, I wished to mention that the command fswebcam proved somewhat useful for debugging. Here's a weird thing about that camera We had one, then I got another one. The two cameras do not behave the same way! Device files
I guess Raspberry Pi has its own version of plug-and-play. So what it means is that when you plug in the camera a device file is dynamically created called /dev/video0. Now if you happen to plug in a second USB camera, that one becomes device /dev/video1. Some utilities are designed to work with /dev/video0 and require extra arguments to deal with a camera with a different device number, e.g., fswebcam -d /dev/video1 image.jpg.

But actually running two cameras did not work out too well for me. It seemed to crash and I don’t have time to investigate that.

The working command is…
My livestream.sh file looks like this right now. It will change but this is a good document point.

/usr/local/bin/mjpg_streamer -i "/usr/local/lib/input_uvc.so -yuv -f 12 -q 50 -r 352x288" -o "/usr/local/lib/output_http.so -w /usr/local/www"

The main point is that I found this additional -yuv argument seemed to get the one webcam to work, whereas the other USB camera didn’t need that! If you don’t include it launcher.sh may appear to work, but all you see when you connect to the direct video stream looks like this image:

One time when I ran it it crashed and suggested that -yuv argument be added, so I tried it and it actually worked! That’s how i discovered that oddity.

Bandwidth with those settings
About 2 mbps. How do I measure that? simple. I bring up the web page and tool around the networking stuff until i find Change Adapter Settings (always difficult to find). Then I double-click on my active adapter and stare at the received bytes to get a feel for how much it’s incrementing by each second. Multiply by 10, and voila, you have a crude measure, perhaps +/- 30%, of your bandwidth consumed!

This is so important it needs its own section.

Latency is pretty good. We’ve measured it to be 0.26 seconds.

fswebcam errors
What happens if you run fswebcam while livestream is running?
$ fswebcam /tmp/image.jpg

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Error selecting input 0
VIDIOC_S_INPUT: Device or resource busy

Makes sense. Only one program on the Pi can capture the output form the camera.

Does the simple command fswebcam image.jpg work all the time? No it does not! Sometimes it simply fails, which is scary.

Here is an example of two consecutive calls to fswebcam about a second apart which illustrates the problem:

$ fswebcam /tmp/image.jpg

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Adjusting resolution from 384x288 to 352x288.
--- Capturing frame...
Timed out waiting for frame!
No frames captured.

$ fswebcam /tmp/image.jpg

--- Opening /dev/video0...
Trying source module v4l2...
/dev/video0 opened.
No input was specified, using the first.
Adjusting resolution from 384x288 to 352x288.
--- Capturing frame...
Captured frame in 0.00 seconds.
--- Processing captured image...
Writing JPEG image to '/tmp/image.jpg'.

Running two USB cameras wih a single Ras Pi
This initially did not work in my first attempts but now it does!

It probably helps to be running a Raspebrry Pi 3 with Raspbian Stretch OS.

Maybe this wasn’t needed but we made a directory /usr/local/www2 and copied all the files from /usr/local/www to /usr/local/www2. A 2nd USB camera when plugged in creates /dev/video1 as I mentioned. You have to pick a different port, so we chose port 8090. Putting it all together we have the script below, livestream2.sh:

/usr/local/bin/mjpg_streamer -i "/usr/local/lib/input_uvc.so -d /dev/video1 -yuv -q 50 -r 352x288 -f 12" -o "/usr/local/lib/output_http.so -w /usr/local/www2 -p 8090"

If a 2nd camera isn’t plugged in then the script errors out and doesn’t run, which is pretty much what we want. Running it by hand we get this:

$ ./livestream2.sh

MJPG Streamer Version: svn rev: 3:172M
 i: Using V4L2 device.: /dev/video1
 i: Desired Resolution: 352 x 288
 i: Frames Per Second.: 12
 i: Format............: YUV
 i: JPEG Quality......: 80
ERROR opening V4L interface: No such file or directory
 Init v4L2 failed !! exit fatal
 i: init_VideoIn failed

Reining in the bandwidth
We found that by lowering the jpeg quality with the -q option we could reduce the bandwidth and the quality, but the quality was still good enough for our purposes. Now the video streams from both cameras comes in around 4.5 mbps, even in bright lighting. So we settled on -q 50 for a 50% quality. Even a quality of 10 (10%) is not all that bad! I believe the default is 80%.

Bandwidth monitor on the Pi
Some of this was written by the student so apologies for the misspellings! Probably will be refined in the future. We can tease out how much bandwidth we’re actually using on the Pi by measuring the transmitted (TX) bytes periodically. We’ll record that during a matcgh so we can prove to ourselves and others that we have our bandwidth under control – far less than 7 mbps despite using two cameras.

banwidthmonitor.pl Perl program

#monitor banwidth
$DEBUG = 1;
$sleep = 5;
$| = 1;
$date = `date`;
print $date;
for (;;) {
  $tx = `ip -s link show eth0 | tail -1| awk \'{print \$1}\'`;
  print $tx if $DEBUG;
  $txbitstotal = 8 * $tx;
  $timetotal = time;
  $txbits = $txbitstotal - $txbitstotalold if $txbitstotalold;
  $time = $timetotal - $timetotalold;
  $txbitstotalold = $txbitstotal;
  $banwidth = $txbits / $time if $timetotalold;
  print "banwidth $banwidth\n";
  $timetotalold = $timetotal;
# TX: bytes  packets  errors  dropped carrier collsns
#    833844072  626341   0       0       0       0
  sleep $sleep;

Output from program
Watch as our bandwidth usage grows to around 700 kbps as we turn on one of our video cameras.
$ ./banwidthmonitor.pl

Tue Jan 30 21:09:32 EST 2018
banwidth 518.4
banwidth 571164.8
banwidth 712920
banwidth 726939.2
banwidth 712976
banwidth 713016

Unreliable video stream startup
Sometimes one video stream does not come on correctly after first power-up. This is most perplexing as with computer gear one expects consistent, reproducible behaviour, yet that is not at all what we’ve observed.
This makes no sense, but in one environment we had our two streams running successfully six times in a row. Then I take the equipment home and find only one of the two streams starts up. It seems more likely to fail after sitting powered off for a few hours! I know it doesn’t make sense but that’s how it is.

In any case we have built a monitor which looks for and corrects this situation. It’s pretty clever and effective if I say so myself! And necessary! We created one monitor each for the two video devices. Here’s videomonitor.sh:

# DrJ make sure video stream is not stuck. Restart it if it is
sleep 8
while /bin/true; do
  chars=`curl -s -m1 localhost:80/?action=stream|wc -c`
  if [ $chars -lt 100 ]; then
# we are stuck!
    echo Video stuck so we will restart it
    pid=`ps -ef|grep mjpg|grep 'p 80'|grep -v sudo|awk '{print $2}'`
    sudo kill $pid
    sleep 1
    ~/livestream.sh &
# restart...
# we have a good stream
    touch /tmp/stream80
  sleep 5

and videomonitor2.sh

# DrJ make sure video stream is not stuck. Restart it if it is
sleep 8
while /bin/true; do
  chars=`curl -s -m1 localhost:443/?action=stream|wc -c`
  if [ $chars -lt 100 ]; then
# we are stuck!
    echo Video stuck so we will restart it
    pid=`ps -ef|grep mjpg|grep 'p 443'|grep -v sudo|awk '{print $2}'`
    sudo kill $pid
    sleep 1
    ~/livestream2.sh &
# restart...
# we have a good stream
    touch /tmp/stream443
  sleep 5

And we’ll start these at boot time like the long and growing list of things we are starting at boot time.

Allowed ports
From rule 66…

R66. Communication between the ROBOT and the OPERATOR CONSOLE is restricted as follows:
A. Network Ports:
HTTP 80: Camera connected via switch on the ROBOT, bi-directional
HTTP 443: Camera connected via switch on the ROBOT, bi-directional

So…to be safe we are switching from use of ports 8080 and 8090 to ports 80 and 443. But this means we have to preface certain commands – such as mjpg_streamer – with sudo since tcp ports < 1024 are privileged.

Flashing an led when we have a good video stream
Our led is soldered to a gruond pin and GPIO pin 18.

We call this program ledflash.sh

#flashes the led
while /bin/true; do
if [ -f /tmp/stream80 ] && [ -f /tmp/stream443 ]; then
  cd /sys/class/gpio
  echo $pin > export
  cd gpio$pin
  echo out > direction
  while /bin/true; do
#make 5 quick flashes
    for i in `seq 1 5`; do
      echo 1 > value
      sleep 0.1
      echo 0 > value
      sleep 0.1
#now lets make the long flash
    echo 1 > value
    sleep 0.6
sleep 2

We start it at boot time as well. It tells us when both video streams are ready for viewing because only then do the files get created and then the led starts flashing.

It takes about 62 seconds from the time power is supplied to the Raspberry Pi to the time the LED starts flashing (indicating the two video streams are ready).

Picture of setup

This picture goes a long way to convey the ideas.

2 USB cameras, 1 Ras Pi, flashing LED

References and related
Multiple IP addresses
We needed an IP for testing in the lab, another when we brought it home and a third for competitions. This blog post showed how we gave it all needed IP addresses for our purposes!

FIRST FRC provides this guide for use of IP addresses at their events.

Amazon seemed to run out of the original USB camera we worked with. The ELP pinhole USB camera seems to work just as well and is just as cheap, around $20: https://smile.amazon.com/gp/product/B00K7ZWVVO/ref=oh_aui_detailpage_o00_s00?ie=UTF8&psc=1

Raspberry Pi model 2 and 3 GPIO pins are documented here: https://www.raspberrypi.org/documentation/usage/gpio-plus-and-raspi2/.

Posted in Linux, Raspberry Pi, Web Site Technologies | Tagged , | Leave a comment

A first taste of OpenCV on a Raspberry Pi 3

I’ve done a few things to do some vision processing with OpenCV on a Raspberry Pi 3. I am a rank amateur so my meager efforts will not be of much help to anyone else. My idea is that maybe this could be used on an FRC First Robotics team’s robot. Hence I will be getting into some tangential areas where I am more comfortable.

Even though this is a work in progress I wanted to get some of it down before I forget what I’ve done so far!

Tangential Stuff

Disable WiFi
You shouldn’t have peripheral devices with WiFi enabled. Raspeberry Pi 3 comes with built-in WiFi. Here’s how to turn it off.

Add the following line to your /boot/config.txt file:



If it worked you should only see the loopback and eth0 interefaces in response to the ip link command, something like this:

$ ip link
1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether b8:27:eb:3f:92:f3 brd ff:ff:ff:ff:ff:ff

Hardcode an IP address the simple-minded way
On a lark I decided to try the old-fashioned method I first used on Sun Solaris, or was it even Dec Ultrix? That is, ifconfig. I thought it was to be deprecated but it works well enough for my purpose.

So something like

$ sudo ifconfig eth0

does the job, as long as the network interface is up and connected.

Autolaunch a VNC Server so we can haul the camera image back to the driver station
$ vncserver &hypher;geometry 640×480 ‐Authentication=VncAuth :1

Launch our python-based opencv program and send output to VNC virtual display

$ export DISPLAY=:1
$ /home/pi/.virtualenvs/cv/bin/python green.py > /tmp/green.log 2>&1 &

The above was just illustrative. What I actually have is a single script, launcher.sh which puts it all together. Here it is.

# DrJ
sleep 2
# set a hard-wired IP - this will have to change!!!
sudo ifconfig eth0
# launch small virtual vncserver on DISPLAY 1
vncserver -Authentication=VncAuth :1
# launch UDP server
$HOME/server.py > /tmp/server.log 2>&1 &
# run virtual env
cd $HOME
# don't need virtualenv if we use this version of python...
#. /home/pi/.profile
#workon cv
# now launch our python video capture program
export DISPLAY=:1
/home/pi/.virtualenvs/cv/bin/python green.py > /tmp/green.log 2>&1 &

OpenCV (open computer Vision)
opencv is a bear and you have to really work to get it onto a Pi 3. There is no apt-get install opencv. You have to download and compile the thing. There are many steps and few accurate documentation sources on the Internet as of this writing (January 2018).

I think this guide by Adrian is the best guide:

Install guide: Raspberry Pi 3 + Raspbian Jessie + OpenCV 3

However I believe I still ran into trouble and I needed this cmake command in stead of the one he provides:

        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.1.0/modules \

I also replaced opencv references to version 3.0.0 with 3.1.0.

I also don’t think I got make -j4 to work. Just plain make.

An interesting getting started tutorial on images, opencv, and python:


Simplifying launch of VNC Viewer
I wrote a simple-minded DOS script which launches UltraVNC with a password. So with a double-click it should work).

Here’s a Dos .bat file to launch ultravnc viewer by double-clicking on it.

if not "%minimized%"=="" goto :minimized
set minimized=true
start /min cmd /C "%~dpnx0"
goto :EOF
c:\apps\ultravnc\vncviewer -password raspberry

I’m sure there’s a better way but I don’t know it.

The setup
We have a USB camera plugged into the Pi.
A green disc LED light.
A green filter over the camera lens.
A target with two parallel strips of retro-reflective tape we are trying to suss out from everything else.
Some sliders to control the sensitivity of our color matching.
The request to analyze the video in opencv as well as display it on the driver station.
Have opencv calculate the pixel distance (“correction”) from image center of the “target” (the two parallel strips).
Send this correction via a UDP server to any client who wants to know the correction.

Here is our current python program green.py which does these things.

import Tkinter as tk
from threading import Thread,Event
from multiprocessing import Array
from ctypes import c_int32
import cv2
import numpy as np
import sys
#from Tkinter import *
#cap = cv2.VideoCapture(0)
global x
global f
x = 1
y = 1
f = "green.txt"
class CaptureController(tk.Frame):
    NSLIDERS = 7
    def __init__(self,parent):
        self.parent = parent
        # create a synchronised array that other threads will read from
        self.ar = Array(c_int32,self.NSLIDERS)
        # create NSLIDERS Scale widgets
        self.sliders = []
        for ii in range(self.NSLIDERS):
            # through the command parameter we ensure that the widget updates the sync'd array
            s = tk.Scale(self, from_=0, to=255, length=650, orient=tk.HORIZONTAL,
                         command=lambda pos,ii=ii:self.update_slider(ii,pos))
            if ii == 0:
                s.set(0)  #green min
            elif ii == 1:
            elif ii == 2:
            elif ii == 3:
                s.set(3)  #green max
            elif ii == 4:
            elif ii == 5:
            elif ii == 6:
                s.set(249)  #way down below
        # Define a quit button and quit event to help gracefully shut down threads
        self._quit = Event()
        self.capture_thread = None
    # This function is called when each Scale widget is moved
    def update_slider(self,idx,pos):
        self.ar[idx] = c_int32(int(pos))
    # This function launches a thread to do video capture
    def start_capture(self):
        # Create and launch a thread that will run the video_capture function
#        self.capture_thread = Thread(cap = cv2.VideoCapture(0), args=(self.ar,self._quit))
        self.capture_thread = Thread(target=video_capture, args=(self.ar,self._quit))
        self.capture_thread.daemon = True
    def quit(self):
        except TypeError:
# This function simply loops over and over, printing the contents of the array to screen
def video_capture(ar,quit):
    print ar[:]
    cap = cv2.VideoCapture(0)
    Xerror = 0
    Yerror = 0
    XerrorStr = '0'
    YerrorStr = '0'
    while not quit.is_set():
        # the slider values are all readily available through the indexes of ar
        # i.e. w1 = ar[0]
        # w2 = ar[1]
        # etc.
        # Take each frame
        _, frame = cap.read()
        # Convert BGR to HSV
        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        # define range of blue color in HSV
        lower_green = np.array([ar[0],ar[1],ar[2]])
        upper_green = np.array([ar[3],ar[4],ar[5]])
        # Threshold the HSV image to get only green colors
        mask = cv2.inRange(hsv, lower_green, upper_green)
        # Bitwise-AND mask and original image
        res = cv2.bitwise_and(frame,frame, mask= mask)
        cv2.imshow('frame', frame)
#        cv2.imshow('mask',mask)
#        cv2.imshow('res',res)
        img = cv2.blur(mask,(5,5))   #filter (blur) image to reduce errors
        ret,thresh = cv2.threshold(img,127,255,0)
        im2,contours,hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
        print 'number of contours==640x480====================  ', len(contours)
        if len(contours) > 0:
            numbercontours = len(contours)
            while numbercontours > 0:
                numbercontours = numbercontours -1  # contours start at 0
                cnt = contours[numbercontours]   #this is  getting the first contour found, could look at 1,2,3 etc
                x,y,w,h = cv2.boundingRect(cnt)
#---line below has the limits of the area of the target-----------------------
                #if w * h > 4200 and w * h < 100000:  #area of capture must exceed  to exit loop
                if h > 30 and w < h/3:  #area of capture must exceed  to exit loop
                    print ' X   Y  W  H  AREA      Xc  Yc      xEr yEr'
                    Xerror = (-1) * (320 - (x+(w/2)))
                    XerrorStr = str(Xerror)
                    Yerror = 240 - (y+(h/2))
                    YerrorStr = str(Yerror)
                    print  x,y,w,h,(w*h),'___',(x+(w/2)),(y+(h/2)),'____',Xerror,Yerror
#-------        draw horizontal and vertical center lines below
                displaySTR = XerrorStr + '  ' + YerrorStr
                font = cv2.FONT_HERSHEY_SIMPLEX
                cv2.putText(img,displaySTR,(10,30), font, .75,(255,255,255),2,cv2.LINE_AA)
# wrtie to file for our server'
                sys.stdout = open(f,"w")
                print 'H,V:',Xerror,Yerror
                sys.stdout = sys.__stdout__
        if target==0:
                # no target found. print non-physical values out to a file
                sys.stdout = open(f,"w")
                print 'H,V:',1000,1000
                sys.stdout = sys.__stdout__
        k = cv2.waitKey(1) & 0xFF    #parameter is wait in millseconds
        if k == 27:   # esc key on keboard
if __name__ == "__main__":
    root = tk.Tk()
    selectors = CaptureController(root)
#    q = tk.Label(root, text=str(x))
#    q.pack()

Well, that was a big program by my standards.

Here’s the UDP server that goes with it. I call it server.py.

#!/usr/bin/env python
# inspired by https://gist.github.com/Manouchehri/67b53ecdc767919dddf3ec4ea8098b20
# first we get client connection, then we read data frmo file. This order is important so we get the latest, freshest data!
import socket
import re
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ''
server_port = 5005
server = (server_address, server_port)
print("Listening on " + server_address + ":" + str(server_port))
while True:
# read up to 32 bytes from client
        payload, client_address = sock.recvfrom(32)
        print("Request from client: " + payload)
# get correction from file
        while True:
                with open('green.txt','r') as myfile:
#H,V:  9 -14
                data = data.split(":")
                if len(data) == 2:
        sent = sock.sendto(data[1], client_address)

For development testing I wrote a UDP client to go along with that server. I called it recvudp.py.

#!/usr/bin/env python
import socket
UDP_IP = ""
UDP_PORT = 5005
print "UDP target IP:", UDP_IP
print "UDP target port:", UDP_PORT
sock = socket.socket(socket.AF_INET, # Internet
                 socket.SOCK_DGRAM) # UDP
# need to send one newline minimum to receive server's message...
MESSAGE = "correction";
sock.sendto(MESSAGE, (UDP_IP, UDP_PORT))
# get data
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
print "received message:", data

Lag is bad. Probably 1.5 seconds or so.
Video is green, but then we designed it that way.
Bandwidth consumption of VNC is way too high. We’re supposed to be under 7 mbps and it is closer to 12 mbps right now.
Probably won’t work under the bright lights or an arena or gym.
Sliders should be labelled.
Have to turn a pixel correction into an angle.
Have to suppress initial warning about ssh default password.

To be improved, hopefully…

Posted in Linux, Python, Raspberry Pi | Tagged | Leave a comment