Modifying a python script to output data to a file

I am enhancing the cricket scoreboard project at Build Your Own Scoreboard (wordpress.com)
Normally the board is updated via a web interface which is served by a Raspberry Pi, which then outputs data to an Arduino. As part of this process the Pi saves a file called score.json which is just a string of data in this format:
{“total”:“-45”,“wickets”:“2”,“overs”:“-9”,“runsreq”:“-50”,“bata”:“–0”,“batb”:“–0”,“target”:“-90”,“batanumber”:“-1”,“batbnumber”:“-2”,“dltarget”:“000”,“lastwkt”:“-22”,“pship”:“-23”,“lastman”:“-12”}
This json file is then used by a separate html page which is a display for any spectators

However some of our matches have to be scores with an official app. So someone kindly wrote an integration script which is below.

Everything works fine EXCEPT the app integration scrpt doesn’t create the json file, so the spectator scoreboard doesn’t work

Is it possible to add code to do this into the python script?

I know some of the variables in the json file are not used in the python script, but I’m hoping that once the script forks for the variables it has I’ll be able to work out how to add others. (Or how to adjust the spectator board to just show the data that is available)

import serial, time
import sys
import dbus, dbus.mainloop.glib
from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME =           'org.bluez'
DBUS_OM_IFACE =                'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE =           'org.bluez.GattManager1'
GATT_CHRC_IFACE =              'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID =            '5a0d6a15-b664-4304-8530-3a0ec53e5bc1'
UART_RX_CHARACTERISTIC_UUID =  'df531f62-fc0b-40ce-81b2-32a6262ea440'
LOCAL_NAME =                   'FoSCC-Scoreboard-TGT'

mainloop = None

# Set all the Scoreboard variables to starting positions

Wickets = "0"
Overs = "-0"
BatTotal = "--0"
BatA = "--0"
BatB = "--0"
Target = "---"
Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"

# Open the serial port and set the Scoreboard to starting positions

with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
    # time.sleep(0.1) #wait for serial to open
    if arduino.isOpen():
            print("{} connected!".format(arduino.port))
            arduino.write(Scoreboard.encode())
            print("Data Sent: " + Scoreboard)
            arduino.close

class RxCharacteristic(Characteristic):
    def __init__(self, bus, index, service):
        Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
                                ['write'], service)

    def WriteValue(self, value, options):
        global Wickets
        global Overs
        global BatTotal
        global BatA
        global BatB
        global Target
        global Scoreboard
        try:
            WriteReturn = '{}'.format(bytearray(value).decode())
            ScoreType = WriteReturn[0:3]
            ScoreData = WriteReturn[3:]
	    #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
	    UpdateScoreboard = True
            if ScoreType == "OVB":                                          # Let's deal with Overs Bowled!
                if ScoreData.find(".") == -1:                               # If there's no dot because it's the end of the over, just format the number and use it
                    Overs = ScoreData.rjust(2,"-")
                else:
                    Overs = ScoreData.split(".",1)[0].rjust(2,"-")          # If there's a dot, take whatever's before it.  Cheap way of rounding down without using a float
            elif ScoreType == "B1S":                                        # Batsman 1 score formatted with leading dashes for blanks
                BatA = ScoreData.rjust(3,"-")
            elif ScoreType == "B2S":                                        # Batsman 2 score formatted with leading dashes for blanks
                BatB = ScoreData.rjust(3,"-")
            elif ScoreType == "BTS":                                        # Batting Team Score
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only (split everything before the ampersand)
                BatTotal = TempSplit.split("/",1)[0].rjust(3,"-")           # Everything before the dash is the score, with leading dashes for blanks
                Wickets = TempSplit.split("/",1)[1].replace("10","-")       # Everything after the dash is the wickets, and if it's all out (10) then make it blank because we only have 1 digit
            elif ScoreType == "FTS":
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only
                Target = TempSplit.split("/",1)[0]		            # Everything before the dash is the score, with leading dashes for blanks
		Target = str(int(Target) + 1).rjust(3,"-")
            else:
                print(WriteReturn + " (not used)")
	        #lets not send an update to the scoreboard
	        UpdateScoreboard = False

	    if UpdateScoreboard:
                Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
                print(Scoreboard)
                with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
                    # time.sleep(0.1) #wait for serial to open
                    if arduino.isOpen():
                        print("{} connected!".format(arduino.port))
                        arduino.write(Scoreboard.encode())
                        print("Data Sent: " + Scoreboard)
                        arduino.close
        except:
            print(sys.exc_info()[0], "occurred")
        
        
class UartService(Service):
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
        self.add_characteristic(RxCharacteristic(bus, 1, self))

class Application(dbus.service.Object):
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
        return response

class UartApplication(Application):
    def __init__(self, bus):
        Application.__init__(self, bus)
        self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        self.add_service_uuid(UART_SERVICE_UUID)
        self.add_local_name(LOCAL_NAME)
        self.include_tx_power = True

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()
    for o, props in objects.items():
        if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
            return o
        print('Skip adapter:', o)
    return None

