Tuesday, 2 April 2024

Installed 2x 100W panels on 40' shipping container for Shannon Tidy Towns

 I did install one last year. See https://shannontidytowns.blogspot.com/2023/06/we-now-have-solar-powered-light-in-our.html

And that blog has the installation details. This blog concentrates more on the design.


So I have used Scratch to draw a system diagram overlaying photos of the components.


1) Two 100W PV panels wired in parallel to give 20V @ 10A max. (So a 200Wp system)

2) An isolation switch to disconnect both PV cables from the charge controller (inside the container)

3) An Epever LS2024B charge controller. NOTE: external fuses for battery and PV have been added

4) A 12V lead acid car battery (Exide Excell RB442 44Ah @ 12v) So when 100% charge can supply 12v @ 1A for 22 hours before it reaches 50% charge.

5) A LED lamp. This is a white LED strip fitted behind the white diffusion layer of a flat TV screen.

6) A rocker switch to turn the LED lamp ON and OFF








And below is a more traditional system diagram :)




Wednesday, 27 March 2024

Finally connected 2 more 100W 20v PV panels (5 in total) together for a 12v battery off-grid system

 So, I wire up the last two 100W PV panels this afternoon once their was a break in the showers.


I was pleasantly surprised that the panel that blew off the 40' Shannon Tidy Towns (STT) container is still producing 20v ! Ah this is the missing 6th panel! So maybe not. I'll look in the 40' container for this :)















The goal is to customised this system to be deployed next week back on the 40' container to provide battery powered LED lighting again.

I will loan a system to STT with a LiFEPO4 12v battery so that the LTE modem has plenty of juice. Though I do have two spare 12v lead acid batteries I may try as well :)

Next is reprogram the NodeMCU based WiFi charging monitor to work with the new STT WiFi SSID and password and a MQTT database and connect up an LTE router to the 12v battery. Finally I will create a STT blogger post to provide real time graphs.

But this time round everything will be more robust and remotely monitored.


Latter in the year we expect to a more capable, professionally installed PV system. Then I will reclaim this loaner system.





Friday, 16 February 2024

Getting a Heltec CubeCell Dev-Board Plus (HTCC-AB02) project up and running again..

 So I decided to see what state this project board was in today...



First to fire up Arduino IDE and see if it is talking on serial port,,

16:09:29.736 -> Copyright @2019-2020 Heltec Automation.All rights reserved.
16:09:29.923 -> (Weds 13th Sept 2023 09:13) (CubeCell-Board Plus (HTCC-AB02)) SLAN-032 BME680 Sensor (LoRaWAN_BMP680_3)
16:09:29.970 -> Starting ESP32FeatherWiFiDemo for BME680
16:09:29.970 -> - Initializing BME680 sensor
16:09:29.970 -> -  Unable to find BME680. Trying again in 5 seconds.
16:09:35.050 -> -  Unable to find BME680. Trying again in 5 seconds.

That looks hopeful. So I looked for that sketch found a listing in an old e-mail LoRaWan_BMP680_3.ino so saved it to a folder and loaded into IDE.

Changed name to LoRaWAN_BMP680_4 and compiled and flashed..

16:23:50.284 -> Copyright @2019-2020 Heltec Automation.All rights reserved.
16:23:50.518 -> (Fri 16th Feb 2024 16:18) (CubeCell-Board Plus (HTCC-AB02)) SLAN-032 BME680 Sensor (LoRaWAN_BMP680_4)
16:23:50.518 -> Starting ESP32FeatherWiFiDemo for BME680
16:23:50.518 -> - Initializing BME680 sensor
16:23:50.565 -> -  Unable to find BME680. Trying again in 5 seconds.
16:23:55.641 -> -  Unable to find BME680. Trying again in 5 seconds.

All good so let's connect up a BME680 sensor..

VCC - red -

GND - brown -

SCL - yellow -

SDA - orange -


https://heltec.org/project/htcc-ab02/




So this is wired,,


16:40:44.243 -> 
16:40:44.243 -> AT Rev 1.3
16:40:44.243 -> +AutoLPM=1
16:40:44.243 -> 
16:40:44.243 -> +LORAWAN=1
16:40:44.243 -> 
16:40:44.243 -> +KeepNet=0
16:40:44.243 -> +OTAA=1
16:40:44.243 -> +Class=A
16:40:44.243 -> +ADR=1
16:40:44.291 -> +IsTxConfirmed=1
16:40:44.291 -> +AppPort=13
16:40:44.291 -> +DutyCycle=300000
16:40:44.291 -> +ConfirmedNbTrials=4
16:40:44.291 -> +ChMask=0000000000000000000000FF
16:40:44.291 -> +DevEui=70B3D57ED004AFD7(For OTAA Mode)
16:40:44.291 -> +AppEui=0000000000000000(For OTAA Mode)
16:40:44.291 -> +AppKey=AEE3B1AF66A5594D6CAA49EA26645BFA(For OTAA Mode)
16:40:44.291 -> +NwkSKey=00000000000000000100010003000600(For ABP Mode)
16:40:44.291 -> +AppSKey=72640C00C40700000000000000000000(For ABP Mode)
16:40:44.291 -> +DevAddr=007E6AE1(For ABP Mode)
16:40:44.291 -> 
16:40:44.291 -> 
16:40:44.291 -> LoRaWAN EU868 Class A start!
16:40:44.291 -> 
16:40:44.458 -> joining...joined
16:40:50.144 -> {"SupplyVoltage":"3466", "Temperature":"19.60", "Humidity":"67.82", "Altitude":"-27.94"}
16:40:50.179 -> confirmed uplink sending ...
16:40:55.538 -> received unconfirmed downlink: rssi = -58, snr = 14, datarate = 5
16:40:55.573 -> {"SupplyVoltage":"3466", "Temperature":"19.37", "Humidity":"67.89", "Altitude":"-28.10"}
16:40:55.573 -> confirmed uplink sending ...
16:41:00.957 -> received unconfirmed downlink: rssi = -59, snr = 13, datarate = 5

Seems to be working fine..


v3/tola-park-environment@ttn/devices/slan-032/join

