mbed STM32F4DISCOVERY simple LED demo

This demo shows you how to control the STM32F4DISCOVERY LEDs using mbed.
I use mbed from inside PlatformIO.

#include <mbed.h>

DigitalOut greenLED(PD_12);
DigitalOut orangeLED(PD_13);
DigitalOut redLED(PD_14);
DigitalOut blueLED(PD_15);

int main() {
  while(1) {
    // Cycle LEDs in order
    // NOTE: You can toggle a LED using
    //  blueLED = !blueLED;
    blueLED = 0;
    greenLED = 1;
    wait(0.25);
    greenLED = 0;
    orangeLED = 1;
    wait(0.25);
    orangeLED = 0;
    redLED = 1;
    wait(0.25);
    redLED = 0;
    blueLED = 1;
    wait(0.25);
  }
}

 

Posted by Uli Köhler in mbed, PlatformIO

STM32F4DISCOVERY LED pin reference / pinout

The STM32F4 DISCOVERY board has these LEDs:

  • Green LED: PD12, active-high (LED emits light when PD12 is high)
  • Orange LED: PD13, active-high (LED emits light when PD13 is high)
  • Red LED: PD14, active-high (LED emits light when PD14 is high)
  • Blue LED: PD15, active-high (LED emits light when PD15 is high)
Posted by Uli Köhler in Electronics, Embedded

What is Composite Complete Remission (CRc) in cancer research?

Note about medical information:
This information is presented for informational purposes only and is intended for professionals. While we strive to provide accurate information, this information might be outdated, unintentionally misleading or incorrect. Consult a medical professional and/or read the primary sources cited in our article before basing any decision on this information.

Composite Complete Remission (CRc) is the sum of

  • Complete Remission (CR), i.e. the primary disease is no longer active and the blood counts have normalized.
  • Complete Remission with incomplete hematological recovery (CRi), i.e. the primary disease is no longer active but at least one of the blood cell lines has not recovered to normal levels.
  • Complete Remission with incomplete platelet recovery (CRp)i.e. the primary disease is no longer active but the platelet count has not recovered to normal levels.

Note that some authors include CRp in CRi while others list it separately.

Source: NCT00989261: Efficacy Study for AC220 to Treat Acute Myeloid Leukemia (AML) (ACE) (CRc = CR + CRi) or Cortes et al (2018) (CRc = CR + CRi + CRp)

Example:

If you have a CR rate of 50\% and and a CRi rate of 5\%, the CRc rate is 50\% + 5\% = 55\%.
If you have a CR rate of 50\% and and a CRi rate of 4\% and a CRp rate or 2\%, the CRc rate is 50\% + 4\% + 2\% = 56\%.

How is full recovery of the blood cell lines defined?

This depends on the type of primary disease (and sometimes there are slightly conflicting definitions).

For example, Döhner et al (2017) present these numbers for CRi in acute myeloid leukemia:

  • Neutrophils \leq \frac{1000}{\mu l} (in other words, grade 2 or higher grade neutropenia)
  • Platelets \leq \frac{100\,000}{\mu l}

If these criteria for CRi are not fulfilled (even if the blood counts have not reached normal levels), the state is defined as CR.

Note that in this case there are no limits for erythrocyte count recovery, hence the patient might require blood transfusions due to low levels of hemoglobin even though according to this definition he is considered to be in a state of complete remission (CR).

The meaning of CR as opposed to CRi, CRp or CRc is therefore more relevant as endpoint of a medical study, but not so much as indicator that the patient has recovered to a point that allows discontinuation of treatment.

Also see What is grade I/II/III/IV neutropenia?

Posted by Uli Köhler in Bioinformatics

Is pypng 16-bit PNG encoding faster using pypy on the Raspberry Pi?

In our previous post How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng we investigated how to use the pypng library to save 10-bit raw Raspberry Pi Camera images to 16-bit PNG files.

However, saving a single image took ~26 seconds using CPython 3.7.3. Since pypy can provide speedups to many Python workloads, we tried using pypy3 7.0.0 (see How to install pypy3 on the Raspberry Pi) to speed up the PNG encoding.

Results

pypng PNG export seems to be one of the workloads that are much slower using pypy3.

  • CPython 3.7.3: Encoding took 24.22 seconds
  • pypy3 7.0.0: Encoding took 266.60 seconds

Encoding is more that 10x slower when using pypy3!

