4 Secondary Payload Telemetry Notes
Mark Jessop edytuje tę stronę 2019-12-26 14:42:11 +10:30

These notes are to help set up a multiple-payload Wenet system, where a 'primary' Wenet payload multiplexed packets from 'secondary' payloads into the main RF downlink.

Wifi AP Setup

I used this guide to setup a Pi Zero W to be a Wifi AP: 6bc5839652/configuration/wireless/access-point.md

SHSSP 2019 Pi Setup Notes

A few notes to remind myself what I had to change to set up the SHSSP Wifi-enabled master payload. These changes are in relation to the WiFi AP setup guide linked above.

/boot/config.txt

Added:

dtoverlay=pi3-disable-bt
dtparam=watchdog=on

/etc/dhcpcd.conf

<at bottom of file>
interface wlan0
    static ip_address=192.168.1.1/24

/etc/hostapd/hostapd.conf

interface=wlan0
driver=nl80211
ssid=SHSSP-SkyFi
hw_mode=g
channel=3
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=<passphrase here>
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP

/home/pi/checkAP.sh

NOTE: This script was replaced by check_temp.py below. This file was run on a 10-minute cron job, to check if the secondary payload was still connected, and if not, reboot the WiFi AP. z19a.local was the hostname of the secondary payload.

ping -c4 z19a.local > /dev/null

if [ $? != 0 ]
then
  echo $(date) >> /home/pi/apcheck.txt
  echo "restart AP" >> /home/pi/apcheck.txt
  sudo service hostapd stop
  sleep 2
  sudo ifconfig wlan0 down
  sleep 5
  sudo ifconfig wlan0 up
  sleep 2
  sudo service hostapd restart
fi

/home/pi/check_temp.py

This script was started on boot via rc.local. It checked for the presence of the secondary payload on the wifi network, and if not present, restarted the wifi. It would also disable the wifi if the RPi CPU temperature reached a user-defined threshold.

#!/usr/bin/env python
#
#   Example generation of some basic 'Secondary Payload' packets.
#   This is a method by which another process or payload can inject data into
#   the Wenet downlink telemetry channel.
#
#   Refer to the equivalent sec_payload_rx_example in wenet/rx/
#   for how to receive these packets on the ground-station end.
#
#   Copyright (C) 2018  Mark Jessop <vk5qi@rfhead.net>
#   Released under GNU GPL v3 or later
#

import socket, struct, time, json, random, subprocess


def emit_secondary_packet(id=0, packet="", repeats = 1, hostname='<broadcast>', port=55674):
    """ Send a Secondary Payload data packet into the network, to (hopefully) be
        transmitted by a Wenet transmitter.

        Keyword Arguments:
        id (int): Payload ID number, 0 - 255
        packet (): Packet data, packed as a byte array. Maximum of 254 bytes in size.
        repeats (int): Number of times to re-transmit this packet. Defaults to 1.
        hostname (str): Hostname of the Wenet transmitter. Defaults to using UDP broadcast.
        port (int): UDP port of the Wenet transmitter. Defaults to 55674.

    """


    telemetry_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    # Set up the telemetry socket so it can be re-used.
    telemetry_socket.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1)
    telemetry_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # We need the following if running on OSX.
    try:
        telemetry_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
    except:
        pass

    # Place data into dictionary.
    data = {'type': 'WENET_TX_SEC_PAYLOAD', 'id': int(id), 'repeats': int(repeats), 'packet': list(bytearray(packet))}

    # Send to target hostname. If this fails just send to localhost.
    try:
        telemetry_socket.sendto(json.dumps(data), (hostname, port))
    except socket.error:
        telemetry_socket.sendto(json.dumps(data), ('127.0.0.1', port))

    telemetry_socket.close()


# Global text message counter.
text_message_counter = 0

def create_text_message(message):
    """ Create a text message packet, for transmission within a 'secondary payload' message.
    This is in the same format as a standard wenet text message, however the maximum message
    length is shortened by 2 bytes to 250 bytes, due to the extra header overhead.

    Keyword Arguments:
    message(str): A text message as a string, up to 250 characters in length.
    """

    global text_message_counter

    text_message_counter = (text_message_counter+1)%65536
    # Clip message if required.
    if len(message) > 250:
        message = message[:250]

    # We will use the Wenet standard text message format, which has a packet type of 0x00,
    # and consists of a length field, a message count, and then the message itself.
    _PACKET_TYPE = 0x00
    _PACKET_LEN = len(message)
    _PACKET_COUNTER = text_message_counter

    # Assemble the packet.
    _packet = struct.pack(">BBH", _PACKET_TYPE, _PACKET_LEN, _PACKET_COUNTER) + message

    return _packet