def main():
    global mainloop
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('BLE adapter not found')
        return

    service_manager = dbus.Interface(
                                bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    app = UartApplication(bus)
    adv = UartAdvertisement(bus, 0)

    mainloop = GLib.MainLoop()

    service_manager.RegisterApplication(app.get_path(), {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)
    ad_manager.RegisterAdvertisement(adv.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)
    try:
        mainloop.run()
    except KeyboardInterrupt:
        adv.Release()

if __name__ == '__main__':
    main()

1 Like

A minimal snippet is:

import json

def write_to_json_file(path, obj):
    with open(path, 'wt') as f:
        json.dump(obj, f)

This will overwrite pre-existing files, doesn’t support ‘at’ mode (appending json to json is unlikely to produce valid json), and only uses the default encoding (probably utf-8).

Many thanks for your reply but as I said I didn’t write the original script so I don’t really understand it.
Where do I put your code in the py file?
Does it just get added, or does some of the existing code need to be removed?

The first line is best at the top, among the other imports. Otherwise feel free to copy and paste the rest of it in where you think is best (before the function is called). It defines a function. Call it whereever in your code (after it is defined) that you want to write a json-able object to a json file:

d = dict(a=1,b=2,c=3)
write_to_json('/tmp/test_abc_123.json')

Thanks James, I appreciate you taking the time again but I’m no programmer so I have no idea how to do that
Would it be possible for you to put your code in to my py file & paste it in here please.
If you add just say BatTotal, wickets & overs & make that output to the json file, I’ll then understand enough to be able to add my other variables.
The json file does need to be created in the format in my original post as that’s what the spectator.html file expects.

You’re welcome Tony. I’m tied up with work unfortunately. But maybe someone else can help, or come up with their own solution.

1 Like

It is probably worth ensuring that the file is UTF-8. JSON for use
outide tiny niche closed environments is required to be UTF-8 by
RFC8259, section 8.1: RFC 8259 - The JavaScript Object Notation (JSON) Data Interchange Format

This is easily done by updating the open() call:

 with open(path, 'wt', encodeing='utf-8') as f:

Isn’t the “t” in “wt” redundant?

1 Like

Time to learn, at least a bit. Otherwise you’ll be in the same situation
later with other problems.

Note that all the suggestions below are untested!

If it were me doing this, I’d be:

  • putting the stuff which sends the current score into its own function
  • calling that function at each place required
  • modifying the function to also write your score.json file

By putting it all in a function you get to write it just once and get
it correct, and then calling it ensures that this is done the same way
every time.

FIRST: take a copy of the current version of the script in case you
mangle something horribly. And also for reference.

THEN: modify your script.

How?

See this code:

 Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
 print(Scoreboard)
 with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
     # time.sleep(0.1) #wait for serial to open
     if arduino.isOpen():
         print("{} connected!".format(arduino.port))
         arduino.write(Scoreboard.encode())
         print("Data Sent: " + Scoreboard)
         arduino.close

which occurs at least twice in the script? Put it into a function like
this:

 def update_scoreboard():
     Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
     print(Scoreboard)
     with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
         # time.sleep(0.1) #wait for serial to open
         if arduino.isOpen():
             print("{} connected!".format(arduino.port))
             arduino.write(Scoreboard.encode())
             print("Data Sent: " + Scoreboard)
             arduino.close

Define that function up the top of the script, just above the first
occurence of the code your’re replacing. You can’t use a function before
it’s defined, so this comes ahead of the main code which will call it.

Then modify the rest of the script to remove (or, better in the short
term, comment out) the current code. So the first chunk would become:

 # with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
 #     # time.sleep(0.1) #wait for serial to open
 #     if arduino.isOpen():
 #             print("{} connected!".format(arduino.port))
 #             arduino.write(Scoreboard.encode())
 #             print("Data Sent: " + Scoreboard)
 #             arduino.close
 update_scoreboard()

and do the same in the othe part of the script which does this. Make
sure to keep the indentation of the script the same: the call to the
function (update_scoreboard()) has to be indented the same as the
with-statement you’re replacing.

Make that change and check that things still work.

Now modify the script to write the JSON file.

First import the json module, at the top of the script with the other
imports, with this line:

 import json

In the update_scoreboard function, add code like the examples James
provided, eg:

 score_data = {
     "total": BatTotal,
     "wickets": Wickets,
     "overs": Overs,
 }
 with open('score.json', 'w', encoding='utf-8') as jsonf:
     json.dump(score_data, jsonf)

Notice that this makes Python dict (dictionary) containing the various
fields you want in the JSON file (“total” and so forth). The values come
from the variables which store them, just as they did when you defined
the Scoreboard string earlier. Fill it out for all the fields you need
to supply.

You can also print it out ahead of writing the file to see if it looks
correct.

With this new code, the function would look like this:

 def update_scoreboard():
     Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
     print(Scoreboard)
     with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
         # time.sleep(0.1) #wait for serial to open
         if arduino.isOpen():
             print("{} connected!".format(arduino.port))
             arduino.write(Scoreboard.encode())
             print("Data Sent: " + Scoreboard)
             arduino.close
     score_data = {
         "total": BatTotal,
         "wickets": Wickets,
         "overs": Overs,
     }
     print("score_data =", score_data)
     with open('score.json', 'w', encoding='utf-8') as jsonf:
         json.dump(score_data, jsonf)

It is, but it’s also harmless. (You have a typo there, btw: it should be encoding with no second e.)

Thank you for suck a helpful & comprehensive reply, it is much appreciated.
I will make the modifications on my ‘test Pi’ as soon as I can & report back.

Good spot - cheers. I got mixed up with the UTF-8 default of str.encode and bytes.decode (it’s locale dependent for open)

Isn’t the “t” in “wt” redundant?

Yes, but explicit is better than implicit.

I think of JSON as a standard for text data, independent of binary implementation. No encoding is mentioned here: JSON
This one doesn’t mention UTF-8, but does mention UTF-16 as an example, to explain surrogates:
https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf

You’re quite right, of course. Personally, I just feel a JSON parser that rejects non-UTF-8 encodings is needlessly picky.

And PSA: if strict compliance with any JSON standard is required then the default settings of Python’s json library can’t be relied on:

This module does not comply with the RFC in a strict fashion, implementing some extensions that are valid JavaScript but not valid JSON. In particular:

  • Infinite and NaN number values are accepted and output;
  • Repeated names within an object are accepted, and only the value of the last name-value pair is used.

I ran into this when generating random json files for property based testing.

1 Like

Results
I added the def update_scoreboard code and then commented out the lines you said, making sure of the indentation as you said. But the Play Cricket Scorer app couldn’t see the scoreboard over BlueTooth.
This is my py file:

import serial, time
import sys
import dbus, dbus.mainloop.glib
from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME =           'org.bluez'
DBUS_OM_IFACE =                'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE =           'org.bluez.GattManager1'
GATT_CHRC_IFACE =              'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID =            '5a0d6a15-b664-4304-8530-3a0ec53e5bc1'
UART_RX_CHARACTERISTIC_UUID =  'df531f62-fc0b-40ce-81b2-32a6262ea440'
LOCAL_NAME =                   'FoSCC-Scoreboard-TGT'

mainloop = None

# Set all the Scoreboard variables to starting positions

Wickets = "0"
Overs = "-0"
BatTotal = "--0"
BatA = "--0"
BatB = "--0"
Target = "---"

def update_scoreboard():
    Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
    print(Scoreboard)
    with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
        # time.sleep(0.1) #wait for serial to open
        if arduino.isOpen():
            print("{} connected!".format(arduino.port))
            arduino.write(Scoreboard.encode())
            print("Data Sent: " + Scoreboard)
            arduino.close


Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"

# Open the serial port and set the Scoreboard to starting positions

#with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
    # time.sleep(0.1) #wait for serial to open
#    if arduino.isOpen():
#            print("{} connected!".format(arduino.port))
#            arduino.write(Scoreboard.encode())
#            print("Data Sent: " + Scoreboard)
#            arduino.close
update_scoreboard()

class RxCharacteristic(Characteristic):
    def __init__(self, bus, index, service):
        Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
                                ['write'], service)

    def WriteValue(self, value, options):
        global Wickets
        global Overs
        global BatTotal
        global BatA
        global BatB
        global Target
        global Scoreboard
        try:
            WriteReturn = '{}'.format(bytearray(value).decode())
            ScoreType = WriteReturn[0:3]
            ScoreData = WriteReturn[3:]
	    #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
	    UpdateScoreboard = True
            if ScoreType == "OVB":                                          # Let's deal with Overs Bowled!
                if ScoreData.find(".") == -1:                               # If there's no dot because it's the end of the over, just format the number and use it
                    Overs = ScoreData.rjust(2,"-")
                else:
                    Overs = ScoreData.split(".",1)[0].rjust(2,"-")          # If there's a dot, take whatever's before it.  Cheap way of rounding down without using a float
            elif ScoreType == "B1S":                                        # Batsman 1 score formatted with leading dashes for blanks
                BatA = ScoreData.rjust(3,"-")
            elif ScoreType == "B2S":                                        # Batsman 2 score formatted with leading dashes for blanks
                BatB = ScoreData.rjust(3,"-")
            elif ScoreType == "BTS":                                        # Batting Team Score
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only (split everything before the ampersand)
                BatTotal = TempSplit.split("/",1)[0].rjust(3,"-")           # Everything before the dash is the score, with leading dashes for blanks
                Wickets = TempSplit.split("/",1)[1].replace("10","-")       # Everything after the dash is the wickets, and if it's all out (10) then make it blank because we only have 1 digit
            elif ScoreType == "FTS":
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only
                Target = TempSplit.split("/",1)[0]		            # Everything before the dash is the score, with leading dashes for blanks
		Target = str(int(Target) + 1).rjust(3,"-")
            else:
                print(WriteReturn + " (not used)")
	        #lets not send an update to the scoreboard
	        UpdateScoreboard = False

	    if UpdateScoreboard:
                Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
                print(Scoreboard)
                #with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
                #    # time.sleep(0.1) #wait for serial to open
                #    if arduino.isOpen():
                #        print("{} connected!".format(arduino.port))
                #        arduino.write(Scoreboard.encode())
                #        print("Data Sent: " + Scoreboard)
                #        arduino.close
                update_scoreboard()
        except:
            print(sys.exc_info()[0], "occurred")
        
        
class UartService(Service):
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
        self.add_characteristic(RxCharacteristic(bus, 1, self))

class Application(dbus.service.Object):
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
        return response

class UartApplication(Application):
    def __init__(self, bus):
        Application.__init__(self, bus)
        self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        self.add_service_uuid(UART_SERVICE_UUID)
        self.add_local_name(LOCAL_NAME)
        self.include_tx_power = True

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()
    for o, props in objects.items():
        if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
            return o
        print('Skip adapter:', o)
    return None

def main():
    global mainloop
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('BLE adapter not found')
        return

    service_manager = dbus.Interface(
                                bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    app = UartApplication(bus)
    adv = UartAdvertisement(bus, 0)

    mainloop = GLib.MainLoop()

    service_manager.RegisterApplication(app.get_path(), {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)
    ad_manager.RegisterAdvertisement(adv.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)
    try:
        mainloop.run()
    except KeyboardInterrupt:
        adv.Release()

if __name__ == '__main__':
    main()

So back to the original py file I added the import json line & then added the score_data code to both instances of the code scoreboard = “4” code, but this was the same, the app couldn’t see the board. This is that py file

import serial, time
import sys
import dbus, dbus.mainloop.glib
import json
from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME =           'org.bluez'
DBUS_OM_IFACE =                'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE =           'org.bluez.GattManager1'
GATT_CHRC_IFACE =              'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID =            '5a0d6a15-b664-4304-8530-3a0ec53e5bc1'
UART_RX_CHARACTERISTIC_UUID =  'df531f62-fc0b-40ce-81b2-32a6262ea440'
LOCAL_NAME =                   'FoSCC-Scoreboard-TGT'

mainloop = None

# Set all the Scoreboard variables to starting positions

Wickets = "0"
Overs = "-0"
BatTotal = "--0"
BatA = "--0"
BatB = "--0"
Target = "---"
Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"

# Open the serial port and set the Scoreboard to starting positions

with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
    # time.sleep(0.1) #wait for serial to open
    if arduino.isOpen():
            print("{} connected!".format(arduino.port))
            arduino.write(Scoreboard.encode())
            print("Data Sent: " + Scoreboard)
            arduino.close
                 score_data = {
         "total": BatTotal,
         "wickets": Wickets,
         "overs": Overs,
     }
     print("score_data =", score_data)
     with open('score.json', 'w', encoding='utf-8') as jsonf:
         json.dump(score_data, jsonf)

class RxCharacteristic(Characteristic):
    def __init__(self, bus, index, service):
        Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
                                ['write'], service)

    def WriteValue(self, value, options):
        global Wickets
        global Overs
        global BatTotal
        global BatA
        global BatB
        global Target
        global Scoreboard
        try:
            WriteReturn = '{}'.format(bytearray(value).decode())
            ScoreType = WriteReturn[0:3]
            ScoreData = WriteReturn[3:]
	    #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
	    UpdateScoreboard = True
            if ScoreType == "OVB":                                          # Let's deal with Overs Bowled!
                if ScoreData.find(".") == -1:                               # If there's no dot because it's the end of the over, just format the number and use it
                    Overs = ScoreData.rjust(2,"-")
                else:
                    Overs = ScoreData.split(".",1)[0].rjust(2,"-")          # If there's a dot, take whatever's before it.  Cheap way of rounding down without using a float
            elif ScoreType == "B1S":                                        # Batsman 1 score formatted with leading dashes for blanks
                BatA = ScoreData.rjust(3,"-")
            elif ScoreType == "B2S":                                        # Batsman 2 score formatted with leading dashes for blanks
                BatB = ScoreData.rjust(3,"-")
            elif ScoreType == "BTS":                                        # Batting Team Score
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only (split everything before the ampersand)
                BatTotal = TempSplit.split("/",1)[0].rjust(3,"-")           # Everything before the dash is the score, with leading dashes for blanks
                Wickets = TempSplit.split("/",1)[1].replace("10","-")       # Everything after the dash is the wickets, and if it's all out (10) then make it blank because we only have 1 digit
            elif ScoreType == "FTS":
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only
                Target = TempSplit.split("/",1)[0]		            # Everything before the dash is the score, with leading dashes for blanks
		Target = str(int(Target) + 1).rjust(3,"-")
            else:
                print(WriteReturn + " (not used)")
	        #lets not send an update to the scoreboard
	        UpdateScoreboard = False

	    if UpdateScoreboard:
                Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
                print(Scoreboard)
                with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
                    # time.sleep(0.1) #wait for serial to open
                    if arduino.isOpen():
                        print("{} connected!".format(arduino.port))
                        arduino.write(Scoreboard.encode())
                        print("Data Sent: " + Scoreboard)
                        arduino.close
                             score_data = {
         "total": BatTotal,
         "wickets": Wickets,
         "overs": Overs,
     }
     print("score_data =", score_data)
     with open('score.json', 'w', encoding='utf-8') as jsonf:
         json.dump(score_data, jsonf)
        except:
            print(sys.exc_info()[0], "occurred")
        
        
class UartService(Service):
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
        self.add_characteristic(RxCharacteristic(bus, 1, self))

class Application(dbus.service.Object):
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
        return response

class UartApplication(Application):
    def __init__(self, bus):
        Application.__init__(self, bus)
        self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        self.add_service_uuid(UART_SERVICE_UUID)
        self.add_local_name(LOCAL_NAME)
        self.include_tx_power = True

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()
    for o, props in objects.items():
        if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
            return o
        print('Skip adapter:', o)
    return None

def main():
    global mainloop
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('BLE adapter not found')
        return

    service_manager = dbus.Interface(
                                bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    app = UartApplication(bus)
    adv = UartAdvertisement(bus, 0)

    mainloop = GLib.MainLoop()

    service_manager.RegisterApplication(app.get_path(), {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)
    ad_manager.RegisterAdvertisement(adv.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)
    try:
        mainloop.run()
    except KeyboardInterrupt:
        adv.Release()

if __name__ == '__main__':
    main()

I suspect it’s me being daft, but I can’t see what I’ve done wrong.

The new code only writes the score.json file. (Also, I do not see the code to write that file in the function in your modified script.) I do not know how the score.json file is communicated to the html page. Maybe it needs to be written into a specific place.

Does the script with the function in it continue to behave as the original script did? That is the first thing to ensure - that you have not broken existing functionality.

Then add the JSON code and check that it seems to be writing the score.json file with sensible looking contents (by looking at the file from outside, maybe in an editor).

Then find out how the score.json file is supposed to be used - I don’t know what mechanism is involved there. As you described it it sounded like the primary shortcoming was that the script didn’t write the file at all. When you’ve got that working, then it is time to find out what uses the file and where it expects to look for it.

Good spot - cheers. I got mixed up with the UTF-8 default of
str.encode and bytes.decode (it’s locale dependent for open)

My lint tools chastise me about this all the time :frowning:

Isn’t the “t” in “wt” redundant?
Yes, but explicit is better than implicit.

I think of JSON as a standard for text data, independent of binary
implementation. No encoding is mentioned here:
JSON
This one doesn’t mention UTF-8, but does mention UTF-16 as an example, to explain surrogates:
https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf

You’re quite right, of course. Personally, I just feel a JSON parser that rejects non-UTF-8 encodings is needlessly picky.

Sure. Python’s json lib reads strings, and lets the file open deal
with the encoding.

But if we’re writing a JSON file for consumption by something else we
should make the effort to ensure it’s UTF-8.

Hi Cameron. First I added the function and commented out the lines you said but when I started the app it couldn’t see the board. That’s the first py file I posted above.
The serving py file is just me trying to add the other stuff without the Runcton.
Could you please look at the first py file to see if you can find my mistake.

UPDATE - I’ve put the first segment in the code again & I can see the board on the scoring app.
Now to try adding the second part.

OK, so I added the function & commented out as you said & I CAN see the scoreboard from the app.
So I moved on to the second part, but I CAN’T see the board.
I ran the python code in Terminal & this was the result:

pi@scoreboard:~ cd /usr/local/bin/scoreboard pi@scoreboard:/usr/local/bin/scoreboard python uart-peripheral.py
4,–0,0,-0,—#
/dev/ttyACM0 connected!
Data Sent: 4,–0,0,-0,—#
(‘score_data =’, {‘total’: ‘–0’, ‘overs’: ‘-0’, ‘wickets’: ‘0’})
Traceback (most recent call last):
File “uart-peripheral.py”, line 63, in
update_scoreboard()
File “uart-peripheral.py”, line 48, in update_scoreboard
with open(‘score.json’, ‘w’, encoding=‘utf-8’) as jsonf:
TypeError: ‘encoding’ is an invalid keyword argument for this function
pi@scoreboard:/usr/local/bin/scoreboard $

Here’s my py file:

import serial, time
import sys
import dbus, dbus.mainloop.glib
import json

from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME =           'org.bluez'
DBUS_OM_IFACE =                'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE =           'org.bluez.GattManager1'
GATT_CHRC_IFACE =              'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID =            '5a0d6a15-b664-4304-8530-3a0ec53e5bc1'
UART_RX_CHARACTERISTIC_UUID =  'df531f62-fc0b-40ce-81b2-32a6262ea440'
LOCAL_NAME =                   'FoSCC-Scoreboard-TGT'

mainloop = None

# Set all the Scoreboard variables to starting positions

Wickets = "0"
Overs = "-0"
BatTotal = "--0"
BatA = "--0"
BatB = "--0"
Target = "---"

def update_scoreboard():
    Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
    print(Scoreboard)
    with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
        # time.sleep(0.1) #wait for serial to open
        if arduino.isOpen():
            print("{} connected!".format(arduino.port))
            arduino.write(Scoreboard.encode())
            print("Data Sent: " + Scoreboard)
            arduino.close
    score_data = {
        "total": BatTotal,
        "wickets": Wickets,
        "overs": Overs,
    }
    print("score_data =", score_data)
    with open('score.json', 'w', encoding='utf-8') as jsonf:
        json.dump(score_data, jsonf)


Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"

# Open the serial port and set the Scoreboard to starting positions

#with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
    # time.sleep(0.1) #wait for serial to open
#    if arduino.isOpen():
#            print("{} connected!".format(arduino.port))
#            arduino.write(Scoreboard.encode())
#            print("Data Sent: " + Scoreboard)
#            arduino.close
update_scoreboard()

class RxCharacteristic(Characteristic):
    def __init__(self, bus, index, service):
        Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
                                ['write'], service)

    def WriteValue(self, value, options):
        global Wickets
        global Overs
        global BatTotal
        global BatA
        global BatB
        global Target
        global Scoreboard
        try:
            WriteReturn = '{}'.format(bytearray(value).decode())
            ScoreType = WriteReturn[0:3]
            ScoreData = WriteReturn[3:]
	    #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
	    UpdateScoreboard = True
            if ScoreType == "OVB":                                          # Let's deal with Overs Bowled!
                if ScoreData.find(".") == -1:                               # If there's no dot because it's the end of the over, just format the number and use it
                    Overs = ScoreData.rjust(2,"-")
                else:
                    Overs = ScoreData.split(".",1)[0].rjust(2,"-")          # If there's a dot, take whatever's before it.  Cheap way of rounding down without using a float
            elif ScoreType == "B1S":                                        # Batsman 1 score formatted with leading dashes for blanks
                BatA = ScoreData.rjust(3,"-")
            elif ScoreType == "B2S":                                        # Batsman 2 score formatted with leading dashes for blanks
                BatB = ScoreData.rjust(3,"-")
            elif ScoreType == "BTS":                                        # Batting Team Score
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only (split everything before the ampersand)
                BatTotal = TempSplit.split("/",1)[0].rjust(3,"-")           # Everything before the dash is the score, with leading dashes for blanks
                Wickets = TempSplit.split("/",1)[1].replace("10","-")       # Everything after the dash is the wickets, and if it's all out (10) then make it blank because we only have 1 digit
            elif ScoreType == "FTS":
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only
                Target = TempSplit.split("/",1)[0]		            # Everything before the dash is the score, with leading dashes for blanks
		Target = str(int(Target) + 1).rjust(3,"-")
            else:
                print(WriteReturn + " (not used)")
	        #lets not send an update to the scoreboard
	        UpdateScoreboard = False

	    if UpdateScoreboard:
                Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
                print(Scoreboard)
                #with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
                #    # time.sleep(0.1) #wait for serial to open
                #    if arduino.isOpen():
                #        print("{} connected!".format(arduino.port))
                #        arduino.write(Scoreboard.encode())
                #        print("Data Sent: " + Scoreboard)
                #        arduino.close
                update_scoreboard()
        except:
            print(sys.exc_info()[0], "occurred")
        
        
class UartService(Service):
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
        self.add_characteristic(RxCharacteristic(bus, 1, self))

class Application(dbus.service.Object):
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
        return response

class UartApplication(Application):
    def __init__(self, bus):
        Application.__init__(self, bus)
        self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        self.add_service_uuid(UART_SERVICE_UUID)
        self.add_local_name(LOCAL_NAME)
        self.include_tx_power = True

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()
    for o, props in objects.items():
        if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
            return o
        print('Skip adapter:', o)
    return None

def main():
    global mainloop
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('BLE adapter not found')
        return

    service_manager = dbus.Interface(
                                bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    app = UartApplication(bus)
    adv = UartAdvertisement(bus, 0)

    mainloop = GLib.MainLoop()

    service_manager.RegisterApplication(app.get_path(), {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)
    ad_manager.RegisterAdvertisement(adv.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)
    try:
        mainloop.run()
    except KeyboardInterrupt:
        adv.Release()

if __name__ == '__main__':
    main()

We like to enclose computer output in code fences as we do for code,
makes things easier to read and preserves the characters. So:

 pi@scoreboard:~ $ cd /usr/local/bin/scoreboard
 pi@scoreboard:/usr/local/bin/scoreboard $ python uart-peripheral.py
 4,--0,0,-0,---#
 /dev/ttyACM0 connected!
 Data Sent: 4,--0,0,-0,---#
 ('score_data =', {'total': '--0', 'overs': '-0', 'wickets': '0'})
 Traceback (most recent call last):
   File "uart-peripheral.py", line 63, in <module>
     update_scoreboard()
   File "uart-peripheral.py", line 48, in update_scoreboard
     with open('score.json', 'w', encoding='utf-8') as jsonf:
 TypeError: 'encoding' is an invalid keyword argument for this function
 pi@scoreboard:/usr/local/bin/scoreboard $

Ah. You’re invoking your script like this:

 python uart-peripheral.py

and it looks like on your Pi the python command is Python 2. Try
invoking the script like this:

 python3 uart-peripheral.py

The transition from Python 2 to Python 3 was used as an opportunity to
make some significant breaking changes to the language. Among them are
two affecting your script:

  • the print statement became a function with brackets like other
    function calls
  • the open() function grew an encoding parameter to indicate how text
    should be encoded as bytes (in Python 2 text kind of is just bytes
    and an encoding parameter doesn’t make great sense)

First: see if python3 is (or can be) installed on your system. There’s
really little call for Python 2 these days.

Alternatively (less preferred): If you don’t have python3 already
and can’t install it, we’ll change your script to be Python 2. For
this, the immediate thing is to discard the encoding parameter
entirely:

 with open('score.json', 'w') as jsonf:

It happens that your JSON data are all ASCII compatible and so the
encoding doesn’t matter.

Removing the encoding has cleared that error, now it’s complaining about write permissions on the json file with python2
Running it with python3 it complains about improper indentation in line 83. So I’ve tried replacing that with no indents, & also 8 spaces.
I then get an ‘invalid syntax’ error on line 83

The json file is located in /var/www/html, do we need to define the load & save path to the json file somewhere?

CODE:

import serial, time
import sys
import dbus, dbus.mainloop.glib
import json

from gi.repository import GLib
from example_advertisement import Advertisement
from example_advertisement import register_ad_cb, register_ad_error_cb
from example_gatt_server import Service, Characteristic
from example_gatt_server import register_app_cb, register_app_error_cb

BLUEZ_SERVICE_NAME =           'org.bluez'
DBUS_OM_IFACE =                'org.freedesktop.DBus.ObjectManager'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_MANAGER_IFACE =           'org.bluez.GattManager1'
GATT_CHRC_IFACE =              'org.bluez.GattCharacteristic1'
UART_SERVICE_UUID =            '5a0d6a15-b664-4304-8530-3a0ec53e5bc1'
UART_RX_CHARACTERISTIC_UUID =  'df531f62-fc0b-40ce-81b2-32a6262ea440'
LOCAL_NAME =                   'FoSCC-Scoreboard-TGT'

mainloop = None

# Set all the Scoreboard variables to starting positions

Wickets = "0"
Overs = "-0"
BatTotal = "--0"
BatA = "--0"
BatB = "--0"
Target = "---"

def update_scoreboard():
    Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
    print(Scoreboard)
    with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
        # time.sleep(0.1) #wait for serial to open
        if arduino.isOpen():
            print("{} connected!".format(arduino.port))
            arduino.write(Scoreboard.encode())
            print("Data Sent: " + Scoreboard)
            arduino.close
    score_data = {
        "total": BatTotal,
        "wickets": Wickets,
        "overs": Overs,
    }
    print("score_data =", score_data)
    with open('score.json', 'w') as jsonf:
        json.dump(score_data, jsonf)


Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"

# Open the serial port and set the Scoreboard to starting positions

#with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
    # time.sleep(0.1) #wait for serial to open
#    if arduino.isOpen():
#            print("{} connected!".format(arduino.port))
#            arduino.write(Scoreboard.encode())
#            print("Data Sent: " + Scoreboard)
#            arduino.close
update_scoreboard()

class RxCharacteristic(Characteristic):
    def __init__(self, bus, index, service):
        Characteristic.__init__(self, bus, index, UART_RX_CHARACTERISTIC_UUID,
                                ['write'], service)

    def WriteValue(self, value, options):
        global Wickets
        global Overs
        global BatTotal
        global BatA
        global BatB
        global Target
        global Scoreboard
        try:
            WriteReturn = '{}'.format(bytearray(value).decode())
            ScoreType = WriteReturn[0:3]
            ScoreData = WriteReturn[3:]
	    #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
        UpdateScoreboard = True
            if ScoreType == "OVB":                                          # Let's deal with Overs Bowled!
                if ScoreData.find(".") == -1:                               # If there's no dot because it's the end of the over, just format the number and use it
                    Overs = ScoreData.rjust(2,"-")
                else:
                    Overs = ScoreData.split(".",1)[0].rjust(2,"-")          # If there's a dot, take whatever's before it.  Cheap way of rounding down without using a float
            elif ScoreType == "B1S":                                        # Batsman 1 score formatted with leading dashes for blanks
                BatA = ScoreData.rjust(3,"-")
            elif ScoreType == "B2S":                                        # Batsman 2 score formatted with leading dashes for blanks
                BatB = ScoreData.rjust(3,"-")
            elif ScoreType == "BTS":                                        # Batting Team Score
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only (split everything before the ampersand)
                BatTotal = TempSplit.split("/",1)[0].rjust(3,"-")           # Everything before the dash is the score, with leading dashes for blanks
                Wickets = TempSplit.split("/",1)[1].replace("10","-")       # Everything after the dash is the wickets, and if it's all out (10) then make it blank because we only have 1 digit
            elif ScoreType == "FTS":
                TempSplit = ScoreData.split(" &",1)[0]                      # Current Inning Score only
                Target = TempSplit.split("/",1)[0]		            # Everything before the dash is the score, with leading dashes for blanks
		Target = str(int(Target) + 1).rjust(3,"-")
            else:
                print(WriteReturn + " (not used)")
	        #lets not send an update to the scoreboard
	        UpdateScoreboard = False

	    if UpdateScoreboard:
                Scoreboard = "4," + BatTotal + "," + Wickets + "," + Overs + "," + Target + "#"
                print(Scoreboard)
                #with serial.Serial("/dev/ttyACM0", 57600, timeout=1) as arduino:
                #    # time.sleep(0.1) #wait for serial to open
                #    if arduino.isOpen():
                #        print("{} connected!".format(arduino.port))
                #        arduino.write(Scoreboard.encode())
                #        print("Data Sent: " + Scoreboard)
                #        arduino.close
                update_scoreboard()
        except:
            print(sys.exc_info()[0], "occurred")
        
        
class UartService(Service):
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, UART_SERVICE_UUID, True)
        self.add_characteristic(RxCharacteristic(bus, 1, self))

class Application(dbus.service.Object):
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
        return response

class UartApplication(Application):
    def __init__(self, bus):
        Application.__init__(self, bus)
        self.add_service(UartService(bus, 0))

class UartAdvertisement(Advertisement):
    def __init__(self, bus, index):
        Advertisement.__init__(self, bus, index, 'peripheral')
        self.add_service_uuid(UART_SERVICE_UUID)
        self.add_local_name(LOCAL_NAME)
        self.include_tx_power = True

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()
    for o, props in objects.items():
        if LE_ADVERTISING_MANAGER_IFACE in props and GATT_MANAGER_IFACE in props:
            return o
        print('Skip adapter:', o)
    return None

def main():
    global mainloop
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('BLE adapter not found')
        return

    service_manager = dbus.Interface(
                                bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)

    app = UartApplication(bus)
    adv = UartAdvertisement(bus, 0)

    mainloop = GLib.MainLoop()

    service_manager.RegisterApplication(app.get_path(), {},
                                        reply_handler=register_app_cb,
                                        error_handler=register_app_error_cb)
    ad_manager.RegisterAdvertisement(adv.get_path(), {},
                                     reply_handler=register_ad_cb,
                                     error_handler=register_ad_error_cb)
    try:
        mainloop.run()
    except KeyboardInterrupt:
        adv.Release()

if __name__ == '__main__':
    main()

Removing the encoding has cleared that error,

Ok, sticking with python 2 then.

now it’s complaining about write permissions on the json file with python2

It’d do that with python 3 too.

Running it with python3 it complains about improper indentation in line 83. So Iv’e tried replacing that with no indents, & also 8 spaces.
I then get an ‘invalid syntax’ error on line 83

It’s important to see these in context. Python should be citing the
offending line. Looking at the code:

             ScoreData = WriteReturn[3:]
             #if the bluetooth data is of type OVB, B1S, B2S, BTS, FTS then we need to update the scoreboard.  Other messages can be ignore.
         UpdateScoreboard = True

Line 83 is the UpdateScoreboard = True, which is pretty obviously not
indented the same as the ScoreData = WriteReturn[3:] line.

The specific number of spaces doesn’t matter so much (2, 4, 8,
whatever), but it is essential that lines in the same block are all
indented by exactly the same number of spaces. Example:

 if True:
   print(1)
   print(2)
 else:
     print(3)
     print(4)

Here, the if and else must have the same indent. And the print(1)
and print(2) must have the same indent. The print(3) and the
print(4) must have the same indent. They don’t need to be the same
indent as the first 2 prints. (But we usually do to keep things
readable.)

The json file is located in /var/www/html so I added sys.path.append (‘var/www/html’) on the line after import.sys but it still complains

Ah.

The sys.path variable has nothing to do with this. It is just the list
of places where Python looks when you run an import statement. Nothing
more.

What you have is a permissions problem.

You will find that the files in /var/www/html are owned by a different
user. You’re the user pi, and these will be something else like
www-data. Don’t change that!

All you need is an existing file /var/www/html/score.json file to
which you have write access. So you’ll need to hand make that. Untested
example:

 pi$ sudo su -
 Enter password for pi: *********
 # cd /var/www/html
 # touch score.json
 # chown pi score.json
 # chmod a+r score.json
 # exit
 pi$

In the example above, the pi$ is supposed to be your prompt as the
user pi (you have something a bit longer, but I hope you get the
point). The # is the prompt for root (the superuser). The commands
above do the following:

  • sudo su -: use the sudo command to get a root prompt, ahd shell
    running the the root user
  • as root, we cd into the directory where you want the corw.json
    file to be
  • we create the file with the touch command (technically this is for
    updating the modification time of the file, but it will create it if
    it isn’t there)
  • change the ownership of the file to the pi user (you) with the
    chown command
  • give the file public read permission using the chmod command (a+r
    means “all plus read”) so that everyone can read it; importantly this
    makes it possible for the web server to read it, which is needed for
    it to display it to users
  • exit the root shell, returning to the pi user shell

Now you should have a /var/www/html/score.json file which is owned by
you, so that you can write data into it.

Have a look at the directory after all this:

 pi$ ls -la /var/www/html

You should see the file score.json in there, owned by the pi user,
ready for your use.

You want the path to the file in your open() call. So change this:

 with open('score.json', 'w') as jsonf:
     json.dump(score_data, jsonf)

to:

 with open('/var/www/html/score.json', 'w') as jsonf:
     json.dump(score_data, jsonf)

I’m so grateful for all your help, & I’m very sorry I’m not better at this.

I’ll park the access to the json file for now until I get the py file working properly. I wonder whether changing permissions would remove the html permissions to it. Maybe I’ll make it create a separate json file & create a separate spectator board pointing at that json. TBH that might be easier than creating all the extra variables in the json output.

I’ve modified the indent to remove that error, it now moans about line 101. I’ve tried 0,4,8,12 & 16 indents, I just don’t know what it needs to line up with!

I’m happy to use whichever python , this is a standalone system (no internet) so it won’t matter from a security standpoint

pi@scoreboard:/usr/local/bin/scoreboard $ python3 uart-peripheral.py
  File "uart-peripheral.py", line 101
    else:
       ^
SyntaxError: invalid syntax
pi@scoreboard:/usr/local/bin/scoreboard $ python uart-peripheral.py
  File "uart-peripheral.py", line 101
    else:
       ^
SyntaxError: invalid syntax