Modifying a python script to output data to a file

To solve this problem, it’s necessary to understand the existing code and its intended structure. It cannot be solved by guessing. Also keep in mind that errors are reported where they are detected, which is not necessarily exactly where the problem is.

Could you please help me understand how I find where the problem is.

Assuming that you understand the reason why there are indents in the source code at all, and what they mean in general, there is nothing more I can possibly explain. The rest is a matter of problem solving skills, not education.

That’s the problem, I didn’t write the original code, and I have no idea why they’re there or what they mean.

Oh, I see. The tutorial isn’t about teaching you how to write code at all; it’s about how to set up some hardware and make it run the code that is given to you.

I think you had better talk to the authors about this.

I would but I can’t as the author hasn’t been around for a long time.
This is a not for profit project to build scoreboards.
All of it is in html.
Then someone who’s no longer around wrote this python integration so that the official ECB cricket scoring app could control the scoreboard.
Then the original html project was enhanced to add a spectator scoreboard which uses the html.
But the ECB app works differently so when using that the spectator board doesn’t work.
The html creates a json file which the spectator board pulls.
All I am trying to achieve is to enhance the python script to produce the json file.,but I have no idea how, hence my post, hoping someone can help me.

Ok. Post the current state of the code, and the current error message.
We need to see both. Particularly, if you change the code then the line
numbers will change (if you insert or remove lines), so we need the code
to match up the error message to the code.

Indentation is how Python expresses the control flow. If we have an
if-statement to do one set of commands (or, optionally, an alternative)
then the indentation groups the commands. Example from your script:

 if ScoreType == "OVB":
     if ScoreData.find(".") == -1:
         Overs = ScoreData.rjust(2,"-")
     else:
         Overs = ScoreData.split(".",1)[0].rjust(2,"-")
 elif ScoreType == "B1S":
     BatA = ScoreData.rjust(3,"-")

This is part of the if-statement which seems to receive a bluetooth
message and update the scoreboard connected to your /dev/ttyACM0
serial device. If the message type is "OVB" it does the entire
if-else pair underneath that test. Otherwise, if the message type is
"B1S" it does the assignment statement under that. And so on for other
message types.

Everything between the top if and the next elif is indented to show
that it is all a group to be done if the top if test is true (an
"OVB" message). Similarly, inside that group (the inner if-else) the
true/false branches are themselves indented.

Python uses indents to define what’s “inside” (indented further) or not.
Everything one level inside needs the same amount of extra indentation.

So above, the first if and the elif need the same indentation.

Inside the “true” part of the first if, the inner if and else need
the same indent as each other.

Here’s a while loop:

 print("before the loop")
 while some_test_function():
     print(1)
     do_something()
     print(2)
 print("after the loop")

The first print(), the while and the last print() all need the
same indentation.

The things to happen inside the loop (a print(), another function
call, and another print()) need need the same indentation, which needs
to be more than that of the while because they’re inside the
while.

See that there’s no “end of the loop body” marker? Python knows that the
final print() is not part of the loop body because it is back at the
same indent as the while statement itself.

If there are changes of indent but no associated control structure like
an if or while to cause the new indent, that’s an indentation error.
Example:

 while some_test_function():
     print(1)
     do_something()
       print(2)

The last print(2) above is incorrectly indented.

Syntax errors are more to do with incorrect or incomplete structure.
Here’s an if-statement:

 if ScoreData.find(".") == -1:
     Overs = ScoreData.rjust(2,"-")
 else:
     Overs = ScoreData.split(".",1)[0].rjust(2,"-")

The else: is syntacticly correct because it is parrt of the preceeding
if. But this:

 else:
     Overs = ScoreData.split(".",1)[0].rjust(2,"-")

on its own would cause a SyntaxError because there’s no preceeding
if.

Thank you for such a comprehensive reply, a lot for me to digest there.

I built the scoreboard cabinet, soldered all the chips & did all the wiring, and enhanced the html interfaces to include lots of extra elements. The final link to make it as I want is this Scorer App integration for the spectator board. I’m sorry that I’m so poor at this aspect & I truly appreciate your efforts to help & educate me.