Hence I don’t recommend using pypy3 to speed up pypng encoding workloads, at least not on the Raspberry Pi!

Full example

This example is derived from our full example previously posted on How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng:

#!/usr/bin/env python3
import time
import picamera
import picamera.array
import numpy as np
import png

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

# Write to PNG
print("Writing 16-bit PNG...")
t0 = time.time()
with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)
t1 = time.time()

print(f"Encoding took {(t1 - t0):.2f} seconds")

 

Posted by Uli Köhler in Python, Raspberry Pi

How to install pypy3 on the Raspberry Pi

This post shows you an easy way of getting pypy3 running on the Raspberry Pi. I used Raspbian Buster on a Raspberry Pi 3 for this example. On Raspbian buster this will install pypy3 7.x!

First install pypy3 and virtualenv:

sudo apt update && sudo apt -y install pypy3 pypy3-dev virtualenv

Now we can create a virtualenv to install pypy packages into:

virtualenv -p /usr/bin/pypy3 ~/pypy3-virtualenv

Now we can activate the virtualenv. You need to do this every time you want to use pypy, for each shell / SSH connection separately:

source ~/pypy3-virtualenv/bin/activate

If your shell prompt is now prefixed by (pypy3-virtualenv) you have successfully activated the virtualenv:

(pypy3-virtualenv) uli@raspberrypi:~ $

Now python points to pypy3 and pip will install packages locally to ~/pypy3-virtualenv.

Now you can use e.g.

python3 myscript.py

to run your script (both python and python3 will point to pypy3 if you activated the virtual environment!).

Note: Installing pypy3-dev is not strictly neccessary to get pypy3 running, but you need it in order to compile native librarie like numpy.

Posted by Uli Köhler in Python, Raspberry Pi

How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng

In our previous post How to capture RaspberryPi camera 10-bit raw image in Python we showed how you can use the picamera Python library to capture raw 10-bit image data.

The PNG image format supports storing 16-bit image data. This post shows you how to do that using the NumPy arrays we generated in our previous post. We are using the pypng library.

with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)

Note that the resulting PNGs are ~9.9 Megabytes in size and saving them using pypng takes about 27 seconds to save the image on my Raspberry Pi 3!

For comparison, the raw NumPy data is ~29 Megabytes whereas the compressed NumPy data is 9.3 Megabytes,

  • Raw NumPy data (np.save): 29 Megabytes, takes 0.11 seconds to save.
  • Compressed NumPy data (np.savez_compressed): 9.3 Megabytes, take 12 seconds to save.

So if your motivation for using PNG is to save space, you might be better off using NumPy compressed data, especially if you need to save many camera frames in quick succession and hence are limited.

In case you need to use PNGs, you might want to check Pypy since pypng is a pure Python library and hence might benefit from Pypy’s increased execution speed. However, in practice, pypy3 is more than 10x slower. Please read our detailed analysis at Is pypng 16-bit PNG encoding faster using pypy on the Raspberry Pi?

Full example:

#!/usr/bin/env python3
import picamera
import picamera.array
import numpy as np
import png

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

# Write to PNG
print("Writing 16-bit PNG...")
with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)

 

Posted by Uli Köhler in Raspberry Pi

How to capture RaspberryPi camera 10-bit raw image in Python

You can use the picamera Python library to capture a raw sensor image of a camera attached to the Raspberry Pi via CSI:

#!/usr/bin/env python3
import picamera
import picamera.array
import numpy as np

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

rawimg is a numpy uint16 array of dimensions (w, h, 3), e.g. (1944, 2592, 3) and contains integer values from 0 to 1023.

You can, for example, save it in a NumPy file using

np.save("rawimg.npy", rawimg) # Reload with np.load("rawimg.npy")

or save it in a compressed format using

np.savez_compressed("rawimg.npz", rawimg) # Reload with np.load("rawimg.npz")
Posted by Uli Köhler in Python, Raspberry Pi

How to fix ModuleNotFoundError: No module named ‘picamera’

Problem:

You want to run a Python script using the Raspberry Pi camera but you see an error message like

Traceback (most recent call last):
  File "mycamera.py", line 2, in <module>
    import picamera
ModuleNotFoundError: No module named 'picamera'

Solution:

You need to install the picamera Python module using pip:

sudo pip3 install picamera

or, if you are still using Python 2.x:

sudo pip install picamera

In case you see

sudo: pip3: command not found

install pip3 using

sudo apt install -y python3-pip

 

Posted by Uli Köhler in Python, Raspberry Pi

What is grade I/II/III/IV neutropenia?

Note about medical information:
This information is presented for informational purposes only and is intended for professionals. While we strive to provide accurate information, this information might be outdated, unintentionally misleading or incorrect. Consult a medical professional and/or read the primary sources cited in our article before basing any decision on this information.

Neutropenia is a reduced number of neutrophil granulocytes in the blood. Normally, one would expect a number \geq 1500\ \frac{\text{neutrophils}}{\mu l} in the peripheral blood, with a typical value of 2500-4500 \frac{\text{neutrophils}}{\mu l}.
The decreased number of neutrophils is associated with reduced immune response and hence causes an increased chance of infection. Patients with neutropenia have a risk of developing febrile neutropenia (also known as neutropenic sepsis). Febrile neutropenia is a medical emergency and hence requires immediate treatment!

Neutropenia can be easily diagnosed using a complete blood count to count the number of neutrophils in the peripheral blood. In case a lab report does not contain the absolute neutrophil count (ANC), you can also multiply the percentage of neutrophils with the leukocyte count.

The different grades of neutropenia are defined in the Common Terminology Criteria for Adverse Events (CTCAE). On this page, we use Version 5.0 published in 2017.

Grade 1 neutropenia

is defined as an absolute neutrophil count (ANC) of

\leq 1500\ \frac{\text{neutrophils}}{\mu l} or equivalently
\leq 1500\ \frac{\text{neutrophils}}{mm³} or equivalently
\leq 1.5\ billion\ \frac{\text{neutrophils}}{l} or equivalently
\leq 1.5e9\ \frac{\text{neutrophils}}{l}

Grade 2 neutropenia

is defined as an absolute neutrophil count (ANC) of

1000\dotsb 1500\ \frac{\text{neutrophils}}{\mu l} or equivalently
1000\dotsb 1500\ \frac{\text{neutrophils}}{mm³} or equivalently
1000\ billion\ \dotsb 1500\ billion\ \frac{\text{neutrophils}}{l} or equivalently
1e9\dotsb 1.5e9\ \frac{\text{neutrophils}}{l}

Grade 3 neutropenia

is defined as an absolute neutrophil count (ANC) of

500\dotsb 1000\ \frac{\text{neutrophils}}{\mu l} or equivalently
500\dotsb 1000\ \frac{\text{neutrophils}}{mm³} or equivalently
500\ billion\ \dotsb 1000\ billion\ \frac{\text{neutrophils}}{l} or equivalently
0.5e9\dotsb 1e9\ \frac{\text{neutrophils}}{l}

Grade 4 neutropenia

is defined as an absolute neutrophil count (ANC) of

\leq 500\ \frac{\text{neutrophils}}{\mu l} or equivalently
\leq 500\ \frac{\text{neutrophils}}{mm³} or equivalently
\leq 500\ million\ \frac{\text{neutrophils}}{l} or equivalently
\leq 0.5e9\ \frac{\text{neutrophils}}{l}

Grade 5 neutropenia

is defined as death (like all Grade 5 adverse events).

Posted by Uli Köhler in Bioinformatics

NodeJS argparse minimal example

Install argparse using

npm i --save argparse

Optional argument:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['-u', '--url'], {help: "The URL to use"});
const args = parser.parseArgs();

// Example usage
console.log(args.url) // null if no such argument

If you want to make an argument mandatory, use required: true:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['-u', '--url'], {help: "The URL to use", required: true});
const args = parser.parseArgs();

// Example usage
console.log(args.url)

In case the user does not provide the argument, it will print

usage: CLI.js [-h] -u URL
CLI.js: error: Argument "-u/--url" is required

Positional arguments

This positional argument will always be required:

const ArgumentParser = require('argparse').ArgumentParser;
const parser = new ArgumentParser({});
parser.addArgument(['url'], {help: "The URL to use"});
const args = parser.parseArgs();

// Example usage
console.log(args.url)

 

Posted by Uli Köhler in Javascript, NodeJS

How to just show HTTP headers using wget

Use --server-response -qO /dev/null:

wget --server-response -qO /dev/null [URL]

Example:

wget --server-response -qO /dev/null https://techoverflow.net

