{"id":31844822,"url":"https://github.com/nsls2/a1soft-ioc","last_synced_at":"2026-01-17T17:40:41.625Z","repository":{"id":318693864,"uuid":"1016243584","full_name":"NSLS2/A1Soft-IOC","owner":"NSLS2","description":"Caproto IOC to control A1Soft spectrum analyzer from MB Scientific AB https://www.mbscientific.se/","archived":false,"fork":false,"pushed_at":"2026-01-16T19:58:05.000Z","size":373,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-17T07:30:03.424Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NSLS2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-07-08T17:50:28.000Z","updated_at":"2026-01-09T21:48:51.000Z","dependencies_parsed_at":"2025-10-08T18:36:05.919Z","dependency_job_id":null,"html_url":"https://github.com/NSLS2/A1Soft-IOC","commit_stats":null,"previous_names":["nsls2/a1soft-ioc"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/NSLS2/A1Soft-IOC","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NSLS2%2FA1Soft-IOC","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NSLS2%2FA1Soft-IOC/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NSLS2%2FA1Soft-IOC/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NSLS2%2FA1Soft-IOC/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NSLS2","download_url":"https://codeload.github.com/NSLS2/A1Soft-IOC/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NSLS2%2FA1Soft-IOC/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28513954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-12T07:50:58.190Z","updated_at":"2026-01-17T17:40:41.601Z","avatar_url":"https://github.com/NSLS2.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A1Soft-IOC\nCaproto IOC to control A1Soft spectrum analyzer from MB Scientific AB https://www.mbscientific.se/ through the provided TCP Server.\n\n\u003e [!NOTE]\n\u003e Much of the behavior described below was deduced through trial \u0026 error. There are a significant amount of unknowns that we still need to discover through this process.\n\n# GenAcqTE TCP Server\n\nThe \"General Acquisition Tango-EPICS\" VI when specified to use the \"EPICS\" Instance Name, runs a TCP server which exports some data and controls over socket connections.\n\n## TCP Sockets\n\nThere are three TCP socket connections.\n\n| Usage | Description |\n|-------|-------------|\n| Data | For images accessed via the `GET_IMAGE` [Action](#Actions). |\n| Commands | For commands sent from a client and responses to that client (in JSON format) |\n| Live plots | For live data monitoring |\n\n## Commands Types\n\nHere are a list of the types of commands able to be processed by the TCP server. It is currently unknown what the \"DATA\" command type does.\n\n| Type | Description |\n|------|-------------|\n| GET  | Get the current value of a [Parameter](#Parameters) |\n| SET  | Set the value of a non-read-only [Parameter](#Parameters) |\n| ACTION | Perform one of the [Actions](#Actions) |\n\n## Parameters\n\n\u003e [!NOTE]\n\u003e For the `acqMode` parameter, we manually changed the LabView TypeDef to include FixedTrigd, which was not included by default.\n\n| Name | Type | Description | Read-Only? | \n|------|------|-------------|------------|\n| state | enum | The state of the acquisition. Options are: RUNNING, MOVING, STANDBY | Y |\n| endX | int | \u003cunknown\u003e | Y |\n| startY | int | \u003cunknown\u003e | Y |\n| numSlice | int | \u003cunknown\u003e | Y |\n| endY | int | \u003cunknown\u003e | Y |\n| startX | int | \u003cunknown\u003e | Y |\n| frames | int | The number of frames to capture | N |\n| numSteps | int | \u003cunknown\u003e | N |\n| passEnergy | enum | Options are PE001, PE002, PE005, PE010, PE020, PE050 | N |\n| lensMode | enum | Options are L4Ang0d6, L4Ang0d8, L4Ang1d6, L4Ang3d9, L4MSpat5, L4Spat5 | N |\n| numScans | int | The number of scans to take | N |\n| regNum | int | \u003cunknown\u003e | Y |\n| totSteps | int | \u003cunknown\u003e | Y |\n| addFms | int | \u003cunknown\u003e | Y |\n| actScans | int | The number of actual scans taken | Y |\n| dithSteps | int | \u003cunknown\u003e | N |\n| startKe | float | The starting kinetic energy of the scan | N |\n| stepSize | float | The energy step size of the scan | N |\n| endKe | float | The ending kinetic energy of the scan | N |\n| spinOffs | float | \u003cunknown\u003e | N |\n| width | float | \u003cunknown\u003e | N |\n| centreKe | float | The center kinetic energy | N |\n| firstEnergy | float | \u003cunknown\u003e | N |\n| deflX | float | \u003cunknown\u003e | N |\n| deflY | float | \u003cunknown\u003e | N |\n| dbl10 | float | \u003cunknown\u003e | Y |\n| acqMode | enum | Options are Fixed, FixedTrigd, Swept, Dither | N |\n| dateNumber | bool | \u003cunknown\u003e | Y |\n| locDet | bool | \u003cunknown\u003e | Y |\n| xtab | bool | \u003cunknown\u003e | N |\n| spin | bool | \u003cunknown\u003e | ? |\n| regName | str | \u003cunknown\u003e | Y |\n| nameString | str | \u003cunknown\u003e | Y |\n| generatedName | str | \u003cunknown\u003e | Y |\n| comment1 | str | \u003cunknown\u003e | N |\n| startTime | str | \u003cunknown\u003e | N |\n| discr | int | \u003cunknown\u003e | Y |\n| adcMask | int | \u003cunknown\u003e | Y |\n| adcOffset | int | \u003cunknown\u003e | Y |\n| pCntType | int | \u003cunknown\u003e | Y |\n| pcMask | int | \u003cunknown\u003e | Y |\n| softBinX | int | \u003cunknown\u003e | N |\n| softBinY | int | \u003cunknown\u003e | N |\n| EScaleMult | float | \u003cunknown\u003e | N |\n| EScaleMax | float | \u003cunknown\u003e | Y |\n| EScaleMin | float | \u003cunknown\u003e | Y |\n| YScaleMult | float | \u003cunknown\u003e | Y |\n| YScaleMax | float | \u003cunknown\u003e | Y |\n| YScaleMin | float | \u003cunknown\u003e | Y |\n| YScaleName | str | \u003cunknown\u003e | Y |\n| XScaleMult | float | \u003cunknown\u003e | Y |\n| XScaleMax | float | \u003cunknown\u003e | Y |\n| XScaleMin | float | \u003cunknown\u003e | Y |\n| XScaleName | str | \u003cunknown\u003e | Y |\n| PsuMode | str | \u003cunknown\u003e | Y |\n| OverRArr | str | \u003cunknown\u003e | Y |\n| OverRange | int | \u003cunknown\u003e | Y |\n\n## Actions\n\n| Name | Description |\n|------|-------------|\n| START | Starts the acquisition |\n| STOP | Stops the acquisition |\n| DET_OFF | Turns the detector off |\n| GET_IMAGE | Requests an image on the data socket |\n| GET_ACQ_STATS | Gets the acquisition state and index |\n\n\n## Data Socket\n\nYou will only receive data on this socket after sending a `GET_IMAGE` [Action](#Actions) over the JSON socket. The schema is\n\n| Name | Byte offset | Description |\n|------|-------------|-------------|\n| Header | 0 - 40 | Contains the header data |\n| Marker | 0 - 4 | \u003cunknown\u003e |\n| Index | 4 - 8 | The current frame number requested since acquisition start |\n| State | 8 - 12 | \u003cunknown\u003e |\n| Reserved | 12 - 16 | \u003cunknown\u003e |\n| Width | 16 - 20 | The width of the first channel image |\n| Height | 20 - 24 | The height of the first channel image |\n| Length | 24 - 28 | The length of the first channel image in bytes |\n| Current width | 28 - 32 | The width of the second channel image |\n| Current height | 32 - 36 | The height of the second channel image |\n| Current length | 36 - 40 | The length of the second channel image in bytes |\n| First channel image | 40 - (40 + Length) | The uint32 byte array for the first channel image, representing the current image being displayed |\n| Second channel image | (40 + length) - (40 + length + current length) | The uint32 byte array for the second channel image, representing the sum of the first channel images in the acquisition so far |\n\nIn Fixed acquisition mode, you can only capture data at a maximum of ~12.5 frames per second. The A1Soft latency between receiving a GET_IMAGE action and putting all of this data into the data socket is greater than the rate at which images are produced. This means that the IOC will never be able to keep up and will drop frames!\n\n## Live Socket\n\nThere is also a live data socket for streaming data from the detector. This is only available while acquiring.\n\nThe \"Live Data\" must be configured to be on and the monitor must be enabled. There is no way to control this from\nthe TCP server, so you must manually configure this in LabView. You can control the rate at which the TCP server adds\nlive data by configuring it in the `parameters.ini` file that should have come with the server.\n\n\u003e [!NOTE]\n\u003e There were some bugs in the `dataTransferLoop.vi` that was causing data to be added to the live socket every 5ms! This overloaded the socket and caused\n\u003e many issues. You have to fix this by either increasing the 5ms to 1000ms or moving the error wire after the socket connection heartbeat. We did both to make it work.\n\nThe schema is\n\n| Name | Byte offset | Description |\n|------|-------------|-------------|\n| Marker | 0 - 4 | Message start marker (delimits frames) |\n| Index | 4 - 8 | The current frame number requested since acquisition start |\n| Width | 8 - 12 | The width of the image |\n| Height | 12 - 16 | The height of the image |\n| Length | 16 - 20 | The length of the image in bytes |\n| Image | 20 - (20 + Length) | The uint32 byte array for the first channel image, representing the current image being displayed |\n\n# Process Variables (PVs)\n\nHere are the tables of EPICS PVs that are hosted by the Caproto server. They are roughly grouped into categories.\n\n`$(P)` corresponds to the specified prefix set via the command-line argument.\n\n## Acquisition Control\n\n| PV Name | Description |\n|---------|-------------|\n| $(P)ACQUIRE | Starts acquiring when set to 1. Stops when set to 0. Does not do anything if already running. |\n| $(P)ACQ:STATUS | Status of the acquisition started via EPICS |\n| $(P)LIVE:MONITORING | Enable/disable live data monitoring from live_port |\n| $(P)LIVE:MAX_COUNT | Current maximum pixel count from live data stream |\n| $(P)LIVE:LAST_UPDATE | Timestamp of last live data update |\n| $(P)LIVE:MAX_COUNT_THRESH | Threshold for the maximum value of a single pixel of the detector |\n| $(P)LIVE:MAX_COUNT_EXCEEDED | Indicates if the maximum count has exceeded the threshold |\n| $(P)LIVE:MAX_COUNT_AVG_N | Window size for averaging the max count (i.e. average over the last n max counts) |\n\n## Detector Control\n\n| PV Name | Description |\n|---------|-------------|\n| $(P)DET:OFF | Perform [Action](#Actions) DET_OFF |\n\n## System Control\n\n| PV Name | Description |\n|---------|-------------|\n| $(P)SYS:CONNECTED | Connection status of the client to the TCP Server, true if all ports are connected. |\n| $(P)SYS:LAST_SYNC | Timestamp of the last time the parameters from A1Soft were synced with the [PVs](#process-variables-pvs) |\n| $(P)SYS:SYNC | Enable/Disable syncing [Detector Parameters](#detector-parameters). Set polling frequency via `.SCAN` record |\n\n## File Writing\n\nThe IOC will open a [NeXus](https://www.nexusformat.org/) file for writing when you turn on `$(P)FILE:CAPTURE`. The file only remains open when there is data available to write.\nWithin a single acquisition, it will continually *update* the frame every time `actScans` increases by 1. Once `actScans` is equal to `numScans`, it will commit this frame to the file. Subsequent acquisitions will append a new frame to the same dataset.\nThis way if you stop a single acquisition in the middle, there will be partial frame data available.\n\n\nThe number of full acquisitions that complete will be equal to the number of frames committed to the file. So if I call `caput $(P)ACQUIRE 1` 5 separate times (waiting for each acquisition to complete in between calls), there will be 5 frames in the file.\n\n| PV Name | Description |\n|---------|-------------|\n| $(P)FILE:CAPTURE | Enable/disable file capture |\n| $(P)FILE:NAME | File name for captured data |\n| $(P)FILE:PATH | File path for captured data |\n| $(P)FILE:NUM_CAPTURED | Number of images captured while file capture is on |\n| $(P)FILE:NUM_PROCESSED | Number of scans processed during a single acquisition |\n\n## Detector Parameters\n\nWhen synchronization is active, these parameters will be updated, by default every second, to match what the parameter values are in LabView. This way, when someone changes one of these parameters in LabView, it gets reflected in the PV values.\n\n| PV Name | Description |\n|---------|-------------|\n| $(P)STATE | Sets and gets `state` |\n| $(P)ENDX | Sets and gets `endX` |\n| $(P)STARTY | Sets and gets `startY` |\n| $(P)NUM_SLICE | Sets and gets `numSlice` |\n| $(P)ENDY | Sets and gets `endY` |\n| $(P)STARTX | Sets and gets `startX` |\n| $(P)FRAMES | Sets and gets `frames` |\n| $(P)NUM_STEPS | Sets and gets `numSteps` |\n| $(P)PASS_ENERGY | Sets and gets `passEnergy` |\n| $(P)LENS_MODE | Sets and gets `lensMode` |\n| $(P)NUM_SCANS | Sets and gets `numScans` |\n| $(P)REG_NUM | Sets and gets `regNum` |\n| $(P)TOT_STEPS | Sets and gets `totSteps` |\n| $(P)ADD_FMS | Sets and gets `addFms` |\n| $(P)ACT_SCANS | Sets and gets `actScans` |\n| $(P)DITH_STEPS | Sets and gets `dithSteps` |\n| $(P)START_KE | Sets and gets `startKe` |\n| $(P)STEP_SIZE | Sets and gets `stepSize` |\n| $(P)END_KE | Sets and gets `endKe` |\n| $(P)SPIN_OFFS | Sets and gets `spinOffs` |\n| $(P)WIDTH | Sets and gets `width` |\n| $(P)CENTER_KE | Sets and gets `centreKe` |\n| $(P)FIRST_ENERGY | Sets and gets `firstEnergy` |\n| $(P)DEFLX | Sets and gets `deflX` |\n| $(P)DEFLY | Sets and gets `deflY` |\n| $(P)DBL10 | Sets and gets `dbl10` |\n| $(P)ACQ_MODE | Sets and gets `acqMode` |\n| $(P)DATE_NUMBER | Sets and gets `dateNumber` |\n| $(P)LOC_DET | Sets and gets `locDet` |\n| $(P)XTAB | Sets and gets `xtab` |\n| $(P)SPIN | Sets and gets `spin` |\n| $(P)REG_NAME | Sets and gets `regName` |\n| $(P)NAME_STRING | Sets and gets `nameString` |\n| $(P)GENERATED_NAME | Sets and gets `generatedName` |\n| $(P)COMMENT1 | Sets and gets `comment1` |\n| $(P)START_TIME | Sets and gets `startTime` |\n| $(P)DISCR | Sets and gets `discr` |\n| $(P)ADC_MASK | Sets and gets `adcMask` |\n| $(P)ADC_OFFSET | Sets and gets `adcOffset` |\n| $(P)P_CNT_TYPE | Sets and gets `pCntType` |\n| $(P)PC_MASK | Sets and gets `pcMask` |\n| $(P)SOFT_BIN_X | Sets and gets `softBinX` |\n| $(P)SOFT_BIN_Y | Sets and gets `softBinY` |\n| $(P)ESCALE_MULT | Sets and gets `EScaleMult` |\n| $(P)ESCALE_MAX | Sets and gets `EScaleMax` |\n| $(P)ESCALE_MIN | Sets and gets `EScaleMin` |\n| $(P)YSCALE_MULT | Sets and gets `YScaleMult` |\n| $(P)YSCALE_MAX | Sets and gets `YScaleMax` |\n| $(P)YSCALE_MIN | Sets and gets `YScaleMin` |\n| $(P)YSCALE_NAME | Sets and gets `YScaleName` |\n| $(P)XSCALE_MULT | Sets and gets `XScaleMult` |\n| $(P)XSCALE_MAX | Sets and gets `XScaleMax` |\n| $(P)XSCALE_MIN | Sets and gets `XScaleMin` |\n| $(P)XSCALE_NAME | Sets and gets `XScaleName` |\n| $(P)PSU_MODE | Sets and gets `PsuMode` |\n| $(P)OVER_R_ARR | Sets and gets `OverRArr` |\n| $(P)OVER_RANGE | Sets and gets `OverRange` |\n\n## Installation\n\nTo install the package:\n\n```bash\npip install .\n```\n\nOr for editable install:\n\n```bash\npip install -e .\n```\n\n## Usage\n\nTo launch the Caproto server:\n\n```bash\npython -m a1soft.ioc --list-pvs --interfaces=127.0.0.1\n```\n\nTo use the Ophyd device in scripts:\n\n```python\nfrom a1soft.ophyd import SpectrumAnalyzer\n\n# Example usage\ndevice = SpectrumAnalyzer('PREFIX:', name='sa')\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnsls2%2Fa1soft-ioc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnsls2%2Fa1soft-ioc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnsls2%2Fa1soft-ioc/lists"}