def create_arbitrary_float_packet(data=[0.0, 0.1]):
    """ Create a payload that contains a list of floating point numbers.

    Keyword Arguments:
    data (list): A list of floating point numbers to package and send.
    """

    # Clip the amount of numbers to send to a maximum of 63 (we only have 252 bytes to fit data into)
    if len(data) > 63:
        data = data[:63]

    # Our packet format will consist of a packet type, a length field, and then the data.
    _PACKET_TYPE = 0x10  # We will define a packet type of 0x10 to be a list of floats.
    _PACKET_LEN = len(data)

    # Convert the list of floats into a byte array representation.
    _float_bytes = bytes("")

    for _val in data:
        _float_bytes += struct.pack(">f", _val)

    # Now assemble the final packet.
    _packet = struct.pack(">BB", _PACKET_TYPE, _PACKET_LEN) + _float_bytes


    return _packet



def get_temperature():
    try:
        data = subprocess.check_output("/opt/vc/bin/vcgencmd measure_temp", shell=True)
        temp = data.split('=')[1].split('\'')[0]
        return float(temp)
    except Exception as e:
        return -1



def send_message(message):

    _txt_packet = create_text_message(message)
    emit_secondary_packet(id=99, packet=_txt_packet, repeats=2)

    print("Sent message:" + message)



def disable_ap():

    # Otherwise, we shutdown the AP.
    try:
        subprocess.check_call("sudo service hostapd stop", shell=True)
        time.sleep(1)
        subprocess.check_call("sudo ifconfig wlan0 down", shell=True)
        _last_action = time.time()
    except Exception as e:
        send_message("ERROR:" + str(e))
        return False

    return True


def enable_ap():

    # Otherwise, we shutdown the AP.
    try:
        subprocess.check_call("sudo ifconfig wlan0 up", shell=True)
        time.sleep(3)
        subprocess.check_call("sudo service hostapd start", shell=True)
        _last_action = time.time()
    except Exception as e:
        send_message("ERROR:" + str(e))
        return False

    return True


def bounce_ap():
    # Otherwise, we shutdown the AP.
    try:
        subprocess.check_call("sudo service hostapd stop", shell=True)
        time.sleep(1)
        subprocess.check_call("sudo ifconfig wlan0 down", shell=True)
        time.sleep(5)
        subprocess.check_call("sudo ifconfig wlan0 up", shell=True)
        time.sleep(3)
        subprocess.check_call("sudo service hostapd start", shell=True)
        _last_action = time.time()
    except Exception as e:
        send_message("ERROR:" + str(e))
        return False

    return True


def ping_test(ip_addr):
    try:
        result = subprocess.check_call("ping -c2 %s" % ip_addr, shell=True)
        return True
    except Exception as e:
        #send_message("Could not contact Secondary Pi!")
        return False




if __name__ == "__main__":

    # Define ourselves to be 'sub-payload' number 3.
    PAYLOAD_ID = 99

    DISABLE_TEMP_LIMIT = 80.0
    ENABLE_TEMP_LIMIT = 78.0

    AP_INHIBIT = False

    SISTER_PAYLOAD = "z19a.local"
    SISTER_LAST_HEARD = 0
    SISTER_TIMEOUT = 6

    try:
        while True:
            # Get the system temperature
            _temp = get_temperature()

            _message = "CPU Temperature: %.1f degC" % _temp
            print(_message)



            _txt_packet = create_text_message(_message)
            emit_secondary_packet(id=PAYLOAD_ID, packet=_txt_packet, repeats=2)

            if _temp > DISABLE_TEMP_LIMIT:
                # Overtemp - disable AP, if it's enabled
                if AP_INHIBIT == False:
                    _success = disable_ap()
                    AP_INHIBIT = True
                    send_message("DISABLED WIFI AP DUE TO OVERHEATING.")

            elif _temp < ENABLE_TEMP_LIMIT:
                # Undertemp - re-enable AP, if it is disabled
                if AP_INHIBIT:
                    _success = enable_ap()
                    AP_INHIBIT = False
                    send_message("ENABLED WIFI AP")
            else:
                pass


            _sister_ok = ping_test(SISTER_PAYLOAD)
            if _sister_ok:
                SISTER_LAST_HEARD = 0
            else:
                SISTER_LAST_HEARD += 1
                send_message("Could not contact sister payload (%d/%d)" % (SISTER_LAST_HEARD,SISTER_TIMEOUT))

            if SISTER_LAST_HEARD > SISTER_TIMEOUT:
                if AP_INHIBIT == False:
                    send_message("No contact from sister payload. Restarted AP.")
                    bounce_ap()
                    SISTER_LAST_HEARD = 0


            time.sleep(30)




    # Keep going unless we get a Ctrl + C event
    except KeyboardInterrupt:
        print("Closing")