Here are the current error messages:

pi@scoreboard:~ $ cd /usr/local/bin/scoreboard
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

And here’s the current 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') 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()

The error message gives you a line number, which tells you where in the file that part of the code is.

Using your text editor, can you see where line 101 is?

I can see the line that it’s complaining about, but I have no idea what to do to fix it

The lines around line 101 are these:

 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

Line 101 is the else: line. Looking at the lines above it, what do you
think might be the problem?

Remember the while-loop example in my previous post. Python does not end
identify the end of the loop body with a special marker, it sees a line
which is not indented into the loop body.

In the code above, the second Target = line is not indented to be
inside the elif: body. So the if-statement ends there. Because the
if-statement has ended, the else: on the following line makes no sense
because there’s no matching if.

You want that Target = to be indented the same as the line above it.

1 Like

Thank You. I get it that indents are kind of like brackets in Excel.

Now it moans about this:

pi@scoreboard:~ $ cd /usr/local/bin/scoreboard
pi@scoreboard:/usr/local/bin/scoreboard $ python uart-peripheral.py
  File "uart-peripheral.py", line 106
    if UpdateScoreboard:
     ^
SyntaxError: invalid syntax
pi@scoreboard:/usr/local/bin/scoreboard $ python3 uart-peripheral.py
  File "uart-peripheral.py", line 106
    if UpdateScoreboard:
     ^
SyntaxError: invalid syntax

Current 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') 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()

I wonder whether it might be time to give up, I’m taking up too much of your time.
I would still like to achieve this but you must be getting very frustrated with me for which I can only apologise

Thank You. I get it that indents are kind of like brackets in Excel.

Yes.

Now it moans about this:

pi@scoreboard:~ $ cd /usr/local/bin/scoreboard
pi@scoreboard:/usr/local/bin/scoreboard $ python uart-peripheral.py
 File "uart-peripheral.py", line 106
   if UpdateScoreboard:
    ^
SyntaxError: invalid syntax

This is very similar. See the indent of the if UpdateScoreboard:? It
is not indented the same as the rest of the code in the
try:except. Instead it is indented like the except below it.

The rule’s the same: everything inside a block must have the same
indent. So they try: is indented a certain amount. Everything inside
it should be indented more, and the same amount, until the except
which ends the try clause.

Each level of control statement such a tryexcept,
ifelifelse, while and for surrounds some code. The
surrounded code must be indented, and all to the same indent.

The reason Python’s so picky about the exact indent is that a change of
indent means you’ve entered some control statement (while etc) if the
indent gets bigger or left the statement if the indent gets smaller.
If the new indent does not match a previous indent it is taken as a sign
that something’s wrong.

Progress, both in the python & my understanding (a bit!)

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', 'target': '---', 'wickets': '0'})
Traceback (most recent call last):
  File "uart-peripheral.py", line 64, in <module>
    update_scoreboard()
  File "uart-peripheral.py", line 49, in update_scoreboard
    with open('score.json', 'w') as jsonf:
IOError: [Errno 13] Permission denied: 'score.json'
pi@scoreboard:/usr/local/bin/scoreboard $ python3 uart-peripheral.py
4,--0,0,-0,---#
/dev/ttyACM0 connected!
Data Sent: 4,--0,0,-0,---#
score_data = {'total': '--0', 'wickets': '0', 'overs': '-0', 'target': '---'}
Traceback (most recent call last):
  File "uart-peripheral.py", line 64, in <module>
    update_scoreboard()
  File "uart-peripheral.py", line 49, in update_scoreboard
    with open('score.json', 'w') as jsonf:
PermissionError: [Errno 13] Permission denied: 'score.json'

So…
If I follow your instructions above about the permissions to the json file, will that mess up the permissions that the web page has to that file? Maybe I could call this output scorePCS.json & just create a separate spectator board that looks at that file, assuming the permissions relate to the file & not the folder.

