I'm working with a microcontroller and I'm making a mistake when using classes (they cost me a lot).
Before explaining the problem, I put it in context: the microcontroller allows me to work with two files .py
, boot.py
which will be executed when the microcontroller starts up and the main.py
.
The thing is that I have created a class to be able to work with the communication protocol and I have saved it inside the lib
. The class is as follows:
from network import LoRa
import socket
import binascii
import time
import machine
import pycom
import config
# LoRa_config.py
class myLoRa:
def __init__(self):
# LoRa parameters
self.dev_eui = config.dev_eui
self.app_eui = config.app_eui
self.app_key = config.app_key
self.frequency = config.LORA_FREQUENCY
self.dr = config.LORA_NODE_DR
self.region = LoRa.EU868 # EUROPE
# Inicializo la conexion
def LoRa_join(self):
# initialize LoRa in LORAWAN mode.
lora = LoRa(mode=LoRa.LORAWAN, region=self.region)
# set the 3 default channels to the same frequency (must be before sending the OTAA join request)
lora.add_channel(0, frequency=self.frequency, dr_min=0, dr_max=5)
lora.add_channel(1, frequency=self.frequency, dr_min=0, dr_max=5)
lora.add_channel(2, frequency=self.frequency, dr_min=0, dr_max=5)
# join a network using OTAA
lora.join(
activation=LoRa.OTAA,
auth=(self.dev_eui, self.app_eui,self.app_key),
timeout=0,
dr=self.dr)
# wait until the module has joined the network
NumJoin = 0
while not lora.has_joined():
time.sleep(2.5)
print('Trying to join...', str(NumJoin))
if (NumJoin < 30):
NumJoin += 1
else:
machine.reset()
# remove all the non-default channels
for i in range(3, 16):
lora.remove_channel(i)
# LoRa socket
def LoRa_socket(self):
# create a LoRa socket
lora_socket = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
# set the LoRaWAN data rate
lora_socket.setsockopt(socket.SOL_LORA, socket.SO_DR, self.dr)
# make the socket non-blocking
lora_socket.setblocking(False)
def LoRa_send(self, payload, lora_socket):
print('Alcachofa')
self.lora_socket.send(payload)
if self.lora_socket.send(payload):
print('Payload a lora enviado', payload)
When starting the micro I must execute the first two functions of the class, LoRa_join
and LoRa_socket
they are the ones that create the connection for communication. For that I do:
# boot.py -- run on boot-up
import pycom
import config
import binascii
import Clock
import LoRa_config # Archivo con la clase
# Paso 1 - Apagamos el led "heardbeat" para reducir consumos innecesarios.
print('Boot - Apago led heardbeat')
pycom.heartbeat(False)
# Paso 2 - Conectamos a la red LoRa.
xarxa = LoRa_config.myLoRa()
xarxa.LoRa_join()
lora_socket = xarxa.LoRa_socket()
So far so good (although I think lora_socket = xarxa.LoRa_socket()
the output is superfluous), but now main
I want to send a packet (to test that things are going well) and that's where I fail. I do the following:
# main.py
import ustruct
# Programa principal
while True:
print('Main - HOLA')
payload = ustruct.pack('>I', 4)
xarxa.LoRa_send(payload, lora_socket)
I can access LoRa_send
because it prints "Artichoke" but it gives me the error:
AttributeError: 'myLoRa' object has no attribute 'lora_socket'
The first question would be, main.py
is it coming to xarxa
? Should I make it a global variable? In summary, how do I access LoRa_send()
from the main to be able to send the data?
EDIT FROM ABULAFIA'S ANSWER
As I have understood (I hope I have done it well) and I have tried, import boot
it is not necessary to do it because it already does it. Well, from it main
I have access to xarxa
. According to all this, I understand that the thing should be like this (which I misunderstand because it gives an error):
class myLoRa:
def __init__(self):
# Igual
# Inicializo la conexion
def LoRa_join(self):
# Igual
# LoRa socket
def LoRa_socket(self):
# igual
def LoRa_send(self, payload):
print('Alcachofa')
self.lora_socket.send(payload)
if self.lora_socket.send(payload):
print('Payload a lora enviado', payload)
In file boot.py
I remove the equalization because I understand I have access to it as it is a self.
:
# Paso 2 - Conectamos a la red LoRa.
xarxa = LoRa_config.myLoRa()
xarxa.LoRa_join()
xarxa.LoRa_socket()
And in the main.py
no matter boot
, it seems he already does it:
import ustruct
# Programa principal
while True:
print('Main - HOLA')
payload = ustruct.pack('>I', 4)
xarxa.LoRa_send(payload)
break
The problem persists:
AttributeError: 'myLoRa' object has no attribute 'lora_socket'
I think that mentally I should avoid the fact of working with boot.py
and main.py
and think as if the del boot
is simply before the loop while
. Being so, I understand that the problem is that I don't know how to work the classes and lora_socket
either I'm not getting it right or then when I use LoRa_send
it I don't call it correctly.
Thank you very much!
Theory
A module is nothing more than a python script that can be "invoked" from another script using the statement
import
. That not only runs the module, but makes all of its variables, classes, and functions accessible to the script that made theimport
.So if you have a file called
a.py
that contains this:And another script called
b.py
which contains this:42 will come out as a result. What happens is that, before the statement
import a
will be executeda.py
and in addition , all the symbols (functions, variables, classes, etc.) defined in that script will now be accessible within the namespacea
. That is what allows me to accessa.sentido
which will be worth 42. It will also allow me to accessa.funcion()
if I need it.The
import
supports other syntaxes, such as changing the name of the resulting namespace:import a as modulo_a
eg. In this case the same thing happens as before, but the namespace will be calledmodulo_a
, so you would have to putmodulo_a.sentido
42 to get it.You can also import a single symbol into the current namespace, using the
from a import sentido
eg syntax. In this case, the modulea.py
is executed completely, as in the previous cases, but its symbolsentido
is also accessible to the current module as part of the global namespace. This means that you don't need to writea.sentido
, but you can just putsentido
.Knowing all these things we are ready to solve your problem.
Solution
Do you want from
main
to runboot
and then be able to access the variablexarxa
defined in it? Based on the above, you have two options:First option
This will execute
boot.py
completely and also leave the variablexarxa
defined in it accessible from the global namespace. That is, you can now usexarxa
without further ado frommain.py
Second option
In this case you execute
boot.py
without importing any of its symbols into the global namespace. You still have them all accessible as part of the namespaceboot
, so from main you can accessboot.xarxa
and alsoboot.lora_socket
and any variables or functions you have defined inboot.py
Update
After reading the update of the question, I comment a couple of things.
All of the above, in my opinion, is still necessary because otherwise I don't know how in
main
you would have access to the variablexarxa
, since it is defined inboot.py
and sincemain.py
you are not executing it.Apart from that, you have an error that I had not seen in my first reading, and it is in the following method of the class
myLoRa
:This method defines a variable
lora_socket
, but this variable is local to the method and disappears when the method ends. I understand that what you want is for that variable to be part of the object, since later you try to access it with the syntaxself.lora_socket
.To solve it you must initialize the variable as
self.lora_socket
, instead oflora_socket
just plain. Namely:You may have similar errors in other parts of the code, as I haven't thoroughly reviewed it. The idea in any case is always the same. Every time you want to create or access an object variable, you must put it
self.
in front of it. Otherwise it would be a method local variable that exists only while the method is running.