#!/usr/bin/python3

####
# This is a sample program, which introduces the functions and applications of the Baby-LIN-DLL. To run this program you need the current LINWorks software and
# a Baby-LIN device from Lipowsky Industrie-Elektronik GmbH. Make sure that there is a USB connection between your PC and the Baby-LIN-Device and
# that a voltage of 8-26 VDC is applied to the LIN-Bus.  
#
# Table of Contents:
# 1. Display Version of Baby-LIN-DLL und Wrapper
# 2. Connection with the Baby-LIN-Device
# 3. Connection to the LIN-Channel
# 4. Write SerialNumber to signal
# 5. Excecute macro and processing MacroResultString
# 6. Use of getsig/setsig for signal handling
# 7. Frame registration and display of the framecallbacks 
# 8. Error handling
####

from __future__ import unicode_literals
from asyncio.windows_events import NULL
from ctypes import *
from hashlib import new
import os, sys, argparse, six

try:
    # import the BabyLIN Python wrapper
    import BabyLIN_library
except ImportError as e:
    six.print_(e)
    
def parse_arguments():
    """ """
    # get sdf file from the path where the executable is
    parser = argparse.ArgumentParser(description="run `main.py` on sdf file")
    parser.add_argument("-s", "--sdf", help="sdf file to load",
                        default="Example.sdf")
    parser.add_argument("-v", "--verbose", action="count", default=0)
    args = parser.parse_args()

    return args.sdf, args.verbose