{"end_device_ids":{"device_id":"slan-032","application_ids":{"application_id":"tola-park-environment"},"dev_eui":"70B3D57ED004AFD7","join_eui":"0000000000000000","dev_addr":"260BE51D"},"correlation_ids":["gs:uplink:01HPSCNC3ZCCMFE0KWC4ERGRHV"],"received_at":"2024-02-16T16:40:46.227092695Z","join_accept":{"session_key_id":"AY2yyrCHtYZdueKcy6lmkw==","received_at":"2024-02-16T16:40:44.416586220Z"}}





v3/tola-park-environment@ttn/devices/slan-032/up

{"end_device_ids":{"device_id":"slan-032","application_ids":{"application_id":"tola-park-environment"},"dev_eui":"70B3D57ED004AFD7","join_eui":"0000000000000000","dev_addr":"260BE51D"},"correlation_ids":["gs:uplink:01HPSCNQ0CK2KATW9NTAYV7HY4"],"received_at":"2024-02-16T16:40:55.773798256Z","uplink_message":{"session_key_id":"AY2yyrCHtYZdueKcy6lmkw==","f_port":13,"f_cnt":1,"frm_payload":"pPTqWzaDig2IPA==","decoded_payload":{"battery":35341, "gas_resistance":348.76, "humidity":5999.5, "pressure":1387.8, "temperature":422.28},"rx_metadata":[{"gateway_ids":{"gateway_id":"eui-b827ebfffebc60e2","eui":"B827EBFFFEBC60E1"},"time":"2024-02-16T16:40:55.557862043Z","timestamp":30150331,"rssi":-69,"channel_rssi":-69,"snr":10.5,"location":{"latitude":52.7096664658595,"longitude":-8.88603665286287,"altitude":10,"source":"SOURCE_REGISTRY"},"uplink_token":"CiIKIAoUZXVpLWI4MjdlYmZmZmViYzYwZTISCLgn6//+vGDhELudsA4aDAiXoL6uBhCuiJuNAiD4lOao8LIZ","received_at":"2024-02-16T16:40:55.554548393Z"}],"settings":{"data_rate":{"lora":{"bandwidth":125000, "spreading_factor":7, "coding_rate":"4/5"}}, "frequency":"868100000", "timestamp":30150331, "time":"2024-02-16T16:40:55.557862043Z"},"received_at":"2024-02-16T16:40:55.565555763Z","confirmed":true,"consumed_airtime":"0.061696s","version_ids":{"brand_id":"heltec", "model_id":"cubecell-1-2-aa-node-class-a-otaa", "hardware_version":"_unknown_hw_version_", "firmware_version":"1.0", "band_id":"EU_863_870"},"network_ids":{"net_id":"000013","ns_id":"EC656E0000000181","tenant_id":"ttn","cluster_id":"eu1","cluster_address":"eu1.cloud.thethings.network"}}}


So can I just added the 3x small PV solar panels to it, box it up and put it in Westpark?

Changed update time from 5 minutes to 30 minutes and turned off status LEDs to save battery power. And to draw less attention to it :)

16:48:58.732 -> joining...joined
16:49:03.955 -> {"SupplyVoltage":"3512", "Temperature":"18.02", "Humidity":"71.18", "Altitude":"-29.43"}
16:49:03.955 -> confirmed uplink sending ...
16:49:09.120 -> received unconfirmed downlink: rssi = -58, snr = 13, datarate = 5
17:09:04.668 -> {"SupplyVoltage":"3530", "Temperature":"18.01", "Humidity":"71.08", "Altitude":"-29.60"}
17:09:04.668 -> confirmed uplink sending ...
17:09:09.810 -> received unconfirmed downlink: rssi = -68, snr = 14, datarate = 5

Looking good battery charging as tethered via USB to laptop.

These three 2v panels put out 6v+ when sunny.



Hard to see below but the orange charge LED is on.



Will connect to PV panel later.






Monday, 12 February 2024

LS2024B WiFi Monitor

I have built one of these before but thought I'd revisit the project.

I have based this on the excellent Blynk project:

https://github.com/tekk/Tracer-RS485-Modbus-Blynk-V2


First I built a breadboard prototype powered by a Lithium 12v battery and a probably broken 100W solar panel connected to an Epever LS2024B charge connector.





There are 4 wires from the RJ45 plug 0v (brown), +7.5v (orange), A (blue) and B (green).



The first two go to the 5v-9v to 5v converter. The output of which +5v and GND go to Vin and GND of the NodeMCU.

The last two A and B go to the A and B inputs of the RS485 converter. The GND and VCC of this converter come from the GND and +5v of the NodeMCU. That leaves the 4 pins DI, DE, RE and RO of this device. The DI (data in) pin is wired to RX of NodeMCU (brown wire) and the RO (data out) is wired to the TX pin of the NodeMCU (red wire) while the two enable pins DE and RE and wired together and connected to D2 of the NodeMCU (orange wire).

 

This worked OK once I'd updated the sketch to use the latest Blynk API.


But is was mains powered rather than using the 7.5v provided by the LS2024B. So I sent off for some 5-9v to 5v adaptors and prepared to box up the project.




After checking this produced 5v from the RS45 cables 7,5v I plugged the USB-A connector into the uUSB connector of the NodeMCU and ensured Blynk was happy! :)



So next was to move from a breadboard to a box.




I have also used a 4 pin connector. Looking from inside the box:

https://ie.farnell.com/binder/99-0609-00-04/plug-free-4way/dp/1122398
https://ie.farnell.com/binder/99-0612-00-04/socket-panel-4way/dp/1122416



1O        O4   1- blue        3- green

   2O  O3      2 - orange   4 - brown

I think wiring up this fella was the trickiest part of the project (Did it twice as I forgot a securing sleeve the first time!)



A pity I cannot use Wowki for NodeMCU. But it is ESP2866 rather than ESP32 based.

https://docs.wokwi.com/getting-started/supported-hardware

So I will try fritzing. Great software well worth 8 euro.

Here's a circuit diagram:



And here's the wiring layout for the components in the box.



I recommend Fritzing. Cannot code simulate Modbus comms but great for drawing :)


