Custom arduino controller in blender
You're reading binary data from the serial port, so that is why you have a bytes string (indicated by the b'' style of quoting). To convert it to a string, use

as_bytes = ser.readline()
line = as_bytes.decode('utf-8')


To get a repeated call to a "loop" function, it's easiest to start a modal operator with a high-frequency timer.

import bpy


class SERIAL_OT_arduino_read(bpy.types.Operator):
    bl_idname = "serial.arduino_read"
    bl_label = "Read From Arduino"

    def execute(self, context):
        as_bytes = self.ser.readline()
        line = as_bytes.decode('utf-8').strip()
        print('Serial line:', line)

        return {'FINISHED'}

    def modal(self, context, event):
        if event.type == 'TIMER':
            self.execute(context)
        elif event.type == 'LEFTMOUSE':  # Confirm
            self._finish()
            return {'FINISHED'}
        elif event.type in ('RIGHTMOUSE', 'ESC'):  # Cancel
            self._finish()
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.ser = serial.Serial("COM4", 9600)
        self.timer = context.window_manager.event_timer_add(0.01, context.window)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def _finish(self):
        context.window_manager.event_timer_remove(self.timer)


bpy.utils.register_class(SERIAL_OT_arduino_read)

# test call
bpy.ops.serial.arduino_read('INVOKE_DEFAULT')


This will read the serial port approximately 100x per second. Blender can still be used while this operator is running.

I haven't tested this code, just copied parts of the documentation and mixed it with your code.