Example output:

HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Mon, 18 Nov 2019 02:15:19 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
Set-Cookie: cookielawinfo-checkbox-necessary=yes; expires=Mon, 18-Nov-2019 03:15:18 GMT; Max-Age=3600; path=/
Set-Cookie: cookielawinfo-checkbox-non-necessary=yes; expires=Mon, 18-Nov-2019 03:15:18 GMT; Max-Age=3600; path=/
Link: <https://techoverflow.net/wp-json/>; rel="https://api.w.org/"

 

Posted by Uli Köhler in Shell

gzip-compress in-memory in Go

In Go, you can use the built-in gzip library together with bytes.Buffer to compress a []byte to obtain a []byte containing gzip-compressed data:

content := "Hello World!"
buf := &bytes.Buffer{}
gzWriter := gzip.NewWriter(buf)
gzWriter.Write([]byte(content))
gzWriter.Close()

This full example compresses the data in-memory and writes it to a file called text.txt.gz afterwards. Note that you can also gzip directly into a file and if you don’t need to do anything else with the compressed data, you might as well have a look at our previous post How to write to gzipped file in Go.

package main

import (
    "compress/gzip"
    "os"
)

func main() {
    content := "Hello World!"
    // Initialize gzip
    buf := &bytes.Buffer{}
    gzWriter := gzip.NewWriter(buf)
    gzWriter.Write([]byte(content))
    gzWriter.Close()
    // Convert buffer to
    ioutil.WriteFile("test.txt.gz", buf.Bytes(), 0644)
}

You can use zcat test.txt.gz to see that the content has been written to the file correctly.

Posted by Uli Köhler in Go

How to write to gzipped file in Go

This example shows you how to directly write gzip-compressed data using Go’s gzip library.

First, open the file and use gzip.NewWriter() to create a new io.Writer on it:

// Open file
f, _ := os.Create("test.txt.gz")
defer f.Close()
// Create gzip writer
gzWriter := gzip.NewWriter(f)

Now you can gzWriter.Write(). Don’t forget to gzWriter.Close()

content := "Hello World!"
gzWriter.Write([]byte(content))
gzWriter.Close()

Full example:

package main

import (
    "compress/gzip"
    "os"
)

func main() {
    // Open file
    f, _ := os.Create("test.txt.gz")
    defer f.Close()
    // Create gzip writer
    gzWriter := gzip.NewWriter(f)
    // Write content and close
    content := "Hello World!"
    gzWriter.Write([]byte(content))
    gzWriter.Close()
}

You can use zcat test.txt.gz to see that the content has been written to the file correctly.

Posted by Uli Köhler in Go

Go minimal ‘Write file’ example

This is the minimal example that writes a string (Hello World! in this example) to a file (test.txt in this example) using Go.

package main

import "io/ioutil"

func main() {
    content := "Hello World!"
    ioutil.WriteFile("test.txt", []byte(content), 0644)
}

Note that this example:

  • Creates the file with permission mode 0644, i.e. only the owner can write but others can read.
  • Encodes the text as UTF-8
Posted by Uli Köhler in Go

Go equivalent of Python’s io.BytesIO

io.BytesIO in Python 3.x provides a convenient way of having a file-like object that actually streams to/from memory.

In Go, the equivalent problem is to have a io.Reader and/or and io.Writer (i.e. the equivalent of Python’s file-like object) that is backed by a []byte or a string.

Solution: Use bytes.Buffer !

// Initialize an empty buffer (e.g. for writing)
buf := &bytes.Buffer{}

// Initialize a buffer with a []byte content (e.g. for reading)
myBytes := ...
buf := bytes.NewBuffer(myBytes)

// Initialize a buffer with string
myStr := "test 123"
buf := bytes.NewBufferString(myStr)
Posted by Uli Köhler in Go

How to download and parse HTML page in Go

This example uses goquery to request a HTML page (https://techoverflow.net) via the Go net/http client and then uses goquery and a simple CSS-style query to select the <title>...</title> HTML tag and print it’s content.

package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/PuerkitoBio/goquery"
)