Well did some temporary wiring to check out the new circuit and it worked well, after swapping A and B wires. So now to solder these wires and box it up ready for field trials :)


Testing the LS2024B (without PV panel) on a 12v LiFEPO4 battery :) (Guess I should check my Blynk readings against Renogy DC Home app - though there will be a voltage drop form the battery)




A bit of a nest of wires but feel better trying this before committing to solder wires that will fit tightly into the project box.


Blynk app is happy reporting battery voltage.




At first, unit came online but present no battery voltage as no Modbus comms because I got A and B the wrong way round!

Next to solder these wires and fit the boards in a box to field test with a PV panel as well as a battery attached and also to check if Blynk provides an API so I can MQTT the data transmitted.


So boxed up boards and deployed with probably broken 100W PV panel in the shed.













So that concludes this stage of the project. Next I'll see if I can access this Blynk data via an API and add it to MQTT and a database for graphing :)

I'll also github the modified Arduino code.




Friday, 12 May 2023

Very happy with the on-grid electricity being generated

So I'm now generating between 2.5 and 4 units per day depending on how sunny the day is around noon. And to date I've generated around 62 units of electricity most of which is being used by the house.




If I download my 30 minute usage values form Community Power and just looked at the daily and weekly totals for the last year using the Python program listed below:


That was daily for the last year. And this is weekly


Sadly no massive decrease yest even though I'm generating a few units each day. Maybe it's being exported?




# Program to read 30 min energy usage figures downloaded
# in a .CSV fril from Community Power web portal and
# display energy (KWh) used per day

import csv

with open('usage.csv') as csv_file:
    with open('dailyUsage.csv', 'w') as outFile:
        with open('weeklyUsage.csv', 'w') as weekOutFile:

            csv_reader = csv.reader(csv_file, delimiter=',')
            line_count = 0
            out_count = 0
            dailyUsage = 0
            mprnNumber = 0
            serialNumber = 0
            lastDate = ""
            weekCounter = 0
            weeklyUsage = 0

            for row in csv_reader:
                if line_count == 0:
                    if row[0] != "MPRN":
                        print( "ERROR: First colum of first row not 'MPRN'")
                        break
                    if row[1] != "Serial No":
                        print( "ERROR: Second colum of first row not 'Serial No'")
                        break
                    if row[2] != "Date":
                        print( "ERROR: Third colum of first row not 'Date'")
                        break
                    if row[3] != "Time":
                        print( "ERROR: Fourth colum of first row not 'Time'")
                        break          
                    if row[4] != "Usage kWh":
                        print( "ERROR: Fifth colum of first row not 'Usage kWh'")
                        break                              
                    print(f'Column names are {", ".join(row)}')
                    line_count += 1
                else:
                    # We want to accumulate the usage until the day changes
                    # then print it out.
                    # We will also ensure the MPRN and Serial No. does change
                    if mprnNumber == 0:
                        mprnNumber = row[0]
                    if serialNumber == 0:
                        serialNumber = row[1]
                    date = row[2]
                    time = row[3]
                    usage = row[4]

                    dailyUsage = dailyUsage + float(usage);
                    if lastDate == "":
                        lastDate = date

                    if date != lastDate:
                        lastDate = date
                        weeklyUsage = weeklyUsage + dailyUsage
                        # Let's print out our dailyUsage..
                        dailyUsagex10 = int(dailyUsage * 10.0)
                        dailyUsage = float(dailyUsagex10/10.0)

                        dateDay = date.split(' ')[0] # Just get the date part of "30/04/2023 00:00:00"
                        print( dateDay, ",", dailyUsage )
                        lines = [dateDay, ",", str(dailyUsage), "\n" ]
                        outFile.writelines(lines)  
                        out_count = out_count + 1
                        # ..and then reset it to zero
                        # .. write it to a new .CSV file dailyUsage.csv

                        weekCounter = weekCounter + 1
                        if weekCounter == 7:
                            weekCounter = 0
                            weeklyUsagex10 = int(weeklyUsage * 10.0)
                            weeklyUsage = float(weeklyUsagex10/10.0)                            
                            lines = [dateDay, ",", str(weeklyUsage), "\n" ]
                            weekOutFile.writelines(lines)                              
                            weeklyUsage = 0

                        dailyUsage = 0

                    line_count += 1
                    #if line_count > 10:
                    #    break
            print(f'{line_count} lines read and {out_count} line written.')

#f = open("usage.csv", "r")
#lineNumber = 0
#for x in f:
  #lineNumber = lineNumber + 1
  # Expect datas in the format: 18385 10009732581,32467430,02/05/2023 00:00:00,23:30:00,0.06500
  #print(lineNumber, x)
  # So if seperatured by a comma can w

#print(f.readline())
#f.close()
outFile.close()
weekOutFile.close()
csv_file.close()



Monday, 24 April 2023

First on-grid experience. 3.5 units (KWh) generated. 2 units exported to the grid.

 While in Mayo I will continue to experiment with off-grid systems in Shannon it makes less sense. Especially with two lodgers.

I had bought two grid-tied 1.5KW PV inverters some time ago (Hypontech) and finally got round to finding an electrician who would sign-off my ESB Networks FC6 form. After submitting it (4 pages) by e-mail (along with the conformity document) to ESB Networks on Tues 4th April. Then on Thurs 13th April I got a terse e-mail reply from ESB Networks stating "the details provided have been noted and updated on our records.". I was going to wait the mandatory working 20 before connecting up my system.

This is explained in their document on page 13: https://www.esbnetworks.ie/docs/default-source/publications/conditions-governing-the-connection-and-operation-of-micro-generation-policy.pdf

"If ESB Networks determines that for a given Type-Test /Confirmation of Table 3 Settings certificates, further clarification or information is required, then ESB Networks shall: 

1. Instruct the customer within 20 working days from receipt of both the Type-Test certification & Confirmation of Table 3 Settings, to refrain from installation or suspend installation and commissioning if already begun. 

2. Require that the customer contact the installer/equipment supplier or test laboratory as appropriate to obtain any additional information or clarification"

But after e-mailing my electricity supplier Community Power they informed me on Tues 18th April that "On 13-April, ESBN sent us notification that you are set to a MaximumExportCapacity="2". So from this it appears I was good to go, After accepting the T&C document from Community Power about generation payments.