Secondly, the json output from the python has an extra space in it just after the colon. Is there any way to remove it?
HTML: {“total”:“–0”,“wickets”:“0”,“overs”:“-0”, etc
Python {‘total’: ‘–0’, ‘overs’: ‘-0’, etc

There’s no real need to try both python and python3. This is
permissions which are entire to do with the file an the OS. So:

 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', 'target': '---', 'wickets': '0'})
 Traceback (most recent call last):
   File "uart-peripheral.py", line 64, in <module>
     update_scoreboard()
   File "uart-peripheral.py", line 49, in update_scoreboard
     with open('score.json', 'w') as jsonf:
 IOError: [Errno 13] Permission denied: 'score.json'

You’re standing in the /usr/local/bin/scoreboard directory, so writing
to 'score.json' tries to make a file in that directory. Can you show
up the output of the command ls -la please?

I would infer that either you lack write permission to that directory,
or the score.json file exists and you don’t have write permission to
it.

For the exercise we could make the file here. In fact, maybe that would
be a good thing, on reflection.

So…
If I follow your instructions above about the permissions to the json file, will that mess up the permissions that the web page has to that file?

Not, it should be ok. That was the purpose of the chmod a+r command:
to give all users read access to the file.

The objective is that you (the pi user) should have write access to
the file so that you can update it, and that the user running the web
server has read access to the file so that it can serve it to a web
user. So the ritual above (run as root, who can do anything) makes the
file, gives ownership to you, and public read access (since I assume the
scores are not secret).

Maybe I could call this output scorePCS.json & just create a separate
spectator board that looks at that file, assuming the permissions
relate to the file & not the folder.

In principle it could be either. The ls -la command requested above
should tell us.

So I think we should:

  • do an ls -la to see what the state of the /usr/local/bin/scoreboard directory is
  • fix up the score.json file there so that the script can write to it
  • we’ll just put a symlink over in /var/www/html which references the original in /usr/local/bin/scoreboard

Secondly, the json output from the python has an extra space in it just after the colon. Is there any way to remove it?
HTML: {“total”:“–0”,“wickets”:“0”,“overs”:“-0”, etc
Python {‘total’: ‘–0’, ‘overs’: ‘-0’, etc

You don’t need to. JSON allows white space (spaces etc) between the
values and punctuation. It’s common to see this stuff spread out over
several lines in a JSON file, a lot like how we defined data in the
Python script.

However, if you want to have no spaces that’s perfectly fine and doable.
Just change the line containing json.dump(...) to be like this:

 json.dump(score_data, jsonf, separators=(',', ':'))

which will write “,” instead of ", " and “:” instead of ": ".
Adjust to your liking.

The web page stuff should not care either way - they’ll be using some
standard JSON parsing library to load this stuff up, and it should cope
regardless.

The docs for the json.dump function are here:

if you want the gnarly details.

If you want the even more gnarly details of the JSON spec itself, that
is here: RFC 7159 - The JavaScript Object Notation (JSON) Data Interchange Format

Indeed, normally one would need to be root to write anywhere within /usr/local/bin, yes? Probably anywhere in the entire /usr hierarchy, even, I think.

Well, in these modern times? Not necessarily.

Anyway, the scoreboard script is clearly in a subdirectory of
/usr/local/bin and the pi user presumably has write permission to
it. We’ll know soon from the ls output.

I expect /usr to have things owned by by root on the whole, but
places like /usr/local can often contain things writable by other
users. For example, perhaps people on a hypothetical sysadmins group
can put things in /usr/local/bin etc.

Kits like homebrew often expect to install into a tree in /usr/local
running as the user. Me, I go the whole hog, wrapping the
brew command in this script which has my homebrew install in
~/var/homebrew:

Anyway, the OP’s script is, however arranged, in /usr/local.

1 Like

SUCCESS
I now have a separate file (scoreboardPCS.json) on the html folder that gets updated by the Play Cricket Scorer app.
I will have a play adding a couple of extra variables in to the output string (target, runs req etc).
I’m happy to create a separate spectator scoreboard html page.
The reason for keeping it separate is that there are variables sent by the html that aren’t available in the PCS app, for example batanumber
But I should be able to take it from here, thank you so much for all your help. I’ll post if I get stuck but I’ll try first!

Glad to hear it. Good luck.