def main(sdf_name, verbose):
    """ Standard example. """

    def framecallback(handle, frame):
        """ frame callback to be used later."""
        six.print_(frame)
        return 0

    if verbose == 1:
        six.print_("Using dynamic library " + BabyLIN.BABYLIN_DLL_PATH_NAME)

    # create the BabyLIN class contained in BabyLIN_DLL.py
    BabyLIN = BabyLIN_library.create_BabyLIN()

    # inject BabyLIN names into local namespace, so you can, e.g. write
    # BLC_getVersion instead of BabyLIN.BLC_getVersion
    for k, v in BabyLIN.__dict__['_libNames'].items():
        globals()[k] = getattr(BabyLIN, k)

    if verbose == 1:
        six.print_("Using sdf file " + sdf_name)

    try:
        six.print_("Test programm started")
        six.print_("#####################################")
        six.print_("")

        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # 1. Display Version of Baby-LIN-DLL und Wrapper
        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # Display the version of the BabyLIN DLL and the .net Wrapper
        six.print_("DLL and wrapper version are read out")
        six.print_("")
        dllVersion = BLC_getVersionString()
        wrapperVersion = BLC_getWrapperVersion()
        six.print_("BabyLIN version: ", dllVersion)
        six.print_("BabyLIN python Wrapper version: ", wrapperVersion)
        six.print_("")
        
        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # 2. Connection with the Baby-LIN-Device
        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # Search for Baby-LIN devices
        # The BLC_getBabyLinPortsTimeout() function is also searching for network devices
        # If you are using only Baby-LIN devices with USB port, you can use BLC_getBabyLinPorts()
        portCount = 100 # Find up to 100 devices
        six.print_("Search for Baby-LIN-Devices for connection...")
        portList = BLC_getBabyLinPorts(portCount)
        if portList == 0:
            six.print_("Could not find any Baby-LIN devices.")
            sys.exit(-1)

        six.print_(str(len(portList)) +  " devices were found for the connection")
        six.print_("")

        portList = BLC_getBabyLinPortsTimout(portCount, 3000)
        if portList == 0:
            six.print_("Could not find any Baby-LIN devices.")
            sys.exit(-1)
        
        # In this example, we will be using the first found Baby-LIN device
        if len(portList) < 1:
            six.print_("Could not find any Baby-LIN devices.")
            sys.exit(-1)
        port = portList[0]

        # Open a connection to the first found BabyLIN
        six.print_("The connection to the first Baby-LIN-Device of the portlist is established.")
        handle = BLC_openPort(port)
        if handle == NULL:
            six.print_("The connection to the BabyLIN could not be opened. Please check, that the COM Port is correct.")
            sys.exit(-1)

        # Download the SDF file into the BabyLIN device
        six.print_("SDF download...")
        six.print_("")
        rc = BLC_loadSDF(handle, sdf_name, 1)
        if rc != 0:
            six.print_("The SDF file could not be loaded into the BabyLIN. Please check, that the filename is correct.")
            sys.exit(-1)  

        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # 3. Connection to the LIN-Channel
        # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        # Get the number of available channels
        six.print_("Output of the channel info")
        channelCount = BLC_getChannelCount(handle)
        six.print_("available channels: " + str(channelCount))
        
        # the example will open the first device with an included
        # LIN channel, download the sdf to it, start the LIN bus,
        # register a frame-callback and watch the incoming LIN-frames
        # in the callback.

                       # open the device(s)
        conHandle    = (handle for port in portList)

                       # get the device's number of channels
        channelCount = ((BLC_getChannelCount(h), h) for h in conHandle)

                       # among these, look for the first LIN channel:
        channelRange = ((range(chNr), h) for chNr, h in channelCount)

                       # first, get the corresponding channel handles
        channelHandle     = ((BLC_getChannelHandle(h, channelIndex), h)
                            for r, h in channelRange for channelIndex in r)

                       # for each channel (handle), get the channel info
        chInfo       = ((BLC_getChannelInfo(ch), h, ch) for ch, h in channelHandle)

                       # using the channel info, filter the LIN channels
                       # using 'info.type == 0'
        conH_chH     = ((h, ch) for info, h, ch in chInfo if info.type == 0)

        for conHandle, channelHandle in conH_chH:

            # for debugging, print ChannelInfo
            channelInfos = BLC_getChannelInfo(channelHandle)
            six.print_("Channel info: Name=" + str(channelInfos.name) + " , Type=" + str(channelInfos.type) + " , MaxBaudrate=" + str(channelInfos.maxbaudrate))

            # start the LIN bus
            six.print_("Connect to channel number 1 and start the schedule number 0")   
            six.print_("")
            scheduleNr = 0
            rc = BLC_sendCommand(channelHandle, "start schedule " + str(scheduleNr) + ";")
            if rc != 0:
                six.print_("Could not start the LIN bus.")
                sys.exit(-1)

            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # 4. Write SerialNumber to signal
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # Write Signal Serial_Number
            # The SDF provides the following signals: SN_Byte_0, SN_Byte_1, SN_Byte_2 ,SN_Byte_3, SN_Byte_4, SN_Byte_5, SN_Byte_6, SN_Byte_7
            # With the BLCvarWrite() command the signals can all be written with one operation. The varible data_len determines the number of signals to be set. 
            # Exactly one byte is assigned to each signal
            six.print_("Precessing the serial number")
            signal_nr = 0
            data_len = 8
            data = bytes([83, 78, 48, 49, 50, 51, 52, 53]) # ASCII-Code:  "SN012345"
            rc = BLC_varWrite(channelHandle, signal_nr, data, data_len)
            if rc != 0:
                six.print_("Could not write into signal Serial_Number.")
                sys.exit(-1)

            # Read signal Serial_number for control
            # The BLC_varRead() command reads a certain number of signals and stores them in a byte buffer, which is passed to the function when it is called.
            # The number of signals to be read is determined by the variable lenght.
            lenght = 8
            SignalValue = BLC_varRead(channelHandle, signal_nr, lenght)
            if SignalValue == 0:
                six.print_("Could not read the signal Serial_Number.")
                sys.exit(-1)
            six.print_("Serial number set via BLC_varWrite command")
            six.print_("")
            
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # 5. Excecute macro and processing MacroResultString
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # Execute 01_process_SerialNumber
            # In this macro the data from the SN_Byte signals are read out and combined to a result string.
            # The Baby_LIN_DLL provides a set of Baby_LIN commands which can be executed with the BLC_sendCommand().
            # 
            # The macro_exec command executes the macro with the passed macro number. The BLC command does not wait until the macro is fully executed.
            # This must be implemented by the user with the function BLC_macro_result. As long as the macro is still executed, the BLC function returns the value 150.
            six.print_("Create MacroResultString out of serial number bytes")
            macro_nr = 0
            return_value = 0
            timeout_ms = 250 
            rc = BLC_sendCommand(channelHandle, "macro_exec " + str(macro_nr) + ";")
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)
            rc = BLC_macro_result(channelHandle, macro_nr, timeout_ms)
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)

            # Get MacroResultString
            # When executing a macro it returns a result string after successful completion.This can be set additionally by MAcro command print.
            # With parameter passing the values from the signals can be put together to a result string easily. The encoding of the output can also be set,
            # which is shown by the two outputs in ASCII code and HEXByte code.
            MacroResultStringASCII = BLC_getMacroResultString(channelHandle, macro_nr)
            six.print_("Serial number: " + MacroResultStringASCII + "(ASCII Code)")
            six.print_("")
            
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # 6. Use of getsig/setsig for signal handling
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # Use of getsig and setsig with BLC_sendCommand()
            # The BabyLIN commands getsig and setsig are responsible for reading and setting individual signals. 
            # The signal used is determined by the signal number. This can be found in the bus description of the SDF.
            # The signal_flag can be used to determine at which time or event the signal is to be read out:
                # signal_flag = 0x00, returns the last value written to the bus signal. 
                # signal_flag = 0x01, reset fresh flag and wait for fresh signal value appearing on bus.
                # signal_flag = 0x02, return signal value as result, if fresh value is availble, otherwise returns RETCODE_OPERATION_PENDING
            six.print_("Set the bus signals of brightness by setsig and getsig command")
            signal_nr = 8
            signal_flag = 0
            index = 0
            luminanceValue = 100
            BLC_sendCommand(channelHandle, "getsig " + str(signal_nr) + " " + str(signal_flag) + ";")
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)
            rc = BLC_lastAnswerHasData(channelHandle)
            if rc == 0:
                ByteValue = BLC_getAnswerByIndex(channelHandle, index)
                six.print_("Current luminance configuration: " + str(ord(ByteValue)))

            # Signal value luminance is set to 100 with BLC_sendCommand "setsig"
            rc = BLC_sendCommand(channelHandle, "setsig " + str(signal_nr) + " " + str(luminanceValue) + ";")
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)

            # Control setsig Command with readout the Signal value again via getsig
            rc = BLC_sendCommand(channelHandle, "getsig " + str(signal_nr) + " " + str(signal_flag) + ";")
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)
            rc = BLC_lastAnswerHasData(channelHandle)
            if rc == 0:
                ByteValue = BLC_getAnswerByIndex(channelHandle, index)
                six.print_("Luminance increased to 100")
                six.print_("Current luminance configuration: " + str(ord(ByteValue)))
                six.print_("")

            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # 7. Frame registration and display of the framecallbacks
            # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
            # Here we will subscribe to get frames and write their data
            # The disframe command can be used to subscribe to specific frames. These are determined by the frame ID.
            # If you pass 0xff as parameter, a special case is executed and all frames defined in the SDf are subscribed.
            # The frames are defined as a structure in the DLL and thus offer the possibility to display all information, such as the FrameID or the timestamp.
            six.print_("Subscribe to Frames")

            # Subscribe to frames
            FrameIDForAllFrames = 0xff
            rc = BLC_sendCommand(channelHandle, "disframe " + str(FrameIDForAllFrames) + " 1;")
            if rc != 0:
                six.print_("BLC command could not be executed.")
                sys.exit(-1)

            # the output of the callback will fill up the screen quickly
            # press <ENTER> to see the incoming frames, and <ENTER> again
            # to stop the output
            try:
                p =  "Starting frame callback now...\n"
                p += "Press <Enter> to start and stop"
                input(p)
            except Exception as e:
                pass

            # register the frame-callback
            BLC_registerFrameCallback(channelHandle, framecallback)

            try:
                input("") # waiting for the next <enter>
            except Exception as e:
                pass

            # de-register the frame-callback
            BLC_registerFrameCallback(channelHandle, None)

            # stop the LIN-bus
            BLC_sendCommand(channelHandle, "stop;")

            # close all devices. end of example.
            BLC_closeAll()
            break
    # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    # 8. Error handling
    # ------------------------------------------------------------------------------------------------------------------------------------------------------------------------    
    except BabyLIN.BabyLINException as e:
        six.print_(e)
        
if __name__ == '__main__':
    sdf, verbose = parse_arguments()

    try:
        main(sdf, verbose)
    except KeyboardInterrupt:
        pass