func main() {
    // Perform request
    resp, err := http.Get("https://techoverflow.net")
    if err != nil {
        print(err)
        return
    }
    // Cleanup when this function ends
    defer resp.Body.Close()
    // Read & parse response data
    doc, err := goquery.NewDocumentFromReader(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    // Print content of <title></title>
    doc.Find("title").Each(func(i int, s *goquery.Selection) {
        fmt.Printf("Title of the page: %s\n", s.Text())
    })
}

Example output:

Title of the page: TechOverflow

 

Posted by Uli Köhler in Go

A simple reverse proxy example in Go

This example shows you a Go HTTP Server that forwards requests to an upstream Server using a HTTP client. Note that this example does not stream the upstream server’s responses but downloads them to RAM and then serves them to the client (hence you might need to improve upon this example if you intend to serve large files).

Also, note that this example does not implement proper error handling for the upstream request since it’s only intended to serve as a basic starting point for you to develop your own reverse proxy implementation.

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    client := &http.Client{}
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // Perform request
        url := "https://techoverflow.net" + r.URL.Path
        req, _ := http.NewRequest(r.Method, url, r.Body)
        req.Header = r.Header
        resp, _ := client.Do(req)
        // Cleanup when this function ends
        defer resp.Body.Close()
        // Copy headers from request to response
        header := w.Header()
        for name, headers := range resp.Header {
            // Iterate all headers with one name (e.g. Content-Type)
            for _, hdr := range headers {
                header.Add(name, hdr)
            }
        }
        // Read all the response data into a []byte
        body, _ := ioutil.ReadAll(resp.Body)
        // Write header & body
        w.WriteHeader(resp.StatusCode)
        w.Write(body)
    })
    // log.Fatal shows you if there is an error like the
    //  port already being used
    fmt.Println("Listening on port 8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Further reading:

Posted by Uli Köhler in Go

Iterating HTTP client response headers in Go

In this example we iterate all the HTTP headers the server sends back after a HTTP request made with the net/http Go library.

for name, headers := range resp.Header {
    // Iterate all headers with one name (e.g. Content-Type)
    for _, hdr := range headers {
        println(name + ": " + hdr)
    }
}

Full example:

package main

import (
    "net/http"
)

func main() {
    // Perform request
    resp, err := http.Get("https://ipv4.techoverflow.net/api/get-my-ip")
    if err != nil {
        print(err)
        return
    }
    // Cleanup when this function ends
    defer resp.Body.Close()
    // Read all the headers
    for name, headers := range resp.Header {
        // Iterate all headers with one name (e.g. Content-Type)
        for _, hdr := range headers {
            println(name + ": " + hdr)
        }
    }
}

This example will print, for example:

Content-Type: application/octet-stream
Content-Type: text/plain charset=UTF-8
Content-Length: 11
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 16 Nov 2019 00:27:05 GMT

 

Posted by Uli Köhler in Go

Go HTTP Server minimal example

package main

import (
    "fmt"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello world!")
    })

    // log.Fatal shows you if there is an error like the
    //  port already being used
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Save this file as Main.go in a directory called GoHTTPServer, run go build && ./GoHTTPServer. Then, open http://localhost:8080 in your browser and see the result for yourself.

Posted by Uli Köhler in Allgemein

How to get external IPv4/IPv6 address in Go

You can use TechOverflow’s IPv4 HTTP API & IPv6 HTTP API within Go:

import (
    "io/ioutil"
    "net/http"
)

func GetExternalIPv4Address() (string, error) {
    // Perform request
    resp, err := http.Get("https://ipv4.techoverflow.net/api/get-my-ip")
    if err != nil {
        return "", err
    }
    // Cleanup when this function ends
    defer resp.Body.Close()
    // Read all the response data into a []byte
    body, err := ioutil.ReadAll(resp.Body)
    // Decode & print
    return string(body), nil
}

func GetExternalIPv6Address() (string, error) {
    // Perform request
    resp, err := http.Get("https://ipv6.techoverflow.net/api/get-my-ip")
    if err != nil {
        return "", err
    }
    // Cleanup when this function ends
    defer resp.Body.Close()
    // Read all the response data into a []byte
    body, err := ioutil.ReadAll(resp.Body)
    // Decode & print
    return string(body), nil
}

Usage example:

package main

func main() {
    v4, _ := GetExternalIPv4Address()
    v6, _ := GetExternalIPv6Address()
    println(v4)
    println(v6)
}

If you save both files in a directory named GoIPAddress, you can run

$ go build
$ ./GOIPAddress
31.190.168.110
2a03:4012:2:1022::1

 

 

Posted by Uli Köhler in Go