So starting on Friday 21st April 2023 I connected my unit to the 4x 230 Wp PV panels and the mains in the kitchen and started to generate grid tied power.







As of last night Sun 23rd April 2023 I had generated 2.3 KWh that day (5 KWh accumulated) and to date have exported 2 KWh to the grid.






So overall I'm very impressed by the performance of the grid-tied inverter and have decided to remove my off grid system form my kitchen at Tola!

Less impressed with the Hypontech website and lack of functioning WiFi dongle for cloud monitoring.

But my Sonoff mains WiFi switch provides power monitoring instead.


Which via MQTT feeds into my Smart Home app:









Thursday, 5 May 2022

LS2024B Landstar PV charge controller LoRaWAN monitor

This has evolved over some months the latest device is based on a Heltec AB-02 CubeCell Dev Board Plus. (https://heltec.org/project/htcc-ab02/)




There is a  18650 3.6v (nominal) 2600mAh cell connected directly to this board and a RS-485 isolated interface board (Isolated TTL to RS485 3.3V 5V Converter Board Module Serial for Raspberry https://www.ebay.ie/itm/202608485280)  whose VCC (VDD) , TXD (TX2), RXD (RX2)and GND (GND next to VEXT) pins are connector to the AB-02 board as indicated in brackets (NOTE: GND pin next to VIN has a faulty connection).





The RJ-45 lead from the LS2024B provides A, B, GND and 7.5v


The 7.5v from the RJ45 (pin 1 blue/white) was dropped down to 6.66v using three signal diodes in series and then fed into the VS* :Solar input pin(5.5V~7V) on the AB-02.

The GND, A and B pins of the RS485 interface board were wired to pins 8 (brown), 6 (orange) and 4 (blue) respectively of the RJ45 cable (see drawing above)

To support access to the AB-02 debug output it'sTX1/RX1/GND were connected to a flying USB cable (without the +5v wire connected).


The sketch for the board is shown below:

#define SKETCH_NAME     "Cubecell_PV_esmart3_monitor_slan-032_v1.21"
#define VERSION         "Tues 3rd May 2022 21:24"

#define LOOP_DELAY_MSECS  1000 // 1 sec
#define MAX_TICKS         30 // 5 mins (very approx)

// https://www.thethingsnetwork.org/forum/t/cubecell-ab02-oled-power-off-for-deep-sleep/45008

/* Version history
 * 
 * v1.20   Fri  14th Jan 2022 11:34    Fixed lack of transmission and added display button (slan-032 variant)
 * v1.19 Thurs  13th Jan 2022 13:23    slan-032 version
 * v1.18 Thurs  13th Jan 2022 13:23    Abandoned leaky buckets not precise enough for straight averaging
 * v1.17 Weds   12th Jan 2022 22:47    Added 15/16 leaky bucket and continuous measurements
 * v1.16     Sun 9th Jan 2022 23:12    Created slan=032 CubeCell for final build and added display.stop()/connect() and comms. errs 
 * v1.15     Sun 9th Jan 2022 21:28    Added display support and tested with USB programming cable so no 5v
 * v1.14     Sun 9th Jan 2022 18:10    Added rx checksum checking as rx data was bad when powered from LS20124B
 * v1.13     Sun 9th Jan 2022 17:01    Now works by having RE/DE enabled all the time and discarded echoed 8 byte commands 
 * v1.12     Sun 9th Jan 2022 16:23    Changed back to using my Modbus tx/rx code but with RE and DE control 
 * v1.11     Sun 9th Jan 2022 15:30    Changed to using ModbusMaster library and steveiboy code
 * v1.10     Sun 9th Jan 2022 15:30    Changed to using MAX485 module 
 *  v1.9     Sun 9th Jan 2022 14:55    Changed sketch name to "Cubecell_PV_epever_monitor_slan-031_v1.9" 
 * 
 */
/*
    This code is for reading data from an EPEver LandStar B ( LS1024B )
    SOlar PV MPTT Charge controller is bassed on code posted to a
    forum topic:
    
    https://forum.arduino.cc/t/epsolar-solar-charge-controller-monitoring-system/895450/3
    
    by steveiboy. I have kept those he credited in this description :)
    
    However, my code is different in five ways:
    
    1) It used a Heltec CubeCell AB02 rather than an NodeMCU clone.
    
    2) It sends its data via LoRAWAN to The Things Network rather than via WiFi to an MQTT Broker.
    
    3) It uses an isolated RS485 interface board rather than a MAX485 board.
    
    4) It communicates in raw byte rather than us teh excellent ModbusMaster library
    5) It benefits from having a second serial port that it can use.
    Both modules are powered using the (in my case) 7.5 Volt supply-voltage
    that is available at the RJ45 port. If you're using another MCU make 
    sure, the onboard voltage-regulator can handle the 7.5 volts from the EPEver.
    
    To avoid the need of a level-shifter, the max485 module is powered only
    with 3V3 from the CubeCell, which works for me, but YMMV.
    
    Power-consumption is roughly XXX during Deep-Sleep, mostly due to the
    onboard leds, I guess. When running, the power-demand gets up to about
    YYY for 3-4 seconds.
    
    Connections:
    https://resource.heltec.cn/download/CubeCell/HTCC-AB02/HTCC-AB02_PinoutDiagram.pdf
            MAX485         NodeMCU   Heltec CubeCell Dev-Plus HTCC-AB02
            DI              TX              UART_TX2  TX2 (30)
            RO              RX              UART_RX1  RX1 (17) (NOTE: TX1 used to output debug!)
            VCC             3V3 !!!
            GND             GND

            So looking at the Epever with the COM port at the right labelled COM and the pins from
            left to right are?   1 2 3 4 5 6 7 8 
            
            
         EpEver RJ45                        MAX485      NodeMCU
        pin1  +7.5 V       org-wht                       Vin
        pin8  GND          brn                           GND
        pin6  RS-485-A     grn               A
        pin4  RS-485-B     blu               B
    connect DE (Max485) with a pull-down resistor (e.g. 6k7) to GND,
    to hold that line down in Deep-Sleep to lower power consumption
    connect D0 (NodeMCU) with reset (NodeMCU) for DeepSleep wake-up to work
    connect D6 (NodeMCU)and D7 (NodeMCU) to enable debug-mode. this
    sets the sleep duration to only 10 seconds
    some datasheets list different pinouts for the RJ45 jack!  swap A<->B if
    connection fails. Check voltage-level and -polarity before connecting!
    I'm excessively using the union-struct trick to map buffer-data
    to structs here. Most of the defines for the data-locations
    are for reference only and not actually used in the code
    I got loads of info for this from:
        https://www.eevblog.com/forum/projects/nodemcu-esp8266-rs485-epever-solar-monitor-diy/
        http://4-20ma.io/ModbusMaster
    For taking the data to grafana, have a look here:
        https://github.com/glitterkitty/mqtt-mysql
*/

#include "Arduino.h"
#include "LoRaWan_APP.h"
//#include <ModbusMaster.h>
// For a connection via I2C using the Arduino Wire include:
#include <Wire.h>               
#include "HT_SH1107Wire.h"
// Handy for seeing missing Modbus bytes
boolean   debug  = false;  // For RS-485 comms.
boolean   debug2 = false; // For LoRa comms.
boolean   info = false; // For status messages
//SH1107Wire  display(0x3c, 500000, SDA, SCL ,GEOMETRY_128_64,GPIO10); // addr, freq, sda, scl, resolution, rst
extern SH1107Wire  display; // Handled by "LoRaWan_APP.h"
// Pins
//
// ./CubeCell-Arduino/cores/asr650x/board/inc/board-config.h
// ./CubeCell-Arduino/cores/asr650x/board/inc/gpio.h
// ./CubeCell-Arduino/cores/asr650x/cores/ASR_Arduino.h
// ./CubeCell-Arduino/cores/asr6601/base/ASR_Arduino.h
// ./CubeCell-Arduino/variants/CubeCell-ModulePlus/pins_arduino.h
// 


#define MAX485_DE       GPIO2 // D2  // data or
#define MAX485_RE       GPIO1 // D1  //      recv enable
void preTransmission()
{
  digitalWrite(MAX485_RE, 0 /* 1 */); // Receiver Enable LOW to receive
  digitalWrite(MAX485_DE, 1); // Driver Enable HIGH to transmit
 // digitalWrite(LED, LOW);
}
void postTransmission()
{
  digitalWrite(MAX485_RE, 0); // Receiver Enable LOW to receive
  digitalWrite(MAX485_DE, 1 /*0*/);  // Driver Enable HIGH to transmit
  //digitalWrite(LED, HIGH);
}

/* OTAA para*/
//uint8_t devEui[] = { xxx.. };  // MSB (V3 slan-031)
//uint8_t appEui[] = { xxx.. };  // MSB
//uint8_t appKey[] = { xxx.. }; // MSB
uint8_t devEui[] = { yyy..};  // MSB (V3 slan-032)
uint8_t appEui[] = { yyy.. };  // MSB
uint8_t appKey[] = { yyy..}; // MSB
  
/* ABP para for xxxxxx */
uint8_t nwkSKey[1];
uint8_t appSKey[1];
uint32_t devAddr = 0; // <-- Change this address for every node!
uint32_t appTxDutyCycle = (5 * 60 * 1000); // the frequency of readings, in milliseconds (set to 5 mins)
uint16_t userChannelsMask[6]={ 0x00FF,0x0000,0x0000,0x0000,0x0000,0x0000 };
LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
DeviceClass_t  loraWanClass = LORAWAN_CLASS;
bool overTheAirActivation = LORAWAN_NETMODE;
bool loraWanAdr = LORAWAN_ADR;
bool keepNet = LORAWAN_NET_RESERVE;
bool isTxConfirmed = LORAWAN_UPLINKMODE;
// 2022/1/3 Had to add this to get code to compile. I guessed at value 10
uint8_t confirmedNbTrials = 10;
uint8_t appPort = 10; // for LS2024B payload
#define TIMEOUT 10//time in ms
uint8_t serialBuffer[256];
int size;







char buf[256];
// Compute the MODBUS RTU CRC
uint16_t ModRTU_CRC(uint8_t *  buf, int len)
{
  uint16_t crc = 0xFFFF;
  if (debug) Serial.printf("Entering ModRTU_CRC()..\n");
  
  for (int pos = 0; pos < len; pos++) {
    crc ^= (uint16_t)buf[pos];          // XOR byte into least sig. byte of crc
  
    for (int i = 8; i != 0; i--) {    // Loop over each bit
      if ((crc & 0x0001) != 0) {      // If the LSB is set
        crc >>= 1;                    // Shift right and XOR 0xA001
        crc ^= 0xA001;
      }
      else                            // Else LSB is not set
        crc >>= 1;                    // Just shift right
    }
  }
  // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes)
  if (debug) Serial.printf("..leaving ModRTU_CRC()\n");
  return crc;  
}

/*
 * Example
 * TX:01 04 31 1d 00 01 af 30
 * RX:0x01, 0x04, 0x02, 0x04, 0xb0, 0xba, 0x44
 */

int readFromRegister( uint16_t address, int size, uint16_t *  buffer ) 
{
  uint8_t   txBuffer[ 16 ];
  uint16_t  crc; 
  
  uint8_t   rxBuffer[ 16 ];
  uint8_t   incomingByte;
  int       rxCount; 
  int       status = 0;

  if (debug) Serial.printf("Entering readFromRegister()..\n");
  if (size > 16) return -1;
  
  // Read data start ing at registerAddress
  txBuffer[ 0 ] = 0x01; // read coils command
  txBuffer[ 1 ] = 0x04; // ??
  txBuffer[ 2 ] = (address >> 8);
  txBuffer[ 3 ] = (address & 0x00FF);  
  txBuffer[ 4 ] = 0;
  txBuffer[ 5 ] = size;  
  crc = ModRTU_CRC( txBuffer, 6 );
  txBuffer[ 6 ] = (crc & 0xFF);  
  txBuffer[ 7 ] = ((crc >> 8) & 0xFF);
     
  if (debug) Serial.printf("\nRequesting %d bytes starting at 0x%04x\n", 
                ((txBuffer[4] * 256) + txBuffer[5])*2,  
                ((txBuffer[2] << 8) + txBuffer[3]) );
  if (debug) {
    for (int i=0; i<8; i++) Serial.printf("%02x ", txBuffer[i] );
    Serial.printf("\n" ); 
  }
  preTransmission();  
  //delay(200);         
  Serial1.write( txBuffer, 8 );
  //delay(1000);
  postTransmission();
  delay(100);
  
  int ignoreFirstNByte = 1; // If not expecting tx chars to be echoed back don't set to 8
  boolean ignoreLastByte = true;
  
  rxCount = 0;
  while ((Serial1.available() > 0) && (rxCount < 50)) {
    // read the incoming byte:
    incomingByte = Serial1.read();
    if (debug) Serial.printf( "%02x, ", incomingByte);
    rxBuffer[ rxCount ] = incomingByte;
    if (ignoreFirstNByte > 0) {
      if (debug) Serial.printf( "WARNING: Ignoring first byte of message 0x%02x\n", incomingByte);
      ignoreFirstNByte--;
      continue; // ignore first byte
    }
    
    if ((rxCount == 0) && (incomingByte != 1)) {
      // Expected response tp be of the form :
      // RX:0x01, 0x04, 0x02, 0x04, 0xb0, 0xba, 0x44
      Serial.printf( "ERROR: Response started with 0x%02x and not 0x01\n", incomingByte);
      status = -1;      
    }
     if ((rxCount == 1) && (incomingByte != 4)) {
      // Expected response tp be of the form :
      // RX:0x01, 0x04, 0x02, 0x04, 0xb0, 0xba, 0x44
      Serial.printf( "ERROR: Response second byte was 0x%02x and not 0x04\n", incomingByte);
      status = -1;      
    }
    
    if ((rxCount == 2) && (incomingByte != (size * 2))) {
      // Expected response to be of the form :
      // RX:0x01, 0x04, 0x02, 0x04, 0xb0, 0xba, 0x44
      Serial.printf( "ERROR: Response had bytes size of 0x%02x and not 0x%02x as expected\n", incomingByte, (size * 2));
      status = -1;
    }    
       
    rxBuffer[ rxCount ] = incomingByte;
    rxCount++;
  }
  delay(200);
  if (rxCount >= 50) {
      if (debug) Serial.printf( "\nERROR: Terminated loop because rcCount(%d) >= 50 bytes\n", rxCount);    
  }
  
  if (ignoreLastByte) {
      if (debug) Serial.printf( "\nWARNING: Ignoring last byte of message 0x%02x\n", incomingByte);
      rxCount--;
  }
  
  if (rxCount != (5 + (2 * size))) {
    // e.g. [01], [04], [02], [00, 01], [78, f0], 
    Serial.printf( "ERROR: Expected %d bytes but received %d\n", (5 + (2 * size)), rxCount );
    status = -1;    
  }
  // If all good let's put the data on the return array
  if (status == 0) { // wasn't set to -1 by a failure
    int j = 0;
    for (int i = 3; i< 3+(2 + size); i += 2) {
      buffer[ j++ ] = (rxBuffer[ i+1 ] << 8) + rxBuffer[ i ];
    }
    status = size;
  }

  if (info) Serial.printf( "INFO: rxCount=%d\n", rxCount);
  
  if (rxCount > 2) {
    // Finally let's check the rx checksum!
    uint16_t rx_crc = ModRTU_CRC( rxBuffer, rxCount-2 );
    uint16_t actual_crc = ((uint16_t)rxBuffer[ rxCount-1  ] << 8) | (uint16_t)rxBuffer[ rxCount-2 ];
    
    if (info) Serial.printf( "INFO: Expect 0x%04x rx checksum and received 0x%04x\n", rx_crc,  actual_crc);
/*
    if (debug) Serial.printf("displaying checksums on OLED display..\n");  
    
    vExtOn();
    delay(100);
    
    display.connect();
    delay(100);    
    
    display.clear(); 
    String row1 = String(rx_crc);  
    String row2 = String(actual_crc);        
    display.drawString(0, 0,  row1 );
    display.drawString(0, 20, row2);
    display.display();
    delay(2000);
    display.clear();

    display.stop();
    delay(100);
    
    vExtOff();     
    delay(100);    
    if (debug) Serial.printf("..displayed\n");
*/          
    if (actual_crc != rx_crc) {
      Serial.printf( "ERROR: Expected 0x%04x rx checksum but received 0x%04x\n", 
           rx_crc,  actual_crc);
      status = -1;       
    }
  }
  if (debug) Serial.printf("..leaving readFromRegister()\n");  
  return status;
}


uint16_t    PV_VOLTAGE    =   0x3100; //(V|100)         Solar charge controller--PV array voltage
uint16_t    PV_CURRENT    =   0X3101; //(A|100)         Solar charge controller--PV array current
uint16_t    PV_POWER_L    =   0x3102; //(W|100)         Solar charge controller--PV array power
uint16_t    PV_POWER_H    =   0x3103; //(W|100)         Solar charge controller--PV array power
uint16_t    BATT_VOLTAGE  =   0x3104; //(V|100)         Battery voltage
uint16_t    BATT_CURRENT  =   0x3105; //(A|100)         Battery charging current
uint16_t    BATT_POWER_L  =   0x3106; //(W|100)         Battery charging power
uint16_t    BATT_POWER_H  =   0x3107; //(W|100)         Battery charging power
// Updated by readLS2024B() and read by prepareTxFrame()
uint16_t    pvV = 0;
uint16_t    pvI = 0;
uint16_t    battV = 0;
uint16_t    battI = 0;
uint16_t    batteryVoltage = 0;
// Accumulated values
uint32_t    pvVLB = 0;
uint32_t    pvILB = 0;
uint32_t    battVLB = 0;
uint32_t    battILB = 0;
uint32_t    batteryVoltageLB = 0;
uint32_t    commsErrors = 0;


#define X_OFFSET 80
// Four measurements with 0.2 sec delay between each so takes about a sec
//
void readLS2024B() {
  int         rxCount;
  uint16_t    rxData[ 16 ];
  boolean     debug = false;
  if (debug) Serial.printf("Entering readLS2024B()..\n");  
  rxCount = readFromRegister( PV_VOLTAGE, 1, rxData );
  if (rxCount > 0) {
    if (debug) Serial.printf( "\nRegister 0x%04x contains 0x%04x\n", PV_VOLTAGE, rxData[0] );
    pvV = 10 * ((rxData[0] & 0x00FF) << 8) + ((rxData[0] & 0xFF00)>> 8);
    if (debug) Serial.printf( "PV_VOLTAGE = %d mV\n", pvV  );
  } else {
    Serial.printf( "\nERROR: reading register 0x%04x\n", PV_VOLTAGE  );
    pvV = 0;
    commsErrors++;     
  }
  /*
  display.clear(); 
  String row2 = String(pvV);        
  display.drawString(0, 0,  "pV (mV)  :" );
  display.drawString(X_OFFSET, 0, row2);
  display.display();
  delay(2000);
  display.clear();
  */
  delay(200);  
    
  rxCount = readFromRegister( PV_CURRENT, 1, rxData );
  if (rxCount > 0) {
    if (debug) Serial.printf( "\nRegister 0x%04x contains 0x%04x\n", PV_CURRENT, rxData[0] );
    pvI = 10 * ((rxData[0] & 0x00FF) << 8) + ((rxData[0] & 0xFF00)>> 8);
    if (debug) Serial.printf( "PV_CURRENT = %d mA\n", pvI  );
  } else {
    Serial.printf( "\nERROR: reading register 0x%04x\n", PV_CURRENT  );
    pvI = 0;
    commsErrors++;     
  }
  /*
  display.clear(); 
  row2 = String(pvI);        
  display.drawString(0, 0,  "pvI (mA) :" );
  display.drawString(X_OFFSET, 0, row2);
  display.display();
  delay(2000);
  display.clear();
  */
  delay(200);  
  
  rxCount = readFromRegister( BATT_VOLTAGE, 1, rxData );
  if (rxCount > 0) {
    if (debug) Serial.printf( "\nRegister 0x%04x contains 0x%04x\n", BATT_VOLTAGE, rxData[0] );
    battV = 10 * ((rxData[0] & 0x00FF) << 8) + ((rxData[0] & 0xFF00)>> 8);
    if (debug) Serial.printf( "BATT_VOLTAGE = %d mV\n", battV  );
  } else {
    Serial.printf( "\nERROR: reading register 0x%04x\n", BATT_VOLTAGE  );
    battV = 0;
    commsErrors++;     
  }
  /*
  display.clear(); 
  row2 = String(battV);        
  display.drawString(0, 0,  "battV (mV):" );
  display.drawString(X_OFFSET, 0, row2);
  display.display();
  delay(2000);
  display.clear();
  */
  delay(200); 
  
  rxCount = readFromRegister( BATT_CURRENT, 1, rxData );
  if (rxCount > 0) {
    if (debug) Serial.printf( "\nRegister 0x%04x contains 0x%04x\n", BATT_CURRENT, rxData[0] );
    battI = 10 * ((rxData[0] & 0x00FF) << 8) + ((rxData[0] & 0xFF00)>> 8);
    if (debug) Serial.printf( "BATT_CURRENT = %d mA\n", battI  );
  } else {
    Serial.printf( "\nERROR: reading register 0x%04x\n", BATT_CURRENT  );
    battI = 0;   
    commsErrors++; 
  }
  /*
  display.clear(); 
  row2 = String(battI);        
  display.drawString(0, 0,  "battI (mA):" );
  display.drawString(X_OFFSET, 0, row2);
  display.display();
  delay(2000);
  display.clear();
  */
  delay(200);  
   
  Serial.printf( "\n");
  if (debug) Serial.printf("..leaving readLS2024B()\n");  
}



// Each reading is accumulated for MAX_TICKS readings then divided by MAX_TICKS before being transmitted.
// 
void  takeMeasurements() {
 
  if (debug) Serial.printf("Entering takeMeasurements()..\n"); 
  
  // Update PV Charge Controller values
  readLS2024B();  
  batteryVoltage = getBatteryVoltage();
  batteryVoltageLB += (uint32_t)batteryVoltage;  
  Serial.printf("CubCell voltage : %d mV  (%d)\n", batteryVoltage, batteryVoltageLB ); 
  
  pvVLB += (uint32_t)pvV; 
  Serial.printf("PV Voltage      : %d mV  (%d)\n", pvV, pvVLB );
  pvILB += (uint32_t)pvI;    
  Serial.printf("PV Current      : %d mA  (%d)\n", pvI, pvILB ); 
  battVLB += (uint32_t)battV; 
  Serial.printf("Battery Voltage : %d mV  (%d)\n", battV, battVLB ); 
  battILB += (uint32_t)battI;  
  Serial.printf("Battery Current : %d mA  (%d)\n", battI, battILB );
  
  Serial.printf("Comms. errors : %d\n", commsErrors ); 
}
  

static void prepareTxFrame( uint8_t port )
{
  if (debug) Serial.printf("Entering prepareTxFrame()..\n"); 
  display.connect();
  vExtOn();  // Turn on display
  delay(100);
  display.init();
  display.setFont(ArialMT_Plain_16); //10, 24
  
 
  String row2 = "";
  
  pvVLB = pvVLB/MAX_TICKS;
  pvILB = pvILB/MAX_TICKS;
  battVLB = battVLB/MAX_TICKS;
  battILB = battILB/MAX_TICKS;
  batteryVoltageLB = batteryVoltageLB/MAX_TICKS;
  
  Serial.printf("PV Voltage      : %d mV\n", pvVLB  );  
  Serial.printf("PV Current      : %d mA\n", pvILB ); 
  Serial.printf("Battery Voltage : %d mV\n", battVLB ); 
  Serial.printf("Battery Current : %d mA\n", battILB );    
  Serial.printf("CubCell voltage : %d mV\n", batteryVoltageLB ); 
  Serial.printf("Comms. errors : %d\n", commsErrors ); 
  
  display.clear(); 
  row2 = String(batteryVoltageLB);        
  display.drawString(0, 0,  "LoRa (mV):" );
  display.drawString(X_OFFSET, 0, row2);
  display.display();
  delay(2000);
  display.clear();
  
  display.clear(); 
  row2 = String(commsErrors);        
  display.drawString(0, 0,  "Comm. Errs:" );
  display.drawString(X_OFFSET+10, 0, row2);
  display.display();
  delay(2000);
  display.clear();
    
  appDataSize = 12;
  appData[0] = highByte(batteryVoltageLB);
  appData[1] = lowByte(batteryVoltageLB);
  appData[2] = highByte(pvVLB);
  appData[3] = lowByte(pvVLB);
  appData[4] = highByte(pvILB);
  appData[5] = lowByte(pvILB);
  appData[6] = highByte(battVLB);
  appData[7] = lowByte(battVLB);
  appData[8] = highByte(battILB);
  appData[9] = lowByte(battILB);
  appData[10] = highByte(commsErrors);
  appData[11] = lowByte(commsErrors);

   
  for( int i=0; i<appDataSize; i++ ) Serial.printf( "0x%02x,", appData[i]);
  Serial.println("");
  vExtOff();   // Turn off display
  display.stop();
  pvVLB = 0;
  pvILB = 0;
  battVLB = 0;
  battILB = 0;
  batteryVoltageLB = 0;
  
}

void vExtOn() {
    pinMode(Vext,OUTPUT);
    digitalWrite(Vext, LOW);
}
void vExtOff() {
    pinMode(Vext,OUTPUT);
    digitalWrite(Vext, HIGH);
}
int tickCounter = 0;
void setup()
{
  boardInitMcu();
  // Serial 1 TX1, RX1 is used for programming and monitor debug output...
  Serial.begin(9600);
  while (!Serial) { ; }
  // Serial 2 TX2, RX2 is used for RS485 comms...
  Serial1.begin(115200);
  while (!Serial) { ; }
  
  Serial.printf("\n\nSketch=%s, version=%s\n", SKETCH_NAME, VERSION);

  // Initialising the UI will init the display too.
  //display(0x3c, 500000, SDA, SCL ,GEOMETRY_128_64,GPIO10); // addr, freq, sda, scl, resolution, rst
  vExtOn();
  delay(100);
    
  display.init();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);
  //  "Cubecell_PV_epever_monitor_slan-032_v1.16"
  String title = SKETCH_NAME;
  int index = title.indexOf("-")-4;
  display.drawString(0, 0, title.substring(1, index));
  display.drawString(0, 20, title.substring(index));
  display.drawString(0, 40, VERSION);
  display.display();
  delay(5000);
  display.clear();
  
  vExtOff(); // turn off display
  display.stop(); 
   
  pinMode(MAX485_RE, OUTPUT);
  pinMode(MAX485_DE, OUTPUT);
  digitalWrite(MAX485_RE, 0);  // Receiver Enable LOW to receive
  digitalWrite(MAX485_DE, 0);  // Driver Enable HIGH to transmit
  
  deviceState = DEVICE_STATE_INIT;
  LoRaWAN.ifskipjoin();
  tickCounter = (MAX_TICKS - 1);  // So we can test LoRaWAN transmission right after a reset
}


// Every time round the loop we take measurents
// but every 5 mins we send them over LoRaWAN
//
boolean looping = true;
void loop() {
  if (debug) Serial.printf("Entering loop()..\n");  
  tickCounter++;
  takeMeasurements(); // Takes about a second
  
  if (tickCounter == MAX_TICKS) {
    deviceState = DEVICE_STATE_INIT;
    tickCounter = 0;
    looping = true;    
    if (looping) {
      while(deviceState != DEVICE_STATE_CYCLE) {
        // Transmit the data but don't let it deep sleep
        transmitLoop(); // resets accumulated readings as well
      }
      
      int period = 30;
      Serial.printf("Delaying %d secs in state DEVICE_STATE_CYCLE to let LoRaWAN transmission take place..\n", period);
      delay(period * 1000); // Delay 10 secs for sending
    }
  }
  delay(LOOP_DELAY_MSECS); 
}
void transmitLoop()
{
  if (debug2) Serial.printf("deviceState=%d\n", deviceState);
  switch( deviceState )
  {
    case DEVICE_STATE_INIT:
    {
      if (debug2) Serial.printf("deviceState = DEVICE_STATE_INIT(%d)\n",deviceState);
      if (debug2) printDevParam();
      LoRaWAN.init(loraWanClass,loraWanRegion);
      deviceState = DEVICE_STATE_JOIN;
      break;
    }
    case DEVICE_STATE_JOIN:
    {
      if (debug2) Serial.printf("deviceState = DEVICE_STATE_JOIN(%d)\n",deviceState );      
      LoRaWAN.join();
      break;
    }
    case DEVICE_STATE_SEND:
    {
      if (debug2) Serial.printf("deviceState = DEVICE_STATE_SEND(%d)\n",deviceState );      
      prepareTxFrame( appPort );
      LoRaWAN.send();
      deviceState = DEVICE_STATE_CYCLE;
      break;
    }
    case DEVICE_STATE_CYCLE:
    {
      if (debug2) Serial.printf("deviceState = DEVICE_STATE_CYCLE(%d)\n",deviceState );      
      // Schedule next packet transmission
      txDutyCycleTime = appTxDutyCycle + randr( 0, APP_TX_DUTYCYCLE_RND );
      LoRaWAN.cycle(txDutyCycleTime);
      deviceState = DEVICE_STATE_SLEEP;
      break;
    }
    case DEVICE_STATE_SLEEP:
    {
      if (debug2) Serial.printf("deviceState = DEVICE_STATE_SLEEP(%d)\n",deviceState ); 
      looping = false;
      break;     
      //LoRaWAN.sleep();
      //break;
    }
    default:
    {
      if (debug) Serial.printf("deviceState = default(%d)\n",deviceState );      
      deviceState = DEVICE_STATE_INIT;
      break;
    }
  }
}

Below is the output from a run





(Last updated Thurs 5th May 2022 18:17)