Saturday, September 26, 2020

Barcode scan and generate on Android

Bar code and QR code are widely used now. There is a lot of App can do the scan. I'm planing to make an app to scan Bar code and render it on cell phone display, so I can store bar code of my card and no longer need to carry the cards.

ZXing on github is a great choice as an open source solution. As the project home page mentioned: ZXing ("zebra crossing") is an open-source, multi-format 1D/2D barcode image processing library implemented in Java, with ports to other languages.

To get started, please visit: https://github.com/zxing/zxing/wiki/Getting-Started-Developing

Refer to this StackOverflow page for some example code. Get ZXing-2.1.zip from google code.

To use the lib with Android Studio, add this to app/build.gradle dependencies:

 implementation 'com.google.zxing:core:3.3.3'.

Sunday, September 20, 2020

Weird 'sdk version' problem with Android Studio debugging on phone

 I'm seeing error "the app doesn't support sdk version of the device (1)" from Android Studio when I try to run one of my App on a phone with USB debug just enabled. The error message is quite misleading. The phone has Android 8.1.0, which is API 27. My App build gradle file has minimum SDK version set to 15, compile and target SDK set to 28. So there is no reason of the error.

By Googling, it brought me to this Stack Overflow post: ADB Android Device Unauthorized

Usually after the USB debug just enabled, ADB won't work properly for the first time. So disconnect and reconnect USB will solve the problem. And that also solved the SDK version complain issue.


Wednesday, August 26, 2020

Remote Serial Port Access

Not just because of COVID-19 people may need to work from home, there is a lot of case that people need to access a serial port on a remote setup, such as access a Raspberry-Pie UART on a host PC. VirtualHere provides a commercial solution to create a Virtual USB on local host to virtually represent the USB port on the remote host. This works pretty well with FTDI USB-to-Serial chips. There are many other solutions. Specially if you like me want to have a simple manageable solution, such as using script to do the job. A good example I found from here: http://fabacademy.org/archives/2015/doc/WebSocketConsole.html, which create a web view of UART output with purely Python script.

Too lazy to check what the license is for posting the code. So I won't try to copy and paste the article here. It was published in 2015, a little old, but I assume it should still work well. When I tried it out on Windows recently, I do see some issue, mainly due to known issue for python package on Windows.

Python 2.x is too old. So the first thing is updating for Python 3.x, such as convert all "print '...' " to "print('...')". There is also some tab/space mixing issue need to be fixed.

For using pyserial to open serial port on windows such as COM6, no need to use the awkward device name "//./COM6", just open "COM6" should be fine.

The big issue is the multiprocessing pickling error on Windows, will get error:

self = reduction.pickle.load(from_parent)
EOFError: Ran out of input

Google it will get a bunch of similar topic, such as this pytorch one: use if __name__ == '__main__': to protect your main function (more details here). Can also try to run the script in command prompt. If the error persists, then you can try to set num_worker of DataLoader to zero.

And with Stackoverflow. As explained here: Windows doesn't have fork, so there's no way to make a new process just like the existing one. So the child process has to run your code again, but now you need a way to distinguish between the parent process and the child process, and __main__ is it.  

'if __name__ == '__main__'' is already in the original code, the num_worker seems is pytorch specific. So for this case, looks like need to fix empty input problem, maybe need to force the serial port read as a blocking read? Tried changing serialworker.py as:

self.sp = serial.Serial(SERIAL_PORT, SERIAL_BAUDRATE, timeout=0)

but it doesn't work out. So maybe need to consider using pyserial in thread instead.

--- to be continue ---

Though I wasn't able to figure out the reason, but I'm able to get it working by removing the serialworker class completely, put all serial I/O code to the worker of multiprocessing.Process.

Now new issue surfaced: 

1) appdata\local\programs\python\python38\lib\site-packages\tornado\platform\asyncio.py", line 79, in add_handler

    self.asyncio_loop.add_reader(
  File "c:\users\kokat\appdata\local\programs\python\python38\lib\asyncio\events.py", line 498, in add_reader
    raise NotImplementedError

This is a known issue as tornado issue 2068, and also mentioned in Tornado site. The fix/workaround is adding below in \Lib\site-packages\tornado\platform\asyncio.py

import sys
if sys.platform == 'win32':
     asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

Or add above line in main, right after the if __name__ == '__main__' check (add import asyncio)

2) error TypeError: __init__() got an unexpected keyword argument 'io_loop'

One solution is as User a local Jupyter Notebook, downgrade tornado with:

 pip install tornado==4.5.3

This may also get rid of the first issue. But I more prefer to use the new version of Tornado. So the other solution is remote the obsoleted keyword 'io_loop'.

3) checkQueue() is invoked by a scheduler, with 100 as interval, the 'if' check in the code only can pick one line serial port output from the queue, which will lead to bad throughput. Should change the 'if' check as 'while', like this:

    while not output_queue.empty():
        message = output_queue.get()

4) WebSocketHandler is not working as expected, one reason is that main.js failed to be loaded. Other reason could be with the Javascript and the html script.

--- to be continue ---

After some research, debugging and testing, I got a solution to have a html file with inline JavaScript. It's not perfect, still has some minor issue, such as the 'clear' button doesn't work as expected. The code is like below (modified based on tutorialspoint code):

<!DOCTYPE html>
<html>
   <head>
      <meta charset = utf-8>
      <title>WebSocketConsole</title>
      <body>
         <section id = "wrapper">
            <header><h1>WebSocketConsole</h1></header>
            <style>
               body { margin0pxpadding20px; }

               #status { padding5pxcolor#fffbackground#ccc;}
               #status.fail { background#c00; }
               #status.success { background#0c0; }
               #status.offline { background#c00; }
               #status.online { background#0c0; }

               #received { width500pxheight400pxborder1px solid #dededefont-family"Lucida Console"Couriermonospacefont-size10px;
                           overflow-y:scrolldisplay:flexflex-direction:column-reverse}
               #cmd_str { width97%; }
               .message { font-weightbold; }
               .message:before { content' 'color#bbbfont-size14px; }
            </style>
            <article>
               <p id = "status">Not connected</p>
               <p>Users connected: <span id = "connected">0</span></p>
               <p>Data received from serial port</p>
               <div id="received"></div>
               <button id="clear">Clear</button>
               <p>Send data to serial port</p>
               <form onsubmit = "send_cmd(); return false;">
                    <input type = "text" id = "cmd_str" placeholder = "type cmd and press enter" />
               </form>
            </article>
            <script>
               var state = document.getElementById("status");
               var connected = document.getElementById("connected");
               var received = document.getElementById("received");
               var clear = document.getElementById("clear");
               clear.onclick = function() {
                   received.empty();  //TODO: this does not clear the log. Need fix
               }
               var cmd_str = document.getElementById("cmd_str");
               if (window.WebSocket === undefined) {
                  state.innerHTML = "sockets not supported";
                  state.className = "fail";
               }else {
                  if (typeof String.prototype.startsWith != "function") {
                     String.prototype.startsWith = function (str) {
                        return this.indexOf(str) == 0;
                     };
                  }
                  window.addEventListener("load"onLoadfalse);
               }
               function onLoad() {
                  websocket = new WebSocket("ws://"+location.host+"/ws");
                  websocket.onopen = function(evt) { onOpen(evt) };
                  websocket.onclose = function(evt) { onClose(evt) };
                  websocket.onmessage = function(evt) { onMessage(evt) };
                  websocket.onerror = function(evt) { onError(evt) };
               }
               function onOpen(evt) {
                  state.className = "success";
                  state.innerHTML = "Connected to server";
               }
               function onClose(evt) {
                  state.className = "fail";
                  state.innerHTML = "Not connected";
                  connected.innerHTML = "0";
               }
               function onMessage(evt) {
                  var message = evt.data;
                  if (message.startsWith("connected:")) {
                     message = message.slice("connected:".length);
                     connected.innerHTML = message;
                  } else {
                    //console.log("receiving: " + message);
                    received.append(message);
                    var linebreak = document.createElement("br"); //refer to link
                    received.appendChild(linebreak);
                  }
               }
               function onError(evt) {
                  state.className = "fail";
                  state.innerHTML = "Communication error";
               }
               function send_cmd() {
                  var message = cmd_str.value;
                  cmd_str.value = "";
                  websocket.send(message);
               }
            </script>
         </section>
      </body>
   </head>  
</html>

Friday, July 24, 2020

Get Fun with Google Assistant

I first time read about it from Inquirer post of a Mod of Intercom device. And here is the Youtube clip: l
So I started to read about Google Assistant SDK. So basically there are two choices, based on what hardware platform you have: 1) with a Python-based library; 2) use gRPC API.
The Python lib option is a turnkey solution for anyone who wants to quickly integrate the Assistant into a prototype device. It supports linux-armv7l(include Raspberry Pi3) and linux-x86_64. gRPC is a modern open source high performance RPC framework that can run in any environment. It was initially created by Google, and was turned to open source in 2015.

For the Python solution, refer instruction here. If run into problem with 'python3 -m venv env', make sure the 'env' folder does not exist, also, refer to this stackoverflow post. If you already had done this step before, and want to upgrade, rename the old folder in case you have config/json file or code there, run the venv command, then copy over config file and your code after that.

Wednesday, July 22, 2020

学习舵轮(kubernetes)和埠(docker) - kind

Minikube and Kind are the two recommended by the official Kubernetes website.
Refer this for the comparison between minikube, kind and k3s. Here is a brief description of Kind:
Kind is another Kubernetes SIGs project but is quite different compared to minikube. As the name suggests it moves the cluster into Docker containers. This leads to a significantly faster startup speed compared to spawning VM.

For install, refer to https://kubernetes.io/docs/setup/learning-environment/kind/
On Linux, the simple way is to run below to download the single executable:
curl -Lo ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(uname)-amd64
Change it to be executable and add it to path, all done. If like to learn about the hot 'Go', you can use Go to get it, and even build from source.
It has dependency of docker. To install docker: refer to this old post.
Or on Ubuntu, run: sudo apt install docker.io

To play with kind, need to install kubectl:
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
As kind Quick Start mentioned, after kind and kubectl installed, things can be tried are:
  • Creating a Kubernetes cluster is as simple as kind create cluster
  • List your kind clusters: kind get clusters
  • Interact with the created default cluster: kubectl cluster-info --context kind-kind
  • Delete cluster: kind delete cluster 
  • Load image to kind-2 cluster: kind load docker-image my-custom-image --name kind-2

When I  try to use my docker image (by running 'docker pull quyq/ifun:v0.3') to create cluster, I got error:

ERROR: failed to create cluster: failed to generate kubeadm config content: failed to get kubernetes version from node: failed to get file: command "docker exec --privileged kind-control-plane cat /kind/version" failed with error: exit status 1

This sounds like the same issue as kind issue 1288.  Run kind create cluster --image d7fe9f82ee94 -v 1 
shows: cat: can't open '/kind/version': No such file or directory
Per BenTheElder's comment, snap is in the known-issue document, the snap docker package
has a number of issues, e.g. no access to temp directories. I don't recommend snap for docker
and we don't really support this. Resolution was to install docker via apt-get detailed on their
page here. But for me I was usng the curl command installed kind, and even upgrade to version
0.8.1 doesn't help. I have no issue to create the cluster with default kindest/node image.  By
 checking this, turns out I need a customized docker